GraphicsLayerScope.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.compose.ui.graphics

import androidx.compose.ui.unit.Density

/**
 * Default camera distance for all layers
 */
const val DefaultCameraDistance = 8.0f

/**
 * A scope which can be used to define the effects to apply for the content, such as scaling
 * ([scaleX], [scaleY]), rotation ([rotationX], [rotationY], [rotationZ]), opacity ([alpha]), shadow
 * ([shadowElevation], [shape]), and clipping ([clip], [shape]).
 */
interface GraphicsLayerScope : Density {
    /**
     * The horizontal scale of the drawn area. Default value is `1`.
     */
    var scaleX: Float

    /**
     * The vertical scale of the drawn area. Default value is `1`.
     */
    var scaleY: Float

    /**
     * The alpha of the drawn area. Setting this to something other than `1`
     * will cause the drawn contents to be translucent and setting it to `0` will
     * cause it to be fully invisible. Default value is `1` and the range is between
     * `0` and `1`.
     */
    /*@setparam:FloatRange(from = 0.0, to = 1.0)*/
    var alpha: Float

    /**
     * Horizontal pixel offset of the layer relative to its left bound. Default value is `0`.
     */
    var translationX: Float

    /**
     * Vertical pixel offset of the layer relative to its top bound. Default value is `0`
     */
    var translationY: Float

    /**
     * Sets the elevation for the shadow in pixels. With the [shadowElevation] > 0f and
     * [shape] set, a shadow is produced. Default value is `0` and the value must not be
     * negative.
     */
    /*@setparam:FloatRange(from = 0.0)*/
    var shadowElevation: Float

    /**
     * The rotation, in degrees, of the contents around the horizontal axis in degrees. Default
     * value is `0`.
     */
    var rotationX: Float

    /**
     * The rotation, in degrees, of the contents around the vertical axis in degrees. Default
     * value is `0`.
     */
    var rotationY: Float

    /**
     * The rotation, in degrees, of the contents around the Z axis in degrees. Default value is
     * `0`.
     */
    var rotationZ: Float

    /**
     * Sets the distance along the Z axis (orthogonal to the X/Y plane on which
     * layers are drawn) from the camera to this layer. The camera's distance
     * affects 3D transformations, for instance rotations around the X and Y
     * axis. If the rotationX or rotationY properties are changed and this view is
     * large (more than half the size of the screen), it is recommended to always
     * use a camera distance that's greater than the height (X axis rotation) or
     * the width (Y axis rotation) of this view.
     *
     * The distance of the camera from the drawing plane can have an affect on the
     * perspective distortion of the layer when it is rotated around the x or y axis.
     * For example, a large distance will result in a large viewing angle, and there
     * will not be much perspective distortion of the view as it rotates. A short
     * distance may cause much more perspective distortion upon rotation, and can
     * also result in some drawing artifacts if the rotated view ends up partially
     * behind the camera (which is why the recommendation is to use a distance at
     * least as far as the size of the view, if the view is to be rotated.)
     *
     * The distance is expressed in pixels and must always be positive.
     * Default value is [DefaultCameraDistance]
     */
    /*@setparam:FloatRange(from = 0.0)*/
    var cameraDistance: Float

    /**
     * Offset percentage along the x and y axis for which contents are rotated and scaled.
     * The default value of 0.5f, 0.5f indicates the pivot point will be at the midpoint of the
     * left and right as well as the top and bottom bounds of the layer.
     * Default value is [TransformOrigin.Center]
     */
    var transformOrigin: TransformOrigin

    /**
     * The [Shape] of the layer. When [shadowElevation] is non-zero a shadow is produced using
     * this [shape]. When [clip] is `true` contents will be clipped to this [shape].
     * When clipping, the content will be redrawn when the [shape] changes.
     * Default value is [RectangleShape]
     */
    var shape: Shape

    /**
     * Set to `true` to clip the content to the [shape].
     * Default value is `false`
     */
    var clip: Boolean
}

/**
 * Creates simple [GraphicsLayerScope].
 */
fun GraphicsLayerScope(): GraphicsLayerScope = ReusableGraphicsLayerScope()

internal class ReusableGraphicsLayerScope : GraphicsLayerScope {
    override var scaleX: Float = 1f
    override var scaleY: Float = 1f
    override var alpha: Float = 1f
    override var translationX: Float = 0f
    override var translationY: Float = 0f
    override var shadowElevation: Float = 0f
    override var rotationX: Float = 0f
    override var rotationY: Float = 0f
    override var rotationZ: Float = 0f
    override var cameraDistance: Float = DefaultCameraDistance
    override var transformOrigin: TransformOrigin = TransformOrigin.Center
    override var shape: Shape = RectangleShape
    override var clip: Boolean = false

    internal var graphicsDensity: Density = Density(1.0f)

    override val density: Float
        get() = graphicsDensity.density

    override val fontScale: Float
        get() = graphicsDensity.fontScale

    fun reset() {
        scaleX = 1f
        scaleY = 1f
        alpha = 1f
        translationX = 0f
        translationY = 0f
        shadowElevation = 0f
        rotationX = 0f
        rotationY = 0f
        rotationZ = 0f
        cameraDistance = DefaultCameraDistance
        transformOrigin = TransformOrigin.Center
        shape = RectangleShape
        clip = false
    }
}