From 4daa0be19ae520d8588b348e8bc91eaf9920a06d Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 15 Jul 2023 19:41:31 +0700 Subject: [PATCH] Color picker panel --- .../mc/otm/datagen/lang/English.kt | 18 + .../mc/otm/datagen/lang/Russian.kt | 18 + .../otm/client/screen/panels/ColorPicker.kt | 746 ++++++++++++++++++ .../textures/gui/gradient_h.png | Bin 0 -> 627 bytes .../textures/gui/gradient_v.png | Bin 0 -> 682 bytes .../textures/gui/hsv.png | Bin 0 -> 1881 bytes .../textures/gui/widgets/misc.png | Bin 1378 -> 1405 bytes 7 files changed, 782 insertions(+) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt create mode 100644 src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_h.png create mode 100644 src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_v.png create mode 100644 src/main/resources/assets/overdrive_that_matters/textures/gui/hsv.png diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt index 08a7414d8..5c97f4607 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/English.kt @@ -139,6 +139,8 @@ private fun misc(provider: MatteryLanguageProvider) { gui("exopack.go_back", "Open vanilla inventory") gui("exopack.go_in", "Open Exopack inventory") gui("exopack.toggle_visibility", "Toggle Exopack visibility") + gui("exopack.change_color", "Customize Exopack color") + gui("exopack.change_color2", "Press Middle Mouse Button to remove color") gui("exopack.probe1", "This little device feels unnatural to touch, it is almost certainly resilient to any possible attempt to break it open.") gui("exopack.probe2", "There is fingerprint reader built into one of sides which gently glow when touched.") @@ -664,6 +666,22 @@ private fun gui(provider: MatteryLanguageProvider) { with(provider.english) { gui("quicksearch", "Quick search...") + gui("color_picker", "Color Picker") + + gui("color.short.red", "R") + gui("color.short.green", "G") + gui("color.short.blue", "B") + gui("color.short.hue", "H") + gui("color.short.saturation", "S") + gui("color.short.value", "V") + + gui("color.full.red", "Red") + gui("color.full.green", "Green") + gui("color.full.blue", "Blue") + gui("color.full.hue", "Hue") + gui("color.full.saturation", "Saturation") + gui("color.full.value", "Value") + gui("item_monitor.refill_source.desc", "Controls from where to take items for slot auto refill") gui("item_monitor.refill_source.system", "System only. Crafting grid will be auto refilled only from storage system. This is the behavior you see in AE2 and Refined Storage") gui("item_monitor.refill_source.inventory", "Inventory only. Crafting grid will be auto refilled only from player's inventory") diff --git a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt index faecb219c..34b334622 100644 --- a/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt +++ b/src/data/kotlin/ru/dbotthepony/mc/otm/datagen/lang/Russian.kt @@ -147,6 +147,8 @@ private fun misc(provider: MatteryLanguageProvider) { gui("exopack.go_back", "Открыть обычный инвентарь") gui("exopack.go_in", "Открыть инвентарь экзопака") gui("exopack.toggle_visibility", "Переключить отображение Экзопака") + gui("exopack.change_color", "Изменить окраску Экзопака") + gui("exopack.change_color2", "Нажмите среднюю кнопку мыши для сброса окраски") gui("exopack.probe1", "Данное маленькое устройство необычно на ощупь, а так же неприступно для любых попыток вскрыть.") gui("exopack.probe2", "На одной из сторон данного устройства находится сканер отпечатка, который тускло загорается при касании.") @@ -668,6 +670,22 @@ private fun gui(provider: MatteryLanguageProvider) { with(provider.russian) { gui("quicksearch", "Быстрый поиск...") + gui("color_picker", "Выбор цвета") + + gui("color.short.red", "К") + gui("color.short.green", "З") + gui("color.short.blue", "С") + gui("color.short.hue", "Ц") + gui("color.short.saturation", "Н") + gui("color.short.value", "Я") + + gui("color.full.red", "Красный") + gui("color.full.green", "Зелёный") + gui("color.full.blue", "Синий") + gui("color.full.hue", "Цвет") + gui("color.full.saturation", "Насыщение") + gui("color.full.value", "Яркость") + gui("item_monitor.refill_source.desc", "Контролирует источник предметов для заполнения сетки создания") gui("item_monitor.refill_source.system", "Только система. Сетка создания будет заполняться только из системы предметов. Данный параметр соответствует поведению AE2 и Refined Storage") gui("item_monitor.refill_source.inventory", "Только инвентарь. Сетка создания будет заполняться только из инвентаря игрока") diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt new file mode 100644 index 000000000..af60b2190 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ColorPicker.kt @@ -0,0 +1,746 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import com.mojang.blaze3d.platform.InputConstants +import com.mojang.datafixers.util.Either +import it.unimi.dsi.fastutil.floats.FloatConsumer +import net.minecraft.client.gui.GuiGraphics +import net.minecraft.client.gui.screens.Screen +import net.minecraft.network.chat.Component +import net.minecraft.resources.ResourceLocation +import ru.dbotthepony.mc.otm.OverdriveThatMatters +import ru.dbotthepony.mc.otm.client.playGuiClickSound +import ru.dbotthepony.mc.otm.client.render.MatterySprite +import ru.dbotthepony.mc.otm.client.render.RenderGravity +import ru.dbotthepony.mc.otm.client.render.UVWindingOrder +import ru.dbotthepony.mc.otm.client.render.WidgetLocation +import ru.dbotthepony.mc.otm.client.render.renderRect +import ru.dbotthepony.mc.otm.client.screen.MatteryScreen +import ru.dbotthepony.mc.otm.client.screen.panels.button.AbstractButtonPanel +import ru.dbotthepony.mc.otm.client.screen.panels.input.TextInputPanel +import ru.dbotthepony.mc.otm.core.TextComponent +import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.core.math.HSVColor +import ru.dbotthepony.mc.otm.core.math.RGBAColor +import java.util.function.Consumer +import kotlin.math.roundToInt + +open class ColorBoxPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float, + y: Float, + width: Float = 64f, + height: Float = 64f, + protected val callback: Consumer? = null, +) : EditablePanel(screen, parent, x, y, width, height) { + var backgroundColor = RGBAColor.RED + private set + var markerPos = backgroundColor.toHSV() + protected set + + fun setColor(color: Either) { + color.map( + { markerPos = it.toHSV(); if (it.canRepresentHue()) backgroundColor = HSVColor(it.toHSV().hue, 1f, 1f).toRGBA() }, + { markerPos = it; backgroundColor = HSVColor(it.hue, 1f, 1f).toRGBA() }) + } + + var isPressed = false + private set + + override fun shouldRenderTooltips(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean { + return super.shouldRenderTooltips(graphics, mouseX, mouseY, partialTick) || isPressed + } + + protected open fun onPickedColor(color: HSVColor) { + callback?.accept(color) + } + + protected fun pickColor(mouseX: Double, mouseY: Double) { + val (x, y) = screenToLocal(mouseX, mouseY) + + val saturation = 1f - x.coerceIn(0f, width) / width + val value = 1f - y.coerceIn(0f, height) / height + + markerPos = HSVColor(backgroundColor.toHSV().hue, saturation, value) + onPickedColor(markerPos) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (!isPressed) { + isPressed = true + grabMouseInput = true + + pickColor(x, y) + playGuiClickSound() + } + + return true + } + + return super.mouseClickedInner(x, y, button) + } + + override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + if (isPressed && button == InputConstants.MOUSE_BUTTON_LEFT) { + pickColor(x, y) + return true + } + + return super.mouseDraggedInner(x, y, button, xDelta, yDelta) + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (isPressed) { + isPressed = false + grabMouseInput = false + } + + return true + } + + return super.mouseReleasedInner(x, y, button) + } + + override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + graphics.renderRect(0f, 0f, width, height, color = RGBAColor.WHITE) + GRADIENT_LEFT.render(graphics, 0f, 0f, width, height, color = backgroundColor) + GRADIENT_DOWN.render(graphics, 0f, 0f, width, height, color = RGBAColor.BLACK) + + val x = (1f - markerPos.saturation) * width + val y = (1f - markerPos.value) * height + + LINE_VERTICAL.render(graphics, x = x - 1f, height = height) + LINE_HORIZONTAL.render(graphics, y = y - 1f, width = width) + LINE_CROSS.render(graphics, x = x - 1f, y = y - 1f) + } + + companion object { + val GRADIENT_UP = MatterySprite(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/gradient_v.png"), 0f, 0f, 32f, 256f, 32f, 256f) + val GRADIENT_DOWN = GRADIENT_UP.copy(winding = UVWindingOrder.FLIP) + val GRADIENT_RIGHT = MatterySprite(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/gradient_h.png"), 0f, 0f, 256f, 32f, 256f, 32f) + val GRADIENT_LEFT = GRADIENT_RIGHT.copy(winding = UVWindingOrder.FLOP) + + val LINE_VERTICAL = WidgetLocation.MISC.sprite(36f, 0f, 3f, 3f) + val LINE_HORIZONTAL = WidgetLocation.MISC.sprite(36f, 3f, 3f, 3f) + val LINE_CROSS = WidgetLocation.MISC.sprite(36f, 6f, 3f, 3f) + } +} + +abstract class AbstractColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val callback: Consumer? = null, +) : EditablePanel(screen, parent, x, y, width, height) { + abstract val leftColor: RGBAColor + abstract val rightColor: RGBAColor + abstract val wangPosition: Float + abstract fun setColor(color: Either) + protected abstract fun onWangInput(newPosition: Float) + + init { + scissor = true + } + + var isPressed = false + private set + + protected fun updateColor(mouseX: Double) { + onWangInput((screenToLocal(mouseX, 0.0).x / width).coerceIn(0f, 1f)) + } + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (!isPressed) { + isPressed = true + grabMouseInput = true + + updateColor(x) + playGuiClickSound() + } + + return true + } + + return super.mouseClickedInner(x, y, button) + } + + override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + if (isPressed && button == InputConstants.MOUSE_BUTTON_LEFT) { + updateColor(x) + return true + } + + return super.mouseDraggedInner(x, y, button, xDelta, yDelta) + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (isPressed) { + isPressed = false + grabMouseInput = false + } + + return true + } + + return super.mouseReleasedInner(x, y, button) + } + + protected fun renderGradients(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + ColorBoxPanel.GRADIENT_RIGHT.render(graphics, 0f, 0f, width, height, color = rightColor) + ColorBoxPanel.GRADIENT_LEFT.render(graphics, 0f, 0f, width, height, color = leftColor) + } + + protected fun renderWang(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + if (wangPosition in 0f .. 1f) { + ColorBoxPanel.LINE_VERTICAL.render(graphics, x = wangPosition * width - 1f, height = height) + } + } + + override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + renderGradients(graphics, mouseX, mouseY, partialTick) + renderWang(graphics, mouseX, mouseY, partialTick) + } +} + +open class RedColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + callback: Consumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height, callback) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.RED + protected set + override var wangPosition: Float = 0f + protected set + + override fun onWangInput(newPosition: Float) { + val color = leftColor.copy(red = newPosition) + wangPosition = newPosition + callback?.accept(color) + } + + override fun setColor(color: Either) { + @Suppress("name_shadowing") + val color = color.map({ it }, { it.toRGBA() }) + leftColor = color.copy(red = 0f, alpha = 1f) + rightColor = color.copy(red = 1f, alpha = 1f) + wangPosition = color.red + } +} + +open class GreenColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + callback: Consumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height, callback) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.GREEN + protected set + override var wangPosition: Float = 0f + protected set + + override fun onWangInput(newPosition: Float) { + val color = leftColor.copy(green = newPosition) + wangPosition = newPosition + callback?.accept(color) + } + + override fun setColor(color: Either) { + @Suppress("name_shadowing") + val color = color.map({ it }, { it.toRGBA() }) + leftColor = color.copy(green = 0f, alpha = 1f) + rightColor = color.copy(green = 1f, alpha = 1f) + wangPosition = color.green + } +} + +open class BlueColorWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + callback: Consumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height, callback) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.BLUE + protected set + override var wangPosition: Float = 0f + protected set + + override fun onWangInput(newPosition: Float) { + val color = leftColor.copy(blue = newPosition) + wangPosition = newPosition + callback?.accept(color) + } + + override fun setColor(color: Either) { + @Suppress("name_shadowing") + val color = color.map({ it }, { it.toRGBA() }) + leftColor = color.copy(blue = 0f, alpha = 1f) + rightColor = color.copy(blue = 1f, alpha = 1f) + wangPosition = color.blue + } +} + +open class HueWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val hueCallback: FloatConsumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height) { + override val leftColor: RGBAColor get() = RGBAColor.WHITE + override val rightColor: RGBAColor get() = RGBAColor.WHITE + override var wangPosition: Float = 1f + protected set + + override fun setColor(color: Either) { + color.map( + { + if (it.canRepresentHue()) { + wangPosition = it.toHSV().hue / 360f + } + }, + { + wangPosition = it.hue / 360f + } + ) + } + + override fun onWangInput(newPosition: Float) { + wangPosition = newPosition + hueCallback?.accept(newPosition * 360f) + } + + override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + HSV_BAR.render(graphics, 0f, 0f, width, height) + renderWang(graphics, mouseX, mouseY, partialTick) + } + + companion object { + val HSV_BAR = MatterySprite(ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/hsv.png"), 0f, 0f, 256f, 16f, 256f, 16f) + } +} + +open class SaturationWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val saturationCallback: FloatConsumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height) { + override var leftColor: RGBAColor = RGBAColor.WHITE + protected set + override var rightColor: RGBAColor = RGBAColor.WHITE + protected set + override var wangPosition: Float = 1f + protected set + + override fun onWangInput(newPosition: Float) { + wangPosition = newPosition + saturationCallback?.accept(newPosition) + } + + override fun setColor(color: Either) { + color.map( + { + val hsv = it.toHSV() + + if (it.canRepresentHue()) { + leftColor = hsv.copy(saturation = 0f).toRGBA() + rightColor = hsv.copy(saturation = 1f).toRGBA() + } else { + leftColor = hsv.copy(hue = leftColor.toHSV().hue, saturation = 0f).toRGBA() + rightColor = hsv.copy(hue = rightColor.toHSV().hue, saturation = 1f).toRGBA() + } + + wangPosition = hsv.saturation + }, + { hsv -> + leftColor = hsv.copy(saturation = 0f).toRGBA() + rightColor = hsv.copy(saturation = 1f).toRGBA() + wangPosition = hsv.saturation + } + ) + } +} + +open class ValueWangPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 40f, + height: Float = 10f, + protected val valueCallback: FloatConsumer? = null, +) : AbstractColorWangPanel(screen, parent, x, y, width, height) { + override var leftColor: RGBAColor = RGBAColor.BLACK + protected set + override var rightColor: RGBAColor = RGBAColor.WHITE + protected set + override var wangPosition: Float = 1f + protected set + + override fun onWangInput(newPosition: Float) { + wangPosition = newPosition + valueCallback?.accept(newPosition) + } + + override fun setColor(color: Either) { + color.map( + { + val hsv = it.toHSV() + + if (it.canRepresentHue()) { + leftColor = hsv.copy(value = 0f).toRGBA() + rightColor = hsv.copy(value = 1f).toRGBA() + } else { + leftColor = hsv.copy(hue = leftColor.toHSV().hue, value = 0f).toRGBA() + rightColor = hsv.copy(hue = rightColor.toHSV().hue, value = 1f).toRGBA() + } + + wangPosition = hsv.value + }, + { hsv -> + leftColor = hsv.copy(value = 0f).toRGBA() + rightColor = hsv.copy(value = 1f).toRGBA() + wangPosition = hsv.value + } + ) + } +} + +open class ColorPalettePanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 64f, + height: Float = 64f, + protected val callback: Consumer? = null +) : EditablePanel(screen, parent, x, y, width, height) { + open fun onColorChoose(color: RGBAColor) { + callback?.accept(color) + } + + inner class Button(val color: RGBAColor) : AbstractButtonPanel(screen, this@ColorPalettePanel, 0f, 0f, 8f, 8f) { + init { + tooltips.add(TextComponent("${color.redInt}, ${color.greenInt}, ${color.blueInt}")) + tooltips.add(TextComponent(color.toHexStringRGB())) + } + + override fun onClick(mouseButton: Int) { + onColorChoose(color) + } + + override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) { + graphics.renderRect(0f, 0f, width, height, color = RGBAColor.BLACK) + graphics.renderRect(1f, 1f, width - 2f, height - 2f, color = color) + } + + override fun compareTo(other: EditablePanel<*>): Int { + return super.compareTo(other).let { + if (it != 0 || other !is Button) + it + else + color.compareTo(other.color) + } + } + } + + override fun performLayout() { + super.performLayout() + + var x = 0f + var y = 0f + + for (children in visibleChildren) { + if (children is Button) { + if (x != 0f && x + children.width > width) { + y += 9f + x = 0f + } + + children.x = x + children.y = y + x += 9f + } + } + } +} + +open class ColorPickerPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 164f, + height: Float = 118f, + val callback: Consumer? = null +) : EditablePanel(screen, parent, x, y, width, height) { + open fun onColorChanged(color: RGBAColor) { + callback?.accept(color) + } + + var currentColor: Either = Either.left(RGBAColor.WHITE) + protected set + + fun setColor(color: Either) { + currentColor = color + hexInput.text = color.map({ it }, { it.toRGBA() }).toHexStringRGB() + updateWangs(color) + } + + protected fun updateWangs(color: Either) { + box.setColor(color) + + for (wang in wangs) + wang.update(color) + } + + protected open fun onPaletteChoose(color: RGBAColor) { + setColor(Either.left(color)) + onColorChanged(color) + } + + protected open fun onColorBoxChoose(color: HSVColor) { + setColor(Either.right(color)) + onColorChanged(color.toRGBA()) + } + + protected open fun onWangChoose(color: RGBAColor) { + setColor(Either.left(color)) + onColorChanged(color) + } + + protected open fun onHueChoose(hue: Float) { + val current = currentColor.map({ it.toHSV() }, { it }) + val new = current.copy(hue = hue) + setColor(Either.right(new)) + onColorChanged(new.toRGBA()) + } + + protected open fun onSaturationChoose(saturation: Float) { + val current = currentColor.map({ it.toHSV() }, { it }) + val new = current.copy(saturation = saturation) + setColor(Either.right(new)) + onColorChanged(new.toRGBA()) + } + + protected open fun onValueChoose(value: Float) { + val current = currentColor.map({ it.toHSV() }, { it }) + val new = current.copy(value = value) + setColor(Either.right(new)) + onColorChanged(new.toRGBA()) + } + + val topStrip = EditablePanel(screen, this, 0f, 0f, width = width, height = 70f) + val middleStrip = EditablePanel(screen, this) + val palette = ColorPalettePanel(screen, this, callback = { onPaletteChoose(it) }) + + val hexInput = object : TextInputPanel(screen, middleStrip, width = 50f) { + init { + dock = Dock.RIGHT + } + + override fun onFocusChanged() { + if (!isFocusedThis) { + val newColor = RGBAColor.fromHexStringRGB(text) + + if (newColor == null) { + text = currentColor.map({ it }, { it.toRGBA() }).toHexStringRGB() + } else { + setColor(Either.left(newColor)) + onColorChanged(newColor) + } + } + } + + override fun acceptsCharacter(codepoint: Char, mods: Int): Boolean { + return RGBAColor.isHexCharacter(codepoint) + } + } + + init { + topStrip.dock = Dock.TOP + middleStrip.dock = Dock.TOP + palette.dock = Dock.FILL + palette.dockTop = 2f + middleStrip.dockTop = 2f + + for (color in paletteColors) { + palette.Button(color) + } + } + + val box = ColorBoxPanel(screen, topStrip, 0f, 0f, width = 70f, height = 70f, callback = { onColorBoxChoose(it) }) + val wangCanvas = EditablePanel(screen, topStrip, width = 80f) + + inner class WangLine(label: String, val wang: AbstractColorWangPanel, val text: (color: Either) -> Component?) { + val canvas = EditablePanel(screen, wangCanvas, height = 10f) + val label = Label(screen, canvas, width = 10f, text = TranslatableComponent("otm.gui.color.short.$label")) + val textLabel = Label(screen, canvas, width = 25f) + + init { + this.wang.parent = canvas + this.wang.width = 45f + + this.label.childrenOrder = 0 + this.wang.childrenOrder = 1 + this.textLabel.childrenOrder = 2 + + this.canvas.dock = Dock.TOP + this.canvas.dockTop = 2f + + this.label.dock = Dock.LEFT + this.label.dockRight = 2f + + this.wang.dock = Dock.LEFT + this.wang.dockRight = 2f + + this.textLabel.dock = Dock.LEFT + this.textLabel.align = RenderGravity.CENTER_LEFT + + this.label.tooltips.add(TranslatableComponent("otm.gui.color.full.$label")) + this.label.align = RenderGravity.CENTER_RIGHT + } + + fun update(color: Either) { + wang.setColor(color) + + text.invoke(color)?.let { + textLabel.text = it + } + } + } + + val red = WangLine("red", RedColorWangPanel(screen, wangCanvas, callback = { onWangChoose(it) })) { TextComponent((it.map({ it }, { it.toRGBA() }).red * 255f).roundToInt().toString()) } + val green = WangLine("green", GreenColorWangPanel(screen, wangCanvas, callback = { onWangChoose(it) })) { TextComponent((it.map({ it }, { it.toRGBA() }).green * 255f).roundToInt().toString()) } + val blue = WangLine("blue", BlueColorWangPanel(screen, wangCanvas, callback = { onWangChoose(it) })) { TextComponent((it.map({ it }, { it.toRGBA() }).blue * 255f).roundToInt().toString()) } + + val hue = WangLine("hue", HueWangPanel(screen, wangCanvas, hueCallback = { onHueChoose(it) })) { it.map({ if (it.canRepresentHue()) it.toHSV() else null }, { it })?.let { TextComponent(it.hue.roundToInt().toString()) } } + val saturation = WangLine("saturation", SaturationWangPanel(screen, wangCanvas, saturationCallback = { onSaturationChoose(it) })) { it.map({ if (it.canRepresentHue()) it.toHSV() else null }, { it })?.let { TextComponent((it.saturation * 100f).roundToInt().toString() + "%") } } + val value = WangLine("value", ValueWangPanel(screen, wangCanvas, valueCallback = { onValueChoose(it) })) { it.map({ if (it.canRepresentHue()) it.toHSV() else null }, { it })?.let { TextComponent((it.value * 100f).roundToInt().toString() + "%") } } + + val wangs = listOf(red, green, blue, hue, saturation, value) + + init { + box.dock = Dock.LEFT + wangCanvas.dock = Dock.RIGHT + red.canvas.dockTop = 0f + + setColor(Either.right(HSVColor.WHITE)) + } + + companion object { + val paletteColors = listOf( + RGBAColor.rgb(0x9b59b6L), // Amethyst + RGBAColor.rgb(0x010101L), // Black + RGBAColor.rgb(0x0000ffL), // Blue + RGBAColor.rgb(0x8B4513L), // Brown + RGBAColor.rgb(0xe67e22L), // Carrot + RGBAColor.rgb(0xD2691EL), // Chocolate + RGBAColor.rgb(0xecf0f1L), // Clouds + RGBAColor.rgb(0xFF7F50L), // Coral + RGBAColor.rgb(0xDC143CL), // Crimson + RGBAColor.rgb(0x00FFFFL), // Cyan + RGBAColor.rgb(0x008B8BL), // DarkCyan + RGBAColor.rgb(0xBDB76BL), // DarkGold + RGBAColor.rgb(0xB8860BL), // DarkGoldenRod + RGBAColor.rgb(0x006400L), // DarkGreen + RGBAColor.rgb(0x16a085L), // DarkGreen + RGBAColor.rgb(0x8B008BL), // DarkMagenta + RGBAColor.rgb(0x556B2FL), // DarkOlive + RGBAColor.rgb(0xFF8C00L), // DarkOrange + RGBAColor.rgb(0x8B0000L), // DarkRed + RGBAColor.rgb(0xE9967AL), // DarkSalmon + RGBAColor.rgb(0x9400D3L), // DarkViolet + RGBAColor.rgb(0xFF1493L), // DeepPink + RGBAColor.rgb(0x00BFFFL), // DeepSkyBlue + RGBAColor.rgb(0x2ecc71L), // Emerald + RGBAColor.rgb(0xB22222L), // FireBrick + RGBAColor.rgb(0x228B22L), // ForestGreen + RGBAColor.rgb(0xFF00FFL), // Fuchsia + RGBAColor.rgb(0xDCDCDCL), // Gainsboro + RGBAColor.rgb(0xFFD700L), // Gold + RGBAColor.rgb(0xDAA520L), // GoldenRod + RGBAColor.rgb(0x00B000L), // Green + RGBAColor.rgb(0x808080L), // Grey + RGBAColor.rgb(0xFF69B4L), // HotPink + RGBAColor.rgb(0x4B0082L), // Indigo + RGBAColor.rgb(0xF0E68CL), // Khaki + RGBAColor.rgb(0xE6E6FAL), // Lavender + RGBAColor.rgb(0xFF9FF7L), // LavenderRose + RGBAColor.rgb(0xD3D3D3L), // LightGrey + RGBAColor.rgb(0x87CEFAL), // LightSkyBlue + RGBAColor.rgb(0xFFFFE0L), // LightYellow + RGBAColor.rgb(0x00ff00L), // Lime + RGBAColor.rgb(0x32CD32L), // LimeGreen + RGBAColor.rgb(0xBA55D3L), // MediumOrchid + RGBAColor.rgb(0x9370DBL), // MediumPurple + RGBAColor.rgb(0x7B68EEL), // MediumSlateBlue + RGBAColor.rgb(0x808000L), // Olive + RGBAColor.rgb(0x6B8E23L), // OliveGreen + RGBAColor.rgb(0xFFA500L), // Orange + RGBAColor.rgb(0xDA70D6L), // Orchid + RGBAColor.rgb(0xDB7093L), // PaleVioletRed + RGBAColor.rgb(0xCD853FL), // Peru + RGBAColor.rgb(0xFFC0CBL), // Pink + RGBAColor.rgb(0xB0E0E6L), // PowderBlue + RGBAColor.rgb(0xd35400L), // Pumpkin + RGBAColor.rgb(0x800080L), // Purple + RGBAColor.rgb(0xff0000L), // Red + RGBAColor.rgb(0x4169E1L), // RoyalBlue + RGBAColor.rgb(0xFA8072L), // Salmon + RGBAColor.rgb(0xF4A460L), // SandyBrown + RGBAColor.rgb(0x2E8B57L), // SeaGreen + RGBAColor.rgb(0xA0522DL), // Sienna + RGBAColor.rgb(0xbdc3c7L), // Silver + RGBAColor.rgb(0x87CEEBL), // SkyBlue + RGBAColor.rgb(0x708090L), // SlateGrey + RGBAColor.rgb(0x4682B4L), // SteelBlue + RGBAColor.rgb(0xf1c40fL), // SunFlower + RGBAColor.rgb(0x008080L), // Teal + RGBAColor.rgb(0xD8BFD8L), // Thistle + RGBAColor.rgb(0xEE82EEL), // Violet + ) + + fun > frame(screen: S, callback: Consumer, color: RGBAColor = RGBAColor.RED, title: Component? = TranslatableComponent("otm.gui.color_picker")): FramePanel { + return FramePanel.padded(screen, 164f, 118f, title).also { + ColorPickerPanel(screen, it, 0f, 0f, callback = callback).also { + it.dock = Dock.FILL + it.setColor(Either.left(color)) + } + + screen.addPanel(it) + it.toScreenCenter() + it.behaveAsWindow() + it.popup() + } + } + } +} diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_h.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_h.png new file mode 100644 index 0000000000000000000000000000000000000000..2a39ad8d4ae39a50aaa1f44e40b0c8572f05a007 GIT binary patch literal 627 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K56gb#`>zVA{<2r7mQtBO;fa8`Zx$)_E$wpM5O9lYt<{W}U#z}TlSIVb4{m+% z;CRy2gGVF7*~1+L{xB4l76z3}6#741B>2^Qex^@$0rVHG8{1>AWwl?_*q=V01uT==hAhh$GTRHqV}R zu5!M`nNyRTYW!?6FR*sX@6_A<=h)6C8x9yBb~Cf}mr)b*j@2nQ)Rxxz$W!PNXnIP# zNKbptc8k?lt+MY=-fs|cArLjjk(+X82%d1pO$#HJ@KfU<+PDw&-{z#Pa7XbFQ`BJcJ1E& z%j^o?vAXve*xL`dbZ)wCd++Y;ySMkJ-}?KP!E{SepR()p$G~V|OY(Mi0mTvn!%yek z^+1ZVz$3Dlfq`2Xgc%uT&5-~KvX^-Jy0SlK=M&K7S#V)KFHoq!)5S5Q;?~APeQ9c*r_RiK^KJjvXZx6IycrxoX@P}7fNVy?T4pI;7Mr-% R`dFad44$rjF6*2UngF&8@XG)I literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_v.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/gradient_v.png new file mode 100644 index 0000000000000000000000000000000000000000..cb1905189cd34b514b077bb9df5a2874c323feba GIT binary patch literal 682 zcmeAS@N?(olHy`uVBq!ia0vp^3JeU43><7g)}@6dWk60#rn7T^r?ay{K~a8MW=<*t zgT}<#iMAex9b}HiU-k;sQYsTDJP|PW%_60~rCqKY0&a1wwVDz0i`7?Zl8Ctb!L1J- z98bD>@MvT>d$^;(ABMux!l06gLjR|WL>;LpxYJ+$U9S8+!@|haRgxWytI{N2y2PA$ z8h-SFb`ys}iye>WnJbfL9@Viqe%-dFW^eZ=o%hA{eT+*Jj1H&^9iNdGaYXva=GoKE zRnE6Kb83=Pjh{{C1=ddaoqD_f9NYP1!vW*NZf3UrGHPPpu{!03+R|Ddc?w+uO;3pz z>1of|Zn65RRrdYK`wc=agc-#ww`^mXHo=8UDdkF_qo~-TEm=!ucwDZ#bJ+gXkKiYh zPKGinA7{!6P_(T3VE^Op+Q>QciY7Z1E_i-)oBgrI2YMH>ZhZT`F?YKk!++y~rKNwi zsu_IutXjO-wXfk|?t@3W7N4v>@RZNs(-QBtCmwaPoHlaonSaszY2(A_1@&j&uHD;z znO(s4B|a8caOMKHz7`jo{Os^{RnAKA4r`&`XyL OkY%2(elF{r5}E)QWA3*A literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/hsv.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/hsv.png new file mode 100644 index 0000000000000000000000000000000000000000..0c21edb22c8a64ff15a05989e49571614180a7d0 GIT binary patch literal 1881 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 delta 931 zcmV;U16=(53gQZ|x&bx+0RI60puMM)00009a7bBm000id000id0mpBsWB>pF2XskI zMF-~x2?!1!6(wWC000ADlkWi-e;|WQp;Nbb2$uZ?nn9yMmwM<2Xd8nmv=P*u3$_+R zN|w%+!AqtFnx(W*C^ZRq$$A}PDQ9Dyo^&T$ruPF8U3ik;@BO*=-U%v|3L}!J*Xx8* z3iGAteW47oZJQB7i0}LN53&2l005Srk=9S7hY=Exf$O@2Qaa}muP*>FfBwfY5WO!m zdrps%!uPTyWrHBd%cH5l>tW>eBP&9g*tX4V+a|v6GpvWlp0E=>E(46*^L=sG#aITCXL5E&x=Tl3{@gq3K6MQ zMu@}O3=r1+b&==g_PlFu^l4-K($&e#Ad2A0!-74W&;3#TCKJ+65{^9^#)iT z3`8AayWK_^A!$!hG7aWg`h4G)%K72F!KZ&_BR)c8h^qx;9FJvEzPqtTG_ zQiicMD|w#u@|wrtbV|<4<=8d46YTfM)MS*`e^7)W&wE~E1VdF0uWEl~djOeK;7N;&P-2V`6&Ixx<{2n+knhwn1m9B9I{bj{MVgT7>~zczfhQAsD$(^x?2dZ7?J!Ya~_p?Y)iecb$`bs{rp=te1lzV4@Qu zS@OHiNp$hXSVnI)6RuHJAf?v<0GMstLZUL?1=4u@cxQ+7X0wIAu?A3>!%5!+GLw&s z2CNZc`+yvBVH?)54`8AJ9Rb^NB9tZ-c+w&xlrZGhO)yO*5QV>JjWPgFyhuh+QT|X> zKsGhD0Y=fhtD&Kxp`oFnp`oFnp`oFnp`oFnp`no<{sG{Z!WzT57w-T7002ovPDHLk FV1j=wyPN<3