ShellBackground.kt

4.0 KB · raw

1package com.peachpasswords.ui.theme23import androidx.compose.foundation.layout.Box4import androidx.compose.foundation.layout.BoxScope5import androidx.compose.foundation.layout.fillMaxHeight6import androidx.compose.foundation.layout.fillMaxSize7import androidx.compose.foundation.layout.fillMaxWidth8import androidx.compose.foundation.layout.widthIn9import androidx.compose.runtime.Composable10import androidx.compose.ui.Alignment11import androidx.compose.ui.Modifier12import androidx.compose.ui.draw.drawBehind13import androidx.compose.ui.geometry.Offset14import androidx.compose.ui.geometry.Size15import androidx.compose.ui.graphics.Brush16import androidx.compose.ui.graphics.Color17import androidx.compose.ui.unit.dp18import androidx.compose.ui.graphics.drawscope.DrawScope19import androidx.compose.ui.graphics.drawscope.withTransform20import kotlin.math.abs21import kotlin.math.max2223/**24 * The two-bloom shell background that sits behind every screen.25 *26 * Mirrors the browser shell (spec §6.3 / popup.css):27 *   radial-gradient(120% 80% at 0% 0%,    accent 8%, transparent 65%)28 *   radial-gradient(85% 100% at 100% 100%, accent 6%, transparent 70%)29 *   over a flat --bg fill.30 *31 * Both blooms are elliptical. A circular approximation drifts off on tall32 * phones — the top-left bloom's vertical extent (0.8·h) outgrows its33 * horizontal extent (1.2·w) once the aspect ratio passes ~1.5, so we draw a34 * circular gradient and squash it to the CSS ellipse with a non-uniform scale.35 */36@Composable37fun ShellBackground(38    modifier: Modifier = Modifier,39    content: @Composable BoxScope.() -> Unit,40) {41    val accent = LocalPeachAccent.current42    val base = MaterialTheme_background()43    Box(44        modifier = modifier45            .fillMaxSize()46            .drawBehind {47                drawRect(color = base)4849                drawBloom(50                    center = Offset(0f, 0f),51                    radiusX = size.width * 1.2f,52                    radiusY = size.height * 0.8f,53                    color = accent.copy(alpha = 0.08f),54                    transparentAt = 0.65f,55                )56                drawBloom(57                    center = Offset(size.width, size.height),58                    radiusX = size.width * 0.85f,59                    radiusY = size.height * 1.0f,60                    color = accent.copy(alpha = 0.06f),61                    transparentAt = 0.70f,62                )63            },64        content = content,65    )66}6768/**69 * Widest a *form* column grows before it stops. Deliberately NOT applied to70 * every screen: lists and detail views should use a foldable's full width,71 * and blanket-capping them just moved the wasted space to the margins.72 */73val CONTENT_MAX_WIDTH = 480.dp7475/**76 * Draw one elliptical radial bloom. [transparentAt] is the fraction of the77 * ellipse radius where the colour has fully faded — matching the CSS78 * `transparent <pct>` stop so the warmth stays concentrated in its corner.79 */80private fun DrawScope.drawBloom(81    center: Offset,82    radiusX: Float,83    radiusY: Float,84    color: Color,85    transparentAt: Float,86) {87    val r = max(radiusX, radiusY)88    val sx = radiusX / r89    val sy = radiusY / r90    withTransform({ scale(sx, sy, pivot = center) }) {91        // The screen rect, mapped back through the scale, is the area we must92        // paint so the squashed gradient still covers every visible pixel.93        val left = center.x + (0f - center.x) / sx94        val right = center.x + (size.width - center.x) / sx95        val top = center.y + (0f - center.y) / sy96        val bottom = center.y + (size.height - center.y) / sy97        drawRect(98            brush = Brush.radialGradient(99                0f to color,100                transparentAt to Color.Transparent,101                center = center,102                radius = r,103            ),104            topLeft = Offset(minOf(left, right), minOf(top, bottom)),105            size = Size(abs(right - left), abs(bottom - top)),106        )107    }108}109110@Composable111private fun MaterialTheme_background(): Color =112    androidx.compose.material3.MaterialTheme.colorScheme.background