Renderer.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.wear.watchface

import android.graphics.Bitmap
import android.graphics.Rect
import android.icu.util.Calendar
import android.view.SurfaceHolder
import androidx.annotation.CallSuper
import androidx.annotation.Px
import androidx.annotation.UiThread
import androidx.wear.watchface.style.UserStyleRepository

/** The base class for [CanvasRenderer] and [GlesRenderer]. */
public abstract class Renderer(
    /** The [SurfaceHolder] that [renderInternal] will draw into. */
    _surfaceHolder: SurfaceHolder,

    /** The associated [UserStyleRepository]. */
    internal val userStyleRepository: UserStyleRepository,

    /** The associated [WatchState]. */
    internal val watchState: WatchState
) {
    /** The [SurfaceHolder] this Renderer renders into. */
    protected var surfaceHolder: SurfaceHolder = _surfaceHolder
        private set

    /** The bounds of the [SurfaceHolder] this Renderer renders into. */
    public var screenBounds: Rect = surfaceHolder.surfaceFrame
        private set

    /** The center x coordinate of the [SurfaceHolder] this Renderer renders into. */
    @Px
    public var centerX: Float = screenBounds.exactCenterX()
        private set

    /** The center y coordinate of the [SurfaceHolder] this Renderer renders into. */
    @Px
    public var centerY: Float = screenBounds.exactCenterY()
        private set

    /** The current [RenderParameters]. Updated before every onDraw call. */
    public var renderParameters: RenderParameters = RenderParameters.DEFAULT_INTERACTIVE
        /** @hide */
        internal set(value) {
            if (value != field) {
                field = value
                onRenderParametersChanged(value)
            }
        }

    /** Called when the Renderer is destroyed. */
    @UiThread
    public open fun onDestroy() {
    }

    @UiThread
    public open fun onSurfaceDestroyed(holder: SurfaceHolder) {
    }

    /**
     * Renders the watch face into the [surfaceHolder] using the current [renderParameters]
     * with the user style specified by the [userStyleRepository].
     *
     * @param calendar The Calendar to use when rendering the watch face
     * @return A [Bitmap] containing a screenshot of the watch face
     */
    @UiThread
    internal abstract fun renderInternal(calendar: Calendar)

    /**
     * Renders the watch face into a Bitmap with the user style specified by the
     * [userStyleRepository].
     *
     * @param calendar The Calendar to use when rendering the watch face
     * @param renderParameters The [RenderParameters] to use when rendering the watch face
     * @return A [Bitmap] containing a screenshot of the watch face
     */
    @UiThread
    internal abstract fun takeScreenshot(
        calendar: Calendar,
        renderParameters: RenderParameters
    ): Bitmap

    /**
     * Called when the [DrawMode] has been updated. Will always be called before the first
     * call to onDraw().
     */
    @UiThread
    protected open fun onRenderParametersChanged(renderParameters: RenderParameters) {
    }

    /**
     * This method is used for accessibility support to describe the portion of the screen
     * containing  the main clock element. By default we assume this is contained in the central
     * half of the watch face. Watch faces should override this to return the correct bounds for
     * the main clock element.
     *
     * @return A [Rect] describing the bounds of the watch faces' main clock element
     */
    @UiThread
    public open fun getMainClockElementBounds(): Rect {
        val quarterX = centerX / 2
        val quarterY = centerY / 2
        return Rect(
            (centerX - quarterX).toInt(), (centerY - quarterY).toInt(),
            (centerX + quarterX).toInt(), (centerY + quarterY).toInt()
        )
    }

    /**
     * Convenience for [SurfaceHolder.Callback.surfaceChanged]. Called when the
     * [SurfaceHolder] containing the display surface changes.
     *
     * @param holder The new [SurfaceHolder] containing the display surface
     * @param format The new [android.graphics.PixelFormat] of the surface
     * @param width The width of the new display surface
     * @param height The height of the new display surface
     */
    @CallSuper
    @UiThread
    public open fun onSurfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        screenBounds = holder.surfaceFrame
        centerX = screenBounds.exactCenterX()
        centerY = screenBounds.exactCenterY()
    }

    /**
     * The system periodically (at least once per minute) calls onTimeTick() to trigger a display
     * update. If the watch face needs to animate with an interactive frame rate, calls to
     * invalidate must be scheduled. This method controls whether or not we should do that and if
     * shouldAnimate returns true we inhibit entering [DrawMode.AMBIENT].
     *
     * By default we remain at an interactive frame rate when the watch face is visible and we're
     * not in ambient mode. Watchfaces with animated transitions for entering ambient mode may
     * need to override this to ensure they play smoothly.
     *
     * @return Whether we should schedule an onDraw call to maintain an interactive frame rate
     */
    @UiThread
    public open fun shouldAnimate(): Boolean =
        watchState.isVisible.value && !watchState.isAmbient.value
}