Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc779db1 authored by Yein Jo's avatar Yein Jo Committed by Android (Google) Code Review
Browse files

Merge changes I6fd359c9,I3da01c31 into main

* changes:
  Show edge glowing on keyboard connect
  Add glow box effect
parents 8db1c7d0 8e06308b
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.systemui.surfaceeffects.glowboxeffect

/** Parameters used to play [GlowBoxEffect]. */
data class GlowBoxConfig(
    /** Start center position X in px. */
    val startCenterX: Float,
    /** Start center position Y in px. */
    val startCenterY: Float,
    /** End center position X in px. */
    val endCenterX: Float,
    /** End center position Y in px. */
    val endCenterY: Float,
    /** Width of the box in px. */
    val width: Float,
    /** Height of the box in px. */
    val height: Float,
    /** Color of the box in ARGB, Apply alpha value if needed. */
    val color: Int,
    /** Amount of blur (or glow) of the box. */
    val blurAmount: Float,
    /**
     * Duration of the animation. Note that the full duration of the animation is
     * [duration] + [easeInDuration] + [easeOutDuration].
     */
    val duration: Long,
    /** Ease in duration of the animation. */
    val easeInDuration: Long,
    /** Ease out duration of the animation. */
    val easeOutDuration: Long,
)
+185 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.systemui.surfaceeffects.glowboxeffect

import android.animation.ValueAnimator
import android.graphics.Paint
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import com.android.systemui.surfaceeffects.PaintDrawCallback
import com.android.systemui.surfaceeffects.utils.MathUtils.lerp

/** Glow box effect where the box moves from start to end positions defined in the [config]. */
class GlowBoxEffect(
    private var config: GlowBoxConfig,
    private val paintDrawCallback: PaintDrawCallback,
    private val stateChangedCallback: AnimationStateChangedCallback? = null
) {
    private val glowBoxShader =
        GlowBoxShader().apply {
            setSize(config.width, config.height)
            setCenter(config.startCenterX, config.startCenterY)
            setBlur(config.blurAmount)
            setColor(config.color)
        }
    private var animator: ValueAnimator? = null
    @VisibleForTesting var state: AnimationState = AnimationState.NOT_PLAYING
    private val paint = Paint().apply { shader = glowBoxShader }

    fun updateConfig(newConfig: GlowBoxConfig) {
        this.config = newConfig

        with(glowBoxShader) {
            setSize(config.width, config.height)
            setCenter(config.startCenterX, config.startCenterY)
            setBlur(config.blurAmount)
            setColor(config.color)
        }
    }

    fun play() {
        if (state != AnimationState.NOT_PLAYING) {
            return
        }

        playEaseIn()
    }

    /** Finishes the animation with ease out. */
    fun finish(force: Boolean = false) {
        // If it's playing ease out, cancel immediately.
        if (force && state == AnimationState.EASE_OUT) {
            animator?.cancel()
            return
        }

        // If it's playing either ease in or main, fast-forward to ease out.
        if (state == AnimationState.EASE_IN || state == AnimationState.MAIN) {
            animator?.pause()
            playEaseOut()
        }

        // At this point, animation state should be ease out. Cancel it if force is true.
        if (force) {
            animator?.cancel()
        }
    }

    private fun playEaseIn() {
        if (state == AnimationState.EASE_IN) {
            return
        }
        state = AnimationState.EASE_IN
        stateChangedCallback?.onStart()

        animator =
            ValueAnimator.ofFloat(0f, 1f).apply {
                duration = config.easeInDuration
                addUpdateListener {
                    val progress = it.animatedValue as Float
                    glowBoxShader.setCenter(
                        lerp(config.startCenterX, config.endCenterX, progress),
                        lerp(config.startCenterY, config.endCenterY, progress)
                    )

                    draw()
                }

                doOnEnd {
                    animator = null
                    playMain()
                }

                start()
            }
    }

    private fun playMain() {
        if (state == AnimationState.MAIN) {
            return
        }
        state = AnimationState.MAIN

        animator =
            ValueAnimator.ofFloat(0f, 1f).apply {
                duration = config.duration
                addUpdateListener { draw() }

                doOnEnd {
                    animator = null
                    playEaseOut()
                }

                start()
            }
    }

    private fun playEaseOut() {
        if (state == AnimationState.EASE_OUT) return
        state = AnimationState.EASE_OUT

        animator =
            ValueAnimator.ofFloat(0f, 1f).apply {
                duration = config.easeOutDuration
                addUpdateListener {
                    val progress = it.animatedValue as Float
                    glowBoxShader.setCenter(
                        lerp(config.endCenterX, config.startCenterX, progress),
                        lerp(config.endCenterY, config.startCenterY, progress)
                    )

                    draw()
                }

                doOnEnd {
                    animator = null
                    state = AnimationState.NOT_PLAYING
                    stateChangedCallback?.onEnd()
                }

                start()
            }
    }

    private fun draw() {
        paintDrawCallback.onDraw(paint)
    }

    /**
     * The animation state of the effect. The animation state transitions as follows: [EASE_IN] ->
     * [MAIN] -> [EASE_OUT] -> [NOT_PLAYING].
     */
    enum class AnimationState {
        EASE_IN,
        MAIN,
        EASE_OUT,
        NOT_PLAYING,
    }

    interface AnimationStateChangedCallback {
        /**
         * Triggered when the animation starts, specifically when the states goes from
         * [AnimationState.NOT_PLAYING] to [AnimationState.EASE_IN].
         */
        fun onStart()
        /**
         * Triggered when the animation ends, specifically when the states goes from
         * [AnimationState.EASE_OUT] to [AnimationState.NOT_PLAYING].
         */
        fun onEnd()
    }
}
+58 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.systemui.surfaceeffects.glowboxeffect

import android.graphics.RuntimeShader
import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary

/** Soft box shader. */
class GlowBoxShader : RuntimeShader(GLOW_SHADER) {
    // language=AGSL
    private companion object {
        private const val SHADER =
            """
            uniform half2 in_center;
            uniform half2 in_size;
            uniform half in_blur;
            layout(color) uniform half4 in_color;

            float4 main(float2 fragcoord) {
                half glow = soften(sdBox(fragcoord - in_center, in_size), in_blur);
                return in_color * (1. - glow);
            }
        """

        private const val GLOW_SHADER =
            SdfShaderLibrary.BOX_SDF + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SHADER
    }

    fun setCenter(x: Float, y: Float) {
        setFloatUniform("in_center", x, y)
    }

    fun setSize(width: Float, height: Float) {
        setFloatUniform("in_size", width, height)
    }

    fun setBlur(blurAmount: Float) {
        setFloatUniform("in_blur", blurAmount)
    }

    fun setColor(color: Int) {
        setColorUniform("in_color", color)
    }
}
+11 −2
Original line number Diff line number Diff line
@@ -35,6 +35,15 @@ class SdfShaderLibrary {
            }
        """

        const val BOX_SDF =
            """
                float sdBox(vec2 p, vec2 size) {
                    size = size * 0.5;
                    vec2 d = abs(p) - size;
                    return length(max(d, 0.)) + min(max(d.x, d.y), 0.) / size.y;
                }
            """

        const val ROUNDED_BOX_SDF =
            """
            float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
+24 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.systemui.surfaceeffects.utils

/** Copied from android.utils.MathUtils */
object MathUtils {
    fun lerp(start: Float, stop: Float, amount: Float): Float {
        return start + (stop - start) * amount
    }
}
Loading