len
11/06/2020, 9:17 PMlen
11/06/2020, 9:18 PMlen
11/06/2020, 9:18 PM@OptIn(ExperimentalLayout::class)
@Composable
fun ColorPalette(
initialColor: Color = Color.White,
onColorChanged: (Color) -> Unit = {}
) {
var selectedColor by remember { mutableStateOf(initialColor) }
var selectedColorHex by remember { mutableStateOf(selectedColor.toHexString()) }
var hue by remember { mutableStateOf(initialColor.toHsv()[0]) }
var hueCursor by remember { mutableStateOf(0f) }
var matrixSize by remember { mutableStateOf(IntSize(0, 0)) }
var matrixCursor by remember { mutableStateOf(Offset(0f, 0f)) }
val saturationGradient = remember(hue, matrixSize) {
LinearGradient(
colors = listOf(Color(0xFFFFFFFF), hueToColor(hue)),
startX = 0f, startY = 0f, endX = matrixSize.width.toFloat(), endY = 0f
)
}
val valueGradient = remember(matrixSize) {
LinearGradient(
colors = listOf(Color(0xFFFFFFFF), Color(0xFF000000)),
startX = 0f, startY = 0f, endX = 0f, endY = matrixSize.height.toFloat())
}
fun setNewColor(color: Color, invalidate: Boolean = false) {
selectedColor = color
selectedColorHex = color.toHexString()
if (invalidate) {
val hsv = color.toHsv()
hue = hsv[0]
matrixCursor = satValToCoordinates(hsv[1], hsv[2], matrixSize)
hueCursor = hueToCoordinate(hsv[0], matrixSize)
}
onColorChanged(color)
}
Column {
Row(Modifier.preferredHeight(IntrinsicSize.Max)) {
Box(Modifier
.padding(8.dp)
.aspectRatio(1f)
.weight(1f)
.onSizeChanged {
matrixSize = it
val hsv = selectedColor.toHsv()
matrixCursor = satValToCoordinates(hsv[1], hsv[2], it)
hueCursor = hueToCoordinate(hue, it)
}
.drawWithContent {
drawRect(brush = valueGradient)
drawRect(brush = saturationGradient, blendMode = BlendMode.Multiply)
drawCircle(Color.White, radius = 15f, center = matrixCursor, style = Stroke(10f))
}
.pointerInteropFilter { ev ->
if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_MOVE) {
val safeX = ev.x.coerceIn(0f, matrixSize.width.toFloat())
val safeY = ev.y.coerceIn(0f, matrixSize.height.toFloat())
matrixCursor = Offset(safeX, safeY)
val newColor = matrixCoordinatesToColor(hue, safeX, safeY, matrixSize)
setNewColor(newColor)
}
true
})
Box(Modifier.fillMaxHeight().width(48.dp).padding(8.dp).drawWithCache {
var h = 360f
val colors = MutableList(size.height.toInt()) {
hsvToColor(h, 1f, 1f).also {
h -= 360f / size.height
}
}
onDraw {
colors.fastForEachIndexed { i, color ->
val pos = i.toFloat()
drawLine(color, Offset(0f, pos), Offset(size.width, pos))
}
drawRect(Color.White, topLeft = Offset(0f, hueCursor), size = Size(size.width, 10f),
style = Stroke(4f))
}
}.pointerInteropFilter { ev ->
if (ev.action == MotionEvent.ACTION_DOWN || ev.action == MotionEvent.ACTION_MOVE) {
val safeY = ev.y.coerceIn(0f, matrixSize.height.toFloat())
hueCursor = safeY
hue = hueCoordinatesToHue(safeY, matrixSize)
val newColor = matrixCoordinatesToColor(hue, matrixCursor.x, matrixCursor.y, matrixSize)
setNewColor(newColor)
}
true
})
}
Row(Modifier.padding(32.dp)) {
Box(Modifier.size(100.dp, 56.dp).background(selectedColor))
TextField(
value = selectedColorHex,
modifier = Modifier.padding(start = 16.dp).width(124.dp),
onValueChange = {
val newColor = hexStringToColor(it)
if (newColor != null) {
setNewColor(newColor, invalidate = true)
} else {
selectedColorHex = it
}
}
)
}
}
}
// Coordinates <-> Color
private fun matrixCoordinatesToColor(hue: Float, x: Float, y: Float, size: IntSize): Color {
val saturation = 1f / size.width * x
val value = 1f - (1f / size.height * y)
return hsvToColor(hue, saturation, value)
}
private fun hueCoordinatesToHue(y: Float, size: IntSize): Float {
val hue = 360f - y * 360f / size.height
return hsvToColor(hue, 1f, 1f).toHsv()[0]
}
private fun satValToCoordinates(saturation: Float, value: Float, size: IntSize): Offset {
return Offset(saturation * size.width, ((1f - value) * size.height))
}
private fun hueToCoordinate(hue: Float, size: IntSize): Float {
return size.height - (hue * size.height / 360f)
}
// Color space conversions
private fun hsvToColor(hue: Float, saturation: Float, value: Float): Color {
val f = floatArrayOf(hue, saturation, value)
return Color(android.graphics.Color.HSVToColor(f))
}
private fun Color.toHsv(): FloatArray {
val result = floatArrayOf(0f, 0f, 0f)
android.graphics.Color.colorToHSV(toArgb(), result)
return result
}
private fun hueToColor(hue: Float): Color {
return hsvToColor(hue, 1f, 1f)
}
private fun Color.toHexString(): String {
return String.format("#%06X", (0xFFFFFF and toArgb()))
}
private fun hexStringToColor(hex: String): Color? {
return try {
Color(android.graphics.Color.parseColor(hex))
} catch (e: Exception) {
null
}
}
Alejandro Rios
11/06/2020, 9:20 PMJavier
11/06/2020, 11:02 PM