roleplay/client/script/color_picker.lua

266 lines
5.9 KiB
Lua

-- Color picker: element declarations and interaction logic.
-- z controls stacking within the container; equal z means don't overlap.
local bg = ui.rect{
pos = {0, 0},
size = {190, 150},
color = {0.4, 0.4, 0.4, 0.8},
z = 0,
}
local sv_square = ui.rect{
type = RECT_HSV,
pos = {2, 2},
size = {130, 130},
colors = {{0, 0, 1, 1}, {0, 1, 1, 1}, {0, 0, 0, 1}, {0, 1, 0, 1}},
events = EVENT_BUTTON,
z = 1,
}
local hue_bar = ui.rect{
type = RECT_HSV,
pos = {134, 2},
size = {10, 130},
colors = {{0, 1, 1, 1}, {0, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}},
events = EVENT_BUTTON + EVENT_SCROLL,
z = 1,
}
local sv_outline = ui.rect{
pos = {130-4, 130-4},
size = {7, 7},
color = {0, 0, 0, 1},
z = 2,
}
local sv_select = ui.rect{
type = RECT_HSV,
pos = {130-3, 130-3},
size = {5, 5},
color = {1, 0, 0, 1},
z = 3,
}
local hex_area = ui.rect{
pos = {20, 134},
size = {95, 15},
events = EVENT_BUTTON + EVENT_CURSOR,
z = 1,
}
local hue_select = ui.rect{
pos = {134, 2},
size = {10, 1},
color = {0, 0, 0, 1},
z = 2,
}
local hex_text = ui.text{
pos = {2, 150},
size = 16,
color = {1, 1, 1, 1},
font = 0,
max_length = 16,
z = 2,
}
-- 12 saved color slots in a 2-wide grid
local slots = {}
local slot_index = {}
for i = 1, 12 do
local slot = ui.rect{
pos = {146 + ((i-1) % 2)*22, 2 + math.floor((i-1)/2)*22},
size = {20, 20},
color = {0, 0, 0, 1},
events = EVENT_BUTTON,
z = 1,
}
slots[i] = slot
slot_index[slot] = i
end
local state = {
hsv = {0, 0, 0},
rgb = {0, 0, 0, 1},
saved = {},
hex_string = "#000000FF",
}
for i = 1, 12 do state.saved[i] = {0, 0, 0, 0} end
local function clamp01(x) return math.max(0, math.min(1, x)) end
local function update_hex_string()
state.hex_string = string.format("#%02X%02X%02X%02X",
math.floor(state.rgb[1]*255 + 0.5),
math.floor(state.rgb[2]*255 + 0.5),
math.floor(state.rgb[3]*255 + 0.5),
math.floor(state.rgb[4]*255 + 0.5))
hex_text:set_text(state.hex_string)
end
local function sync_rgb_from_hsv()
state.rgb[1], state.rgb[2], state.rgb[3] =
ui.hsv_to_rgb(state.hsv[1], state.hsv[2], state.hsv[3])
update_hex_string()
end
local function sv_pick(s, v)
s = clamp01(s)
v = clamp01(v)
state.hsv[2] = s
state.hsv[3] = v
sv_select:set_pos(s*130 - 2, 130 - v*130 - 2)
for corner = 0, 3 do
sv_select:set_corner_color(corner, state.hsv[1], s, v, 1)
end
sv_outline:set_pos(s*130 - 3, 130 - v*130 - 3)
sync_rgb_from_hsv()
end
local function hue_set(h)
h = clamp01(h)
state.hsv[1] = h
for _, element in ipairs({sv_square, sv_select}) do
for corner = 0, 3 do
local _, s, v, a = element:corner_color(corner)
element:set_corner_color(corner, h, s, v, a)
end
end
hue_select:set_pos(134, 2 + h*130)
sync_rgb_from_hsv()
end
local function hex_string_highlight(value)
for corner = 0, 3 do
local r, g, _, _ = hex_area:corner_color(corner)
hex_area:set_corner_color(corner, r, g, value, value)
end
end
local function set_saved(index, color)
state.saved[index] = {color[1], color[2], color[3], color[4]}
local slot = slots[index]
for corner = 0, 3 do
local _, _, _, a = slot:corner_color(corner)
slot:set_corner_color(corner, color[1], color[2], color[3], a)
end
end
-- Sync HSV+visuals from state.rgb; used after setting state.rgb directly.
local function apply_state_rgb()
state.hsv[1], state.hsv[2], state.hsv[3] =
ui.rgb_to_hsv(state.rgb[1], state.rgb[2], state.rgb[3])
hue_set(state.hsv[1])
sv_pick(state.hsv[2], state.hsv[3])
end
local drag = {
[sv_square] = function(x, y) sv_pick(x, 1 - y) end,
[hue_bar] = function(x, y) hue_set(y) end,
}
function on_button(element, x, y, button, action, mods)
if drag[element] then
if action == PRESS and button == MOUSE_LEFT then
element:focus()
drag[element](x, y)
elseif action == RELEASE and button == MOUSE_LEFT then
ui.blur()
end
return true
elseif element == hex_area then
if action == PRESS and button == MOUSE_LEFT then
element:focus()
hex_string_highlight(1)
end
return true
elseif slot_index[element] then
local index = slot_index[element]
if action == PRESS then
if button == MOUSE_LEFT then
local saved = state.saved[index]
state.rgb = {saved[1], saved[2], saved[3], saved[4]}
apply_state_rgb()
elseif button == MOUSE_RIGHT then
set_saved(index, state.rgb)
elseif button == MOUSE_MIDDLE then
set_saved(index, {0, 0, 0, 0})
end
end
return true
end
return false
end
function on_cursor(element, x, y)
for el, fn in pairs(drag) do
if el:is_focused() then
fn(x, y)
return true
end
end
return false
end
function on_scroll(element, x, y)
if element == hue_bar then
hue_set(state.hsv[1] + y*0.01)
return true
end
return false
end
function on_key(element, key, action, mods)
if element ~= hex_area then return false end
if action == PRESS then
if key == KEY_ESCAPE then
sync_rgb_from_hsv()
ui.blur()
elseif key == KEY_ENTER then
local s = state.hex_string
for i = 1, 4 do
state.rgb[i] = (tonumber(s:sub(2*i, 2*i + 1), 16) or 0) / 255
end
apply_state_rgb()
ui.blur()
elseif key == KEY_BACKSPACE then
if #state.hex_string > 1 then
state.hex_string = state.hex_string:sub(1, -2)
hex_text:set_text(state.hex_string)
end
end
end
return true
end
function on_text(element, codepoint)
if element ~= hex_area then return false end
if codepoint < 128 then
local ch = string.char(codepoint):upper()
if ch:match("[0-9A-F]") and #state.hex_string < 9 then
state.hex_string = state.hex_string .. ch
hex_text:set_text(state.hex_string)
end
end
return true
end
function on_deselect(element)
if element == hex_area then
hex_string_highlight(0)
end
end
-- runs once at container load
hex_text:set_text(state.hex_string)