Improve color picker state handling
This commit is contained in:
parent
426fa7e216
commit
a57f206147
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user