ShellBackground.kt

3.5 KB · raw

1package com.peachpasswords.ui.theme23import androidx.compose.foundation.layout.Box4import androidx.compose.foundation.layout.BoxScope5import androidx.compose.foundation.layout.fillMaxSize6import androidx.compose.runtime.Composable7import androidx.compose.ui.Modifier8import androidx.compose.ui.draw.drawBehind9import androidx.compose.ui.geometry.Offset10import androidx.compose.ui.geometry.Size11import androidx.compose.ui.graphics.Brush12import androidx.compose.ui.graphics.Color13import androidx.compose.ui.graphics.drawscope.DrawScope14import androidx.compose.ui.graphics.drawscope.withTransform15import kotlin.math.abs16import kotlin.math.max1718/**19 * The two-bloom shell background that sits behind every screen.20 *21 * Mirrors the browser shell (spec §6.3 / popup.css):22 *   radial-gradient(120% 80% at 0% 0%,    accent 8%, transparent 65%)23 *   radial-gradient(85% 100% at 100% 100%, accent 6%, transparent 70%)24 *   over a flat --bg fill.25 *26 * Both blooms are elliptical. A circular approximation drifts off on tall27 * phones — the top-left bloom's vertical extent (0.8·h) outgrows its28 * horizontal extent (1.2·w) once the aspect ratio passes ~1.5, so we draw a29 * circular gradient and squash it to the CSS ellipse with a non-uniform scale.30 */31@Composable32fun ShellBackground(33    modifier: Modifier = Modifier,34    content: @Composable BoxScope.() -> Unit,35) {36    val accent = LocalPeachAccent.current37    val base = MaterialTheme_background()38    Box(39        modifier = modifier40            .fillMaxSize()41            .drawBehind {42                drawRect(color = base)4344                drawBloom(45                    center = Offset(0f, 0f),46                    radiusX = size.width * 1.2f,47                    radiusY = size.height * 0.8f,48                    color = accent.copy(alpha = 0.08f),49                    transparentAt = 0.65f,50                )51                drawBloom(52                    center = Offset(size.width, size.height),53                    radiusX = size.width * 0.85f,54                    radiusY = size.height * 1.0f,55                    color = accent.copy(alpha = 0.06f),56                    transparentAt = 0.70f,57                )58            },59        content = content,60    )61}6263/**64 * Draw one elliptical radial bloom. [transparentAt] is the fraction of the65 * ellipse radius where the colour has fully faded — matching the CSS66 * `transparent <pct>` stop so the warmth stays concentrated in its corner.67 */68private fun DrawScope.drawBloom(69    center: Offset,70    radiusX: Float,71    radiusY: Float,72    color: Color,73    transparentAt: Float,74) {75    val r = max(radiusX, radiusY)76    val sx = radiusX / r77    val sy = radiusY / r78    withTransform({ scale(sx, sy, pivot = center) }) {79        // The screen rect, mapped back through the scale, is the area we must80        // paint so the squashed gradient still covers every visible pixel.81        val left = center.x + (0f - center.x) / sx82        val right = center.x + (size.width - center.x) / sx83        val top = center.y + (0f - center.y) / sy84        val bottom = center.y + (size.height - center.y) / sy85        drawRect(86            brush = Brush.radialGradient(87                0f to color,88                transparentAt to Color.Transparent,89                center = center,90                radius = r,91            ),92            topLeft = Offset(minOf(left, right), minOf(top, bottom)),93            size = Size(abs(right - left), abs(bottom - top)),94        )95    }96}9798@Composable99private fun MaterialTheme_background(): Color =100    androidx.compose.material3.MaterialTheme.colorScheme.background