diff --git a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/components/settings/NumberOption.kt b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/components/settings/NumberOption.kt index 8cb559c03..9f7fa30aa 100644 --- a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/components/settings/NumberOption.kt +++ b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/components/settings/NumberOption.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.hoverable import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -23,8 +24,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -72,6 +78,24 @@ fun spinnerStep(step: Float, min: Float, max: Float): Float = when { fun formatSpinnerValue(v: Float): String = if (v == v.toLong().toFloat()) v.toLong().toString() else String.format(Locale.ROOT, "%.2f", v) +@Composable +private fun rememberUnitSuffix(unit: String?): VisualTransformation { + val color = LocalTheme.current.textColorSecondary + return remember(unit, color) { + if (unit == null) VisualTransformation.None else VisualTransformation { typed -> + val shown = AnnotatedString.Builder(typed).apply { + pushStyle(SpanStyle(color = color)) + append(unit) + pop() + }.toAnnotatedString() + TransformedText(shown, object : OffsetMapping { + override fun originalToTransformed(offset: Int) = offset + override fun transformedToOriginal(offset: Int) = offset.coerceAtMost(typed.length) + }) + } + } +} + @Composable private fun SpinnerArrow(icon: String, enabled: Boolean, onClick: () -> Unit) { val theme = LocalTheme.current @@ -116,6 +140,9 @@ fun NumberSpinner( arrowMin: Float = min, arrowMax: Float = max, onEditEnd: (() -> Unit)? = null, + unit: String? = null, + label: String? = null, + icon: String? = null, ) { val theme = LocalTheme.current var focused by remember { mutableStateOf(false) } @@ -153,12 +180,23 @@ fun NumberSpinner( fontFamily = theme.typography.family, ), cursorBrush = SolidColor(theme.textColor), + visualTransformation = rememberUnitSuffix(unit), // the field itself stays in the tree while the formatted text covers it so clicking it still focuses it for editing decorationBox = { innerTextField -> - Box { - Box(Modifier.alpha(if (formatted == null) 1f else 0f)) { innerTextField() } - if (formatted != null) { - Text(formatted, color = theme.textColor, fontSize = 13.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + if (label != null) Text(label, color = theme.textColor, fontSize = 13.sp) + if (icon != null) Icon(icon, color = theme.textColor, modifier = Modifier.size(12.dp)) + Box { + Box(Modifier.alpha(if (formatted == null) 1f else 0f)) { innerTextField() } + if (formatted != null) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(formatted, color = theme.textColor, fontSize = 13.sp, maxLines = 1, overflow = TextOverflow.Ellipsis) + if (unit != null) Text(unit, color = theme.textColorSecondary, fontSize = 13.sp) + } + } } } }, diff --git a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/components/Number.kt b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/components/Number.kt deleted file mode 100644 index 515ffb1f4..000000000 --- a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/components/Number.kt +++ /dev/null @@ -1,244 +0,0 @@ -package org.polyfrost.oneconfig.internal.ui.hud.components - -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshots.Snapshot -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import org.polyfrost.oneconfig.api.ui.v1.keybind.trackTextInputFocus -import org.polyfrost.oneconfig.internal.ui.components.Icon -import org.polyfrost.oneconfig.internal.ui.components.Text -import org.polyfrost.oneconfig.internal.ui.components.settings.SpinnerArrows -import org.polyfrost.oneconfig.internal.ui.components.settings.filterNumberInput -import org.polyfrost.oneconfig.internal.ui.components.settings.formatSpinnerValue -import org.polyfrost.oneconfig.internal.ui.themes.LocalTheme - -@Composable -fun NumberSpinner( - label: String, - unit: String, - value: Float, - onValueChange: (Float) -> Unit, - min: Float, - max: Float, - step: Float, - width: Dp = 128.dp, -) { - val theme = LocalTheme.current - var text by remember(value) { mutableStateOf(formatSpinnerValue(value)) } - - fun commit(v: Float) { - val clamped = v.coerceIn(min, max) - text = formatSpinnerValue(clamped) - Snapshot.withMutableSnapshot { - onValueChange(clamped) - } - } - - Row( - modifier = Modifier - .width(width) - .height(32.dp) - .background(theme.componentBackground, theme.sideBarNavigationEntryShape) - .border(1.dp, theme.borderColor, theme.sideBarNavigationEntryShape), - verticalAlignment = Alignment.CenterVertically, - ) { - BasicTextField( - value = text, - onValueChange = { input -> - val filtered = filterNumberInput(input) - text = filtered - filtered.toFloatOrNull()?.coerceIn(min, max)?.let(onValueChange) - }, - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - textStyle = TextStyle( - color = theme.textColor, - fontSize = 12.sp, - fontFamily = theme.typography.family, - ), - cursorBrush = SolidColor(theme.textColor), - modifier = Modifier.trackTextInputFocus() - .weight(1f) - .padding(start = 8.dp, top = 5.dp, bottom = 5.dp), - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text(label, color = LocalTheme.current.textColor, fontSize = 12.sp) - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp), - verticalAlignment = Alignment.CenterVertically - ) { - it() - Text(unit, color = LocalTheme.current.textColorSecondary, fontSize = 12.sp) - } - } - } - - SpinnerArrows(value, min, max, step) { commit(it) } - } -} -@Composable -fun NumberSpinner( - label: String, - icon: String, - unit: String, - value: Float, - onValueChange: (Float) -> Unit, - min: Float, - max: Float, - step: Float, - width: Dp = 128.dp, -) { - val theme = LocalTheme.current - var text by remember(value) { mutableStateOf(formatSpinnerValue(value)) } - - fun commit(v: Float) { - val clamped = v.coerceIn(min, max) - text = formatSpinnerValue(clamped) - Snapshot.withMutableSnapshot { - onValueChange(clamped) - } - } - - Column( - verticalArrangement = Arrangement.spacedBy(10.dp), - ) { - Text(label, color = LocalTheme.current.textColor, fontSize = 14.sp) - Row( - modifier = Modifier - .width(width) - .height(32.dp) - .background(theme.componentBackground, theme.sideBarNavigationEntryShape) - .border(1.dp, theme.borderColor, theme.sideBarNavigationEntryShape), - verticalAlignment = Alignment.CenterVertically, - ) { - BasicTextField( - value = text, - onValueChange = { input -> - val filtered = filterNumberInput(input) - text = filtered - filtered.toFloatOrNull()?.coerceIn(min, max)?.let(onValueChange) - }, - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - textStyle = TextStyle( - color = theme.textColor, - fontSize = 12.sp, - fontFamily = theme.typography.family, - ), - cursorBrush = SolidColor(theme.textColor), - modifier = Modifier.trackTextInputFocus() - .weight(1f) - .padding(start = 8.dp, top = 5.dp, bottom = 5.dp), - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(icon, color = LocalTheme.current.textColor, modifier = Modifier.size(12.dp)) - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp), - verticalAlignment = Alignment.CenterVertically - ) { - it() - Text(unit, color = LocalTheme.current.textColorSecondary, fontSize = 12.sp) - } - } - } - - SpinnerArrows(value, min, max, step) { commit(it) } - } - } -} - - -@Composable -fun NumberSpinnerWithIcon( - icon: String, - unit: String, - value: Float, - onValueChange: (Float) -> Unit, - min: Float, - max: Float, - step: Float, - width: Dp = 62.dp, -) { - val theme = LocalTheme.current - var text by remember(value) { mutableStateOf(formatSpinnerValue(value)) } - - fun commit(v: Float) { - val clamped = v.coerceIn(min, max) - text = formatSpinnerValue(clamped) - Snapshot.withMutableSnapshot { - onValueChange(clamped) - } - } - - Row( - modifier = Modifier - .width(width) - .height(32.dp) - .background(theme.componentBackground, theme.sideBarNavigationEntryShape) - .border(1.dp, theme.borderColor, theme.sideBarNavigationEntryShape), - verticalAlignment = Alignment.CenterVertically, - ) { - BasicTextField( - value = text, - onValueChange = { input -> - val filtered = filterNumberInput(input) - text = filtered - filtered.toFloatOrNull()?.coerceIn(min, max)?.let(onValueChange) - }, - singleLine = true, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), - textStyle = TextStyle( - color = theme.textColor, - fontSize = 12.sp, - fontFamily = theme.typography.family, - ), - cursorBrush = SolidColor(theme.textColor), - modifier = Modifier.trackTextInputFocus() - .weight(1f) - .padding(start = 6.dp, top = 5.dp, bottom = 5.dp), - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon(icon, color = LocalTheme.current.textColor, modifier = Modifier.size(12.dp)) - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp), - verticalAlignment = Alignment.CenterVertically - ) { - it() - Text(unit, color = LocalTheme.current.textColorSecondary, fontSize = 12.sp) - } - } - } - - SpinnerArrows(value, min, max, step) { commit(it) } - } -} diff --git a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Designer.kt b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Designer.kt index b45ca5aa6..f98e6b9ca 100644 --- a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Designer.kt +++ b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Designer.kt @@ -48,13 +48,12 @@ import org.polyfrost.oneconfig.internal.ui.components.onClick import org.polyfrost.oneconfig.internal.ui.components.rememberInteractionSource import org.polyfrost.oneconfig.internal.ui.components.settings.ColorPickerModel import org.polyfrost.oneconfig.internal.ui.components.settings.ColorPickerPopup +import org.polyfrost.oneconfig.internal.ui.components.settings.NumberSpinner import org.polyfrost.oneconfig.internal.ui.components.settings.SwitchControl import org.polyfrost.oneconfig.internal.ui.hud.HudSettingTarget import org.polyfrost.oneconfig.internal.ui.hud.HudSettingsContent import org.polyfrost.oneconfig.internal.ui.hud.components.AlignmentPicker import org.polyfrost.oneconfig.internal.ui.hud.components.Dropdown -import org.polyfrost.oneconfig.internal.ui.hud.components.NumberSpinner -import org.polyfrost.oneconfig.internal.ui.hud.components.NumberSpinnerWithIcon import org.polyfrost.oneconfig.internal.ui.hud.components.Radio import org.polyfrost.oneconfig.internal.ui.hud.components.RadioValue import org.polyfrost.oneconfig.internal.ui.hud.repairHudStaticSize @@ -151,16 +150,14 @@ fun Designer(hud: Hud?, query: String) { Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { HudSettingTarget(hud, "staticW") { NumberSpinner( - "Width", "px", staticW, { Snapshot.withMutableSnapshot { staticW = it; hud.staticW = it } }, - 20f, 2000f, 1f, width = 128.dp + 20f, 2000f, 1f, width = 132.dp, unit = "px", label = "Width" ) } HudSettingTarget(hud, "staticH") { NumberSpinner( - "Height", "px", staticH, { Snapshot.withMutableSnapshot { staticH = it; hud.staticH = it } }, - 8f, 2000f, 1f, width = 128.dp + 8f, 2000f, 1f, width = 132.dp, unit = "px", label = "Height" ) } } @@ -198,27 +195,27 @@ fun Designer(hud: Hud?, query: String) { Text("Edge padding", color = LocalTheme.current.textColor, fontSize = 14.sp) Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { HudSettingTarget(hud, "padLeft") { - NumberSpinnerWithIcon( - "pad-left", "px", - padLeft, { Snapshot.withMutableSnapshot { padLeft = it; hud.padLeft = it } }, 0f, 100f, 1f + NumberSpinner( + padLeft, { Snapshot.withMutableSnapshot { padLeft = it; hud.padLeft = it } }, + 0f, 100f, 1f, width = 88.dp, unit = "px", icon = "pad-left" ) } HudSettingTarget(hud, "padRight") { - NumberSpinnerWithIcon( - "pad-right", "px", - padRight, { Snapshot.withMutableSnapshot { padRight = it; hud.padRight = it } }, 0f, 100f, 1f + NumberSpinner( + padRight, { Snapshot.withMutableSnapshot { padRight = it; hud.padRight = it } }, + 0f, 100f, 1f, width = 88.dp, unit = "px", icon = "pad-right" ) } HudSettingTarget(hud, "padTop") { - NumberSpinnerWithIcon( - "pad-top", "px", - padTop, { Snapshot.withMutableSnapshot { padTop = it; hud.padTop = it } }, 0f, 100f, 1f + NumberSpinner( + padTop, { Snapshot.withMutableSnapshot { padTop = it; hud.padTop = it } }, + 0f, 100f, 1f, width = 88.dp, unit = "px", icon = "pad-top" ) } HudSettingTarget(hud, "padBottom") { - NumberSpinnerWithIcon( - "pad-bottom", "px", - padBottom, { Snapshot.withMutableSnapshot { padBottom = it; hud.padBottom = it } }, 0f, 100f, 1f + NumberSpinner( + padBottom, { Snapshot.withMutableSnapshot { padBottom = it; hud.padBottom = it } }, + 0f, 100f, 1f, width = 88.dp, unit = "px", icon = "pad-bottom" ) } } @@ -295,11 +292,13 @@ fun Designer(hud: Hud?, query: String) { ) } HudSettingTarget(hud, "textScale") { - NumberSpinner( - "Font size", "align", "px", - textScale * 14f, { Snapshot.withMutableSnapshot { val s = it / 14f; textScale = s; hud.textScale = s } }, - 6f, 64f, 1f, width = 112.dp - ) + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text("Font size", color = LocalTheme.current.textColor, fontSize = 14.sp) + NumberSpinner( + textScale * 14f, { Snapshot.withMutableSnapshot { val s = it / 14f; textScale = s; hud.textScale = s } }, + 6f, 64f, 1f, width = 112.dp, unit = "px", icon = "align" + ) + } } HudSettingTarget(hud, "textBold") { Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { diff --git a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Settings.kt b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Settings.kt index 3a078038d..3d77115e8 100644 --- a/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Settings.kt +++ b/modules/internal/src/main/kotlin/org/polyfrost/oneconfig/internal/ui/hud/screens/sections/Settings.kt @@ -32,10 +32,10 @@ import org.polyfrost.oneconfig.internal.ui.components.Icon import org.polyfrost.oneconfig.internal.ui.components.Text import org.polyfrost.oneconfig.internal.ui.components.onClick import org.polyfrost.oneconfig.internal.ui.components.rememberInteractionSource +import org.polyfrost.oneconfig.internal.ui.components.settings.NumberSpinner import org.polyfrost.oneconfig.internal.ui.components.settings.SwitchControl import org.polyfrost.oneconfig.internal.ui.hud.HudSettingTarget import org.polyfrost.oneconfig.internal.ui.hud.HudSettingsContent -import org.polyfrost.oneconfig.internal.ui.hud.components.NumberSpinner import org.polyfrost.oneconfig.internal.ui.hud.hudHasPositionDefaults import org.polyfrost.oneconfig.internal.ui.hud.repairHudStaticSize import org.polyfrost.oneconfig.internal.ui.hud.resetHudPosition @@ -98,14 +98,14 @@ fun Settings(hud: Hud?, query: String, onDeleted: () -> Unit) { Section("Scale", query) { HudSettingTarget(hud, "customScale") { NumberSpinner( - "Scale", - "x", customScale, { Snapshot.withMutableSnapshot { customScale = it; hud.customScale = it } }, 0.25f, 4f, 0.25f, - width = 176.dp + width = 176.dp, + unit = "x", + label = "Scale", ) } } @@ -126,14 +126,14 @@ fun Settings(hud: Hud?, query: String, onDeleted: () -> Unit) { } HudSettingTarget(hud, "customScale") { NumberSpinner( - "Custom Scale", - "x", customScale, { Snapshot.withMutableSnapshot { customScale = it; hud.customScale = it } }, 0.25f, 4f, 0.25f, - width = 176.dp + width = 176.dp, + unit = "x", + label = "Custom Scale", ) } } @@ -180,9 +180,8 @@ fun Settings(hud: Hud?, query: String, onDeleted: () -> Unit) { } HudSettingTarget(hud, "bgRadius") { NumberSpinner( - "Radius", "px", bgRadius, { Snapshot.withMutableSnapshot { bgRadius = it; hud.bgRadius = it } }, - 0f, 32f, 1f, width = 100.dp + 0f, 32f, 1f, width = 116.dp, unit = "px", label = "Radius" ) } }