Pith - kiln
kiln/app/src/main/java/com/vgmlr/kiln/KilnSettingsKit.kt [11.3 kb]
Modified: 18:59:26 154 026 (20 Aug 026)
3 Days Ago
package com.vgmlr.kiln

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.foundation.Image
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextStyle
import androidx.compose.material.icons.filled.Create

internal val CBg = Color(0xFF0E0F11)
internal val CCard = Color(0xFF272A30)
internal val CText = Color(0xFFE8EAED)
internal val CMuted = Color(0xFF868D97)
internal val CChip = Color(0xFF3A4048)

internal val ROW_H = 54.dp
internal val FS = 15.sp

@Composable
internal fun WgSection(title: String, content: @Composable ColumnScope.() -> Unit) {
    WgSectionLabel(title)
    WgCard(content = content)
    Spacer(Modifier.height(24.dp))
}

@Composable
internal fun WgSectionLabel(title: String) {
    Text(
        text = title.uppercase(),
        color = CMuted,
        fontSize = 11.sp,
        fontWeight = FontWeight.Medium,
        letterSpacing = 1.4.sp,
        modifier = Modifier.padding(start = 4.dp, bottom = 8.dp)
    )
}

@Composable
internal fun WgCard(
    modifier: Modifier = Modifier,
    content: @Composable ColumnScope.() -> Unit
) {
    Column(
        modifier.fillMaxWidth().clip(RoundedCornerShape(14.dp)).background(CCard),
        content = content
    )
}

@Composable
internal fun WgRow(
    label: String,
    bg: Color = Color.Transparent,
    edit: Boolean = false,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .heightIn(min = ROW_H)
            .background(bg)
            .padding(horizontal = 16.dp, vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Row(
            modifier = Modifier.weight(1f),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = label,
                color = CText.copy(alpha = 0.8f),
                fontSize = FS,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.weight(1f, fill = false)
            )
            if (edit) {
                Spacer(Modifier.width(6.dp))
                Icon(
                    imageVector = Icons.Filled.Create,
                    contentDescription = null,
                    tint = CText.copy(alpha = 0.6f),
                    modifier = Modifier.size(14.dp)
                )
            }
        }
        Spacer(Modifier.width(12.dp))
        Row(
            modifier = Modifier.weight(1f),
            horizontalArrangement = Arrangement.End,
            verticalAlignment = Alignment.CenterVertically,
            content = content
        )
    }
}

@Composable
internal fun WgDivider() {
    HorizontalDivider(thickness = 1.dp, color = CBg)
}

@Composable
internal fun WgValue(
    text: String,
    modifier: Modifier = Modifier,
    mono: Boolean = false,
    color: Color = CText,
    maxLines: Int = 1
) {
    Text(
        text = text,
        color = color,
        fontSize = FS,
        fontFamily = if (mono) FontFamily.Monospace else FontFamily.Default,
        textAlign = TextAlign.End,
        maxLines = maxLines,
        overflow = TextOverflow.Ellipsis,
        modifier = modifier
    )
}

@Composable
internal fun WgTextField(
    value: String,
    placeholder: String = "",
    onValueChange: (String) -> Unit
) {
    BasicTextField(
        value = value,
        onValueChange = onValueChange,
        singleLine = true,
        modifier = Modifier.fillMaxWidth(),
        textStyle = TextStyle(
            color = CText,
            fontSize = FS,
            fontFamily = FontFamily.Monospace,
            textAlign = TextAlign.End
        ),
        cursorBrush = SolidColor(CText),
        decorationBox = { inner ->
            Box(contentAlignment = Alignment.CenterEnd) {
                if (value.isEmpty()) WgValue(placeholder, mono = true, color = CMuted)
                inner()
            }
        }
    )
}

@Composable
internal fun WgDropdown(
    value: String,
    options: List<String>,
    placeholder: String = "",
    disabledOptions: List<String> = emptyList(),
    onSelect: (String) -> Unit
) {
    var expanded by remember { mutableStateOf(false) }

    Box {
        Row(
            modifier = Modifier.clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null
            ) { expanded = true },
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = value.ifBlank { placeholder },
                color = if (value.isBlank()) CMuted else CText,
                fontSize = FS,
                fontFamily = FontFamily.Monospace,
                maxLines = 1,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.weight(1f, fill = false)
            )
            Icon(
                imageVector = Icons.Default.KeyboardArrowDown,
                contentDescription = null,
                tint = CMuted,
                modifier = Modifier.size(18.dp)
            )
        }
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            containerColor = Color(0xFF1F2226),
            shape = RoundedCornerShape(10.dp),
            tonalElevation = 0.dp,
            shadowElevation = 0.dp
        ) {
            options.forEach { opt ->
                val disabled = opt in disabledOptions
                DropdownMenuItem(
                    enabled = !disabled,
                    text = {
                        Text(
                            text = opt,
                            color = when {
                                disabled -> CMuted.copy(alpha = 0.4f)
                                opt == value -> CMuted
                                else -> CText
                            },
                            fontSize = FS,
                            fontFamily = FontFamily.Monospace,
                            maxLines = 1,
                            overflow = TextOverflow.Ellipsis
                        )
                    },
                    onClick = { onSelect(opt); expanded = false },
                    contentPadding = PaddingValues(horizontal = 14.dp)
                )
            }
        }
    }
}

@Composable
internal fun WgImageDropdown(
    value: Int,
    options: List<Int>,
    size: Dp = 34.dp,
    onSelect: (Int) -> Unit
) {
    var expanded by remember { mutableStateOf(false) }

    Box {
        Row(
            modifier = Modifier.clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null
            ) { expanded = true },
            verticalAlignment = Alignment.CenterVertically
        ) {
            Image(painterResource(value), null, Modifier.size(size))
            Icon(
                imageVector = Icons.Default.KeyboardArrowDown,
                contentDescription = null,
                tint = CMuted,
                modifier = Modifier.size(18.dp)
            )
        }
        DropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false },
            containerColor = Color(0xFF1F2226),
            shape = RoundedCornerShape(10.dp),
            tonalElevation = 0.dp,
            shadowElevation = 0.dp
        ) {
            options.forEachIndexed { i, res ->
                DropdownMenuItem(
                    text = {
                        Image(
                            painterResource(res),
                            null,
                            Modifier.size(size),
                            alpha = if (res == value) 0.45f else 1f
                        )
                    },
                    onClick = { onSelect(i); expanded = false },
                    contentPadding = PaddingValues(horizontal = 14.dp)
                )
            }
        }
    }
}

@Composable
internal fun WgChip(
    label: String,
    selected: Boolean,
    enabled: Boolean = true,
    shape: Shape = RoundedCornerShape(7.dp),
    border: Boolean = true,
    leftLine: Color? = null,
    onClick: () -> Unit
) {
    val filled = selected && enabled

    Box(
        modifier = Modifier
            .height(28.dp)
            .clip(shape)
            .background(if (filled) CChip else Color.Transparent)
            .then(
                if (leftLine != null) Modifier.drawBehind {
                    val w = 1.dp.toPx()
                    drawLine(leftLine, Offset(w / 2f, 0f), Offset(w / 2f, size.height), w)
                } else Modifier
            )
            .then(
                if (border) Modifier.border(
                    width = 1.dp,
                    color = if (filled) CChip else CMuted.copy(alpha = 0.30f),
                    shape = shape
                ) else Modifier
            )
            .clickable(
                enabled = enabled,
                interactionSource = remember { MutableInteractionSource() },
                indication = null,
                onClick = onClick
            )
            .padding(horizontal = 16.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = label,
            color = when {
                !enabled -> CMuted.copy(alpha = 0.4f)
                selected -> CText
                else -> CMuted
            },
            fontSize = 13.sp,
            fontFamily = FontFamily.Monospace,
            maxLines = 1
        )
    }
}

@Composable
internal fun WgRadio(
    value: String,
    options: List<String>,
    disabledOptions: List<String> = emptyList(),
    onSelect: (String) -> Unit
) {
    val shape = RoundedCornerShape(7.dp)
    val outline = CMuted.copy(alpha = 0.30f)

    Row(
        modifier = Modifier
            .height(28.dp)
            .clip(shape)
            .border(1.dp, outline, shape),
        verticalAlignment = Alignment.CenterVertically
    ) {
        options.forEachIndexed { i, opt ->
            WgChip(
                label = opt,
                selected = opt == value,
                enabled = opt !in disabledOptions,
                shape = RectangleShape,
                border = false,
                leftLine = if (i > 0) outline else null,
                onClick = { onSelect(opt) }
            )
        }
    }
}
Updates
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026
Grit - Firefox/Chrome 154.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00255
256,896 (+150)