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

Commit 0c7f6327 authored by Yein Jo's avatar Yein Jo
Browse files

Add glow box effect

Bug: 324600132
Flag: com.android.systemui.keyboard_docking_indicator
Test: GlowBoxEffectTest
Change-Id: I3da01c319443dd313b27fded2ac676d3d6039d1a
parent 5aa3adcf
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,
)
+147 −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 val config: GlowBoxConfig,
    private val paintDrawCallback: PaintDrawCallback
) {
    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 play() {
        if (state != AnimationState.NOT_PLAYING) {
            return
        }

        playEaseIn()
    }

    fun finish() {
        if (state == AnimationState.NOT_PLAYING || state == AnimationState.EASE_OUT) {
            return
        }

        animator?.pause()
        playEaseOut()
    }

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

        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
                }

                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,
    }
}
+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