Improve color picker state handling

This commit is contained in:
MM20 2023-04-18 18:23:50 +02:00
parent 426fa7e216
commit a57f206147
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
2 changed files with 96 additions and 38 deletions

View File

@ -19,12 +19,15 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.AbsoluteAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow
@ -39,21 +42,74 @@ import de.mm20.launcher2.ui.ktx.toHexString
import kotlin.math.atan2
import android.graphics.Color as AndroidColor
@Stable
class ColorPickerState(
initialColor: Color,
val onColorChanged: (Color) -> Unit,
) {
var hue by mutableStateOf(0f)
var sat by mutableStateOf(0f)
var value by mutableStateOf(0f)
val color by derivedStateOf {
Color.hsv(hue, sat, value)
}
internal fun setHue(hue: Float) {
this.hue = hue
onColorChanged(Color.hsv(hue, sat, value))
}
internal fun setSat(sat: Float) {
this.sat = sat
onColorChanged(Color.hsv(hue, sat, value))
}
internal fun setValue(value: Float) {
this.value = value
onColorChanged(Color.hsv(hue, sat, value))
}
internal fun setColor(color: Color) {
val hsv = FloatArray(3)
AndroidColor.RGBToHSV(
(color.red * 255f).toInt(),
(color.green * 255f).toInt(),
(color.blue * 255f).toInt(),
hsv
)
this.hue = hsv[0]
this.sat = hsv[1]
this.value = hsv[2]
onColorChanged(color)
}
init {
val hsv = FloatArray(3)
AndroidColor.RGBToHSV(
(initialColor.red * 255f).toInt(),
(initialColor.green * 255f).toInt(),
(initialColor.blue * 255f).toInt(),
hsv
)
hue = hsv[0]
sat = hsv[1]
value = hsv[2]
}
}
@Composable
fun rememberColorPickerState(initialColor: Color, onColorChanged: (Color) -> Unit): ColorPickerState {
return remember(initialColor, onColorChanged) {
ColorPickerState(initialColor, onColorChanged)
}
}
@Composable
fun ColorPicker(
value: Color,
onValueChanged: (Color) -> Unit,
state: ColorPickerState,
modifier: Modifier = Modifier,
) {
val (hue, sat, vl) = remember(value) {
val hsv = FloatArray(3)
val r = value.red * 255f
val g = value.green * 255f
val b = value.blue * 255f
AndroidColor.RGBToHSV(r.toInt(), g.toInt(), b.toInt(), hsv)
hsv
}
Column(modifier = modifier) {
BoxWithConstraints(
modifier = Modifier
@ -75,7 +131,7 @@ fun ColorPicker(
x.toDouble() - width.toPx() / 2,
)
val h = (Math.toDegrees(angle) + 360f) % 360f
onValueChanged(Color.hsv(h.toFloat(), sat, vl))
state.setHue(h.toFloat())
}
)
}
@ -87,7 +143,7 @@ fun ColorPicker(
x.toDouble() - width.toPx() / 2,
)
val h = (Math.toDegrees(angle) + 360f) % 360f
onValueChanged(Color.hsv(h.toFloat(), sat, vl))
state.setHue(h.toFloat())
}
}
@ -96,19 +152,19 @@ fun ColorPicker(
drawCircle(
brush = Brush.sweepGradient(
colors = listOf(
Color.Red,
Color.Yellow,
Color.Green,
Color.Cyan,
Color.Blue,
Color.Magenta,
Color.Red
Color.hsv(0f, 1f, 1f),
Color.hsv(60f, 1f, 1f),
Color.hsv(120f, 1f, 1f),
Color.hsv(180f, 1f, 1f),
Color.hsv(240f, 1f, 1f),
Color.hsv(300f, 1f, 1f),
Color.hsv(360f, 1f, 1f),
)
),
style = Stroke(20.dp.toPx())
)
drawCircle(
color = value,
color = state.color,
style = Fill,
center = center,
radius = size.minDimension / 2 - 18.dp.toPx()
@ -117,23 +173,23 @@ fun ColorPicker(
Box(
modifier = Modifier
.fillMaxSize()
.rotate(hue),
.rotate(state.hue),
) {
Box(
modifier = Modifier
.size(16.dp)
.shadow(1.dp, CircleShape)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
.background(Color.White)
.align(AbsoluteAlignment.CenterRight)
)
}
}
Slider(
modifier = Modifier.padding(top = 16.dp),
value = sat,
value = state.sat,
onValueChange = {
onValueChanged(Color.hsv(hue, it, vl))
state.setSat(it)
},
track = {
Canvas(
@ -144,8 +200,8 @@ fun ColorPicker(
drawRoundRect(
brush = Brush.horizontalGradient(
colors = listOf(
Color.hsv(hue, 0f, 1f),
Color.hsv(hue, 1f, 1f)
Color.hsv(state.hue, 0f, 1f),
Color.hsv(state.hue, 1f, 1f)
)
),
style = Fill,
@ -160,15 +216,15 @@ fun ColorPicker(
.size(16.dp)
.shadow(1.dp, CircleShape)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
.background(Color.White)
)
}
)
Slider(
modifier = Modifier,
value = vl,
value = state.value,
onValueChange = {
onValueChanged(Color.hsv(hue, sat, it))
state.setValue(it)
},
track = {
Canvas(
@ -179,8 +235,8 @@ fun ColorPicker(
drawRoundRect(
brush = Brush.horizontalGradient(
colors = listOf(
Color.hsv(hue, sat, 0f),
Color.hsv(hue, sat, 1f)
Color.hsv(state.hue, state.sat, 0f),
Color.hsv(state.hue, state.sat, 1f)
)
),
style = Fill,
@ -195,14 +251,14 @@ fun ColorPicker(
.size(16.dp)
.shadow(1.dp, CircleShape)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.surface)
.background(Color.White)
)
}
)
var hexValue by remember(value) {
var hexValue by remember(state.color) {
mutableStateOf(
value.toHexString().substring(1)
state.color.toHexString().substring(1)
)
}
@ -215,7 +271,7 @@ fun ColorPicker(
if (it.length == 6) {
val hex = it.toIntOrNull(16) ?: return@OutlinedTextField
val color = Color(hex).copy(alpha = 1f)
onValueChanged(color)
state.setColor(color)
}
}
},

View File

@ -9,6 +9,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.component.colorpicker.ColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberColorPickerState
import de.mm20.launcher2.ui.ktx.toHexString
@Composable
@ -38,7 +39,7 @@ fun ColorPreference(
}
)
if (showDialog) {
var color by remember(value) { mutableStateOf(value) }
var color by remember(value) { mutableStateOf(value ?: Color.Black) }
AlertDialog(
onDismissRequest = { showDialog = false },
title = {
@ -51,9 +52,10 @@ fun ColorPreference(
Column(
modifier = Modifier.fillMaxWidth()
) {
ColorPicker(value = color ?: Color.Black, onValueChanged = {
val state = rememberColorPickerState(value ?: Color.Black) {
color = it
})
}
ColorPicker(state = state)
}
},
confirmButton = {