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.Slider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.AbsoluteAlignment import androidx.compose.ui.AbsoluteAlignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
@ -39,21 +42,74 @@ import de.mm20.launcher2.ui.ktx.toHexString
import kotlin.math.atan2 import kotlin.math.atan2
import android.graphics.Color as AndroidColor 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 @Composable
fun ColorPicker( fun ColorPicker(
value: Color, state: ColorPickerState,
onValueChanged: (Color) -> Unit,
modifier: Modifier = Modifier, 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) { Column(modifier = modifier) {
BoxWithConstraints( BoxWithConstraints(
modifier = Modifier modifier = Modifier
@ -75,7 +131,7 @@ fun ColorPicker(
x.toDouble() - width.toPx() / 2, x.toDouble() - width.toPx() / 2,
) )
val h = (Math.toDegrees(angle) + 360f) % 360f 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, x.toDouble() - width.toPx() / 2,
) )
val h = (Math.toDegrees(angle) + 360f) % 360f 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( drawCircle(
brush = Brush.sweepGradient( brush = Brush.sweepGradient(
colors = listOf( colors = listOf(
Color.Red, Color.hsv(0f, 1f, 1f),
Color.Yellow, Color.hsv(60f, 1f, 1f),
Color.Green, Color.hsv(120f, 1f, 1f),
Color.Cyan, Color.hsv(180f, 1f, 1f),
Color.Blue, Color.hsv(240f, 1f, 1f),
Color.Magenta, Color.hsv(300f, 1f, 1f),
Color.Red Color.hsv(360f, 1f, 1f),
) )
), ),
style = Stroke(20.dp.toPx()) style = Stroke(20.dp.toPx())
) )
drawCircle( drawCircle(
color = value, color = state.color,
style = Fill, style = Fill,
center = center, center = center,
radius = size.minDimension / 2 - 18.dp.toPx() radius = size.minDimension / 2 - 18.dp.toPx()
@ -117,23 +173,23 @@ fun ColorPicker(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.rotate(hue), .rotate(state.hue),
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(16.dp) .size(16.dp)
.shadow(1.dp, CircleShape) .shadow(1.dp, CircleShape)
.clip(CircleShape) .clip(CircleShape)
.background(MaterialTheme.colorScheme.surface) .background(Color.White)
.align(AbsoluteAlignment.CenterRight) .align(AbsoluteAlignment.CenterRight)
) )
} }
} }
Slider( Slider(
modifier = Modifier.padding(top = 16.dp), modifier = Modifier.padding(top = 16.dp),
value = sat, value = state.sat,
onValueChange = { onValueChange = {
onValueChanged(Color.hsv(hue, it, vl)) state.setSat(it)
}, },
track = { track = {
Canvas( Canvas(
@ -144,8 +200,8 @@ fun ColorPicker(
drawRoundRect( drawRoundRect(
brush = Brush.horizontalGradient( brush = Brush.horizontalGradient(
colors = listOf( colors = listOf(
Color.hsv(hue, 0f, 1f), Color.hsv(state.hue, 0f, 1f),
Color.hsv(hue, 1f, 1f) Color.hsv(state.hue, 1f, 1f)
) )
), ),
style = Fill, style = Fill,
@ -160,15 +216,15 @@ fun ColorPicker(
.size(16.dp) .size(16.dp)
.shadow(1.dp, CircleShape) .shadow(1.dp, CircleShape)
.clip(CircleShape) .clip(CircleShape)
.background(MaterialTheme.colorScheme.surface) .background(Color.White)
) )
} }
) )
Slider( Slider(
modifier = Modifier, modifier = Modifier,
value = vl, value = state.value,
onValueChange = { onValueChange = {
onValueChanged(Color.hsv(hue, sat, it)) state.setValue(it)
}, },
track = { track = {
Canvas( Canvas(
@ -179,8 +235,8 @@ fun ColorPicker(
drawRoundRect( drawRoundRect(
brush = Brush.horizontalGradient( brush = Brush.horizontalGradient(
colors = listOf( colors = listOf(
Color.hsv(hue, sat, 0f), Color.hsv(state.hue, state.sat, 0f),
Color.hsv(hue, sat, 1f) Color.hsv(state.hue, state.sat, 1f)
) )
), ),
style = Fill, style = Fill,
@ -195,14 +251,14 @@ fun ColorPicker(
.size(16.dp) .size(16.dp)
.shadow(1.dp, CircleShape) .shadow(1.dp, CircleShape)
.clip(CircleShape) .clip(CircleShape)
.background(MaterialTheme.colorScheme.surface) .background(Color.White)
) )
} }
) )
var hexValue by remember(value) { var hexValue by remember(state.color) {
mutableStateOf( mutableStateOf(
value.toHexString().substring(1) state.color.toHexString().substring(1)
) )
} }
@ -215,7 +271,7 @@ fun ColorPicker(
if (it.length == 6) { if (it.length == 6) {
val hex = it.toIntOrNull(16) ?: return@OutlinedTextField val hex = it.toIntOrNull(16) ?: return@OutlinedTextField
val color = Color(hex).copy(alpha = 1f) 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.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.component.colorpicker.ColorPicker import de.mm20.launcher2.ui.component.colorpicker.ColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberColorPickerState
import de.mm20.launcher2.ui.ktx.toHexString import de.mm20.launcher2.ui.ktx.toHexString
@Composable @Composable
@ -38,7 +39,7 @@ fun ColorPreference(
} }
) )
if (showDialog) { if (showDialog) {
var color by remember(value) { mutableStateOf(value) } var color by remember(value) { mutableStateOf(value ?: Color.Black) }
AlertDialog( AlertDialog(
onDismissRequest = { showDialog = false }, onDismissRequest = { showDialog = false },
title = { title = {
@ -51,9 +52,10 @@ fun ColorPreference(
Column( Column(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
ColorPicker(value = color ?: Color.Black, onValueChanged = { val state = rememberColorPickerState(value ?: Color.Black) {
color = it color = it
}) }
ColorPicker(state = state)
} }
}, },
confirmButton = { confirmButton = {