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

Commit 8b0adc5f authored by Lucas Dupin's avatar Lucas Dupin Committed by Android (Google) Code Review
Browse files

Merge "Magic action drawable with turbulence and sparkles" into main

parents 1600fc4b c12c5029
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1122,6 +1122,7 @@
    <dimen name="smart_reply_button_corner_radius">8dp</dimen>
    <dimen name="smart_action_button_icon_size">18dp</dimen>
    <dimen name="smart_action_button_icon_padding">8dp</dimen>
    <dimen name="smart_action_button_outline_stroke_width">2dp</dimen>

    <!-- A reasonable upper bound for the height of the smart reply button. The measuring code
            needs to start with a guess for the maximum size. Currently two-line smart reply buttons
+183 −12
Original line number Diff line number Diff line
@@ -17,39 +17,210 @@
package com.android.systemui.statusbar.notification.row

import android.content.Context
import android.graphics.BlendMode
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.RuntimeShader
import android.graphics.Shader
import android.graphics.drawable.Drawable
import android.widget.FrameLayout
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.internal.graphics.ColorUtils
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
import kotlin.math.max
import kotlin.math.roundToInt

/**
 * A background style for smarter-smart-actions.
 *
 * TODO(b/383567383) implement final UX
 * A background style for smarter-smart-actions. The style is composed by a simplex3d noise,
 * overlaid with sparkles.
 */
class MagicActionBackgroundDrawable(context: Context) : Drawable() {
class MagicActionBackgroundDrawable(
    context: Context,
    primaryContainer: Int? = null,
    private val seed: Float = 0f,
) : Drawable() {

    private val pixelDensity = context.resources.displayMetrics.density
    private val cornerRadius =
        context.resources.getDimensionPixelSize(R.dimen.smart_reply_button_corner_radius).toFloat()
    private val outlineStrokeWidth =
        context.resources
            .getDimensionPixelSize(R.dimen.smart_action_button_outline_stroke_width)
            .toFloat()
    private val buttonShape = Path()
    private val paddingVertical =
        context.resources
            .getDimensionPixelSize(R.dimen.smart_reply_button_padding_vertical)
            .toFloat()

    /** The color of the button background. */
    private val mainColor =
        primaryContainer
            ?: context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)

    /** Slightly dimmed down version of [mainColor] used on the simplex noise. */
    private val dimColor: Int
        get() {
            val labColor = arrayOf(0.0, 0.0, 0.0).toDoubleArray()
            ColorUtils.colorToLAB(mainColor, labColor)
            val camColor = ColorUtils.colorToCAM(mainColor)
            return ColorUtils.CAMToColor(
                camColor.hue,
                camColor.chroma,
                max(0f, (labColor[0] - 20).toFloat()),
            )
        }

    private val bgShader = MagicActionBackgroundShader()
    private val bgPaint = Paint()
    private val outlinePaint = Paint()

    private var _alpha: Int = 255
    private var _colorFilter: ColorFilter? = null
    private val paint =
        Paint().apply {
            color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
    init {
        bgShader.setColorUniform("in_color", mainColor)
        bgShader.setColorUniform("in_dimColor", dimColor)
        bgPaint.shader = bgShader
        outlinePaint.style = Paint.Style.STROKE
        // Stroke is doubled in width and then clipped, to avoid anti-aliasing artifacts at the edge
        // of the rectangle.
        outlinePaint.strokeWidth = outlineStrokeWidth * 2
        outlinePaint.blendMode = BlendMode.SCREEN
        outlinePaint.alpha = (255 * 0.32f).roundToInt()
    }

    override fun draw(canvas: Canvas) {
        canvas.drawRect(bounds, paint)
        // We clip instead of drawing 2 rounded rects, otherwise there will be artifacts where
        // around the button background and the outline.
        canvas.clipPath(buttonShape)

        canvas.drawRect(bounds, bgPaint)
        canvas.drawRoundRect(
            bounds.left.toFloat(),
            bounds.top + paddingVertical,
            bounds.right.toFloat(),
            bounds.bottom - paddingVertical,
            cornerRadius,
            cornerRadius,
            outlinePaint,
        )
    }

    override fun onBoundsChange(bounds: Rect) {
        super.onBoundsChange(bounds)

        val width = bounds.width().toFloat()
        val height = bounds.height() - paddingVertical * 2
        if (width == 0f || height == 0f) return

        bgShader.setFloatUniform("in_gridNum", NOISE_SIZE)
        bgShader.setFloatUniform("in_spkarkleAlpha", SPARKLE_ALPHA)
        bgShader.setFloatUniform("in_noiseMove", 0f, 0f, 0f)
        bgShader.setFloatUniform("in_size", width, height)
        bgShader.setFloatUniform("in_aspectRatio", width / height)
        bgShader.setFloatUniform("in_time", seed)
        bgShader.setFloatUniform("in_pixelDensity", pixelDensity)

        buttonShape.reset()
        buttonShape.addRoundRect(
            bounds.left.toFloat(),
            bounds.top + paddingVertical,
            bounds.right.toFloat(),
            bounds.bottom - paddingVertical,
            cornerRadius,
            cornerRadius,
            Path.Direction.CW,
        )

        val outlineGradient =
            LinearGradient(
                bounds.left.toFloat(),
                0f,
                bounds.right.toFloat(),
                0f,
                mainColor,
                ColorUtils.setAlphaComponent(mainColor, 0),
                Shader.TileMode.CLAMP,
            )
        outlinePaint.shader = outlineGradient
    }

    override fun setAlpha(alpha: Int) {
        _alpha = alpha
        bgPaint.alpha = alpha
        invalidateSelf()
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        _colorFilter = colorFilter
        bgPaint.colorFilter = colorFilter
        invalidateSelf()
    }

    override fun getOpacity(): Int = PixelFormat.TRANSLUCENT

    companion object {
        /** Smoothness of the turbulence. Larger numbers yield more detail. */
        private const val NOISE_SIZE = 0.7f

        /** Strength of the sparkles overlaid on the turbulence. */
        private const val SPARKLE_ALPHA = 0.15f
    }
}

private class MagicActionBackgroundShader : RuntimeShader(SHADER) {

    // language=AGSL
    companion object {
        private const val UNIFORMS =
            """
            uniform float in_gridNum;
            uniform vec3 in_noiseMove;
            uniform vec2 in_size;
            uniform float in_aspectRatio;
            uniform half in_time;
            uniform half in_pixelDensity;
            uniform float in_spkarkleAlpha;
            layout(color) uniform vec4 in_color;
            layout(color) uniform vec4 in_dimColor;
        """
        private const val MAIN_SHADER =
            """vec4 main(vec2 p) {
            vec2 uv = p / in_size.xy;
            uv.x *= in_aspectRatio;
            vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
            half luma = 1.0 - getLuminosity(half3(simplex3d(noiseP)));
            half4 turbulenceColor = mix(in_color, in_dimColor, luma);
            float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time);
            sparkle = min(sparkle * in_spkarkleAlpha, in_spkarkleAlpha);
            return turbulenceColor + half4(half3(sparkle), 1.0);
        }
        """
        private const val SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER
    }
}

// @Preview
@Composable
fun DrawablePreview() {
    AndroidView(
        factory = { context ->
            FrameLayout(context).apply {
                background =
                    MagicActionBackgroundDrawable(
                        context = context,
                        primaryContainer = Color.parseColor("#c5eae2"),
                        seed = 0f,
                    )
            }
        },
        modifier = Modifier.size(100.dp, 50.dp),
    )
}
+5 −0
Original line number Diff line number Diff line
@@ -408,6 +408,11 @@ constructor(
                            action.extras.getBoolean(Notification.Action.EXTRA_IS_MAGIC, false)
                    ) {
                        background = MagicActionBackgroundDrawable(parent.context)
                        val textColor =
                            parent.context.getColor(
                                com.android.internal.R.color.materialColorOnPrimaryContainer
                            )
                        setTextColor(textColor)
                    }
                }