working on in app editor

This commit is contained in:
2026-10-11 09:16:08 -07:00
parent 73b760e658
commit 39098cc206
10 changed files with 436 additions and 56 deletions
+368 -5
View File
@@ -71,11 +71,11 @@ end)
local loadLog = log:child("load")
local old = multi.error
multi.error = function(self, err)
loadLog:error("Error: %s", err)
loadLog:error(debug.traceback())
love.quit()
end
-- multi.error = function(self, err)
-- loadLog:error("Error: %s", err)
-- loadLog:error(debug.traceback())
-- love.quit()
-- end
function readFileData(path)
return io.open(path,"r"):read("*a")
@@ -90,8 +90,358 @@ local picker = THREAD:newFunction(function()
return nil, "No file selected." .. (err and (" (" .. err .. ")") or "")
end
end, true)
local nof = gui.noOf
function gui:newNumberPicker(x, y, w, h, sx, sy, sw, sh, opts)
opts = opts or {}
opts.value = opts.value or 0
opts.inc = opts.inc or 1
opts.decimals = opts.decimals or 0 -- 0 = integers only
-- opts.min / opts.max / opts.onChange are optional, leave nil for "no limit"
local picker = self:newFrame(x, y, w, h, sx, sy, sw, sh)
picker:setRoundness(6, 6)
picker.active = false
local leftF = picker:newFrame(0, 0, 0, 0, 0, 0, .15, 1)
local left = leftF:newTextButton("-", 0, 0, 0, 0, 0, 0, 1, 1)
local rightF = picker:newFrame(0, 0, 0, 0, .85, 0, .15, 1)
local right = rightF:newTextButton("+", 0, 0, 0, 0, 0, 0, 1, 1)
local textbox = picker:newTextBox("", 0, 0, 0, 0, .15, 0, .7, 1)
textbox.blink = false
gui.apply({
scaleFont = {.45},
centerFont = {},
run = {function(self)
if self.text == "-" or self.text == "+" then
self:centerX(true)
self:centerY(true) -- also center vertically, not just horizontally
end
end},
align = gui.ALIGN_CENTER,
}, textbox, left, right)
gui.apply({
visibility = 0,
drawBorder = false, -- flat, no boxy outline around the arrows
}, left, right, leftF, rightF)
-- ---- value handling ----------------------------------------------
local function clamp(n)
if opts.min and n < opts.min then n = opts.min end
if opts.max and n > opts.max then n = opts.max end
return n
end
local function round(n)
if opts.decimals > 0 then
local mult = 10 ^ opts.decimals
return math.floor(n * mult + 0.5) / mult
end
return math.floor(n + 0.5)
end
local function format(n)
if opts.decimals > 0 then
return string.format("%." .. opts.decimals .. "f", n)
end
return tostring(math.floor(n))
end
local function updateButtons()
local canDec = (opts.min == nil) or (opts.value > opts.min)
local canInc = (opts.max == nil) or (opts.value < opts.max)
left.active, right.active = canDec, canInc
left.textColor = canDec and {0, 0, 0, 1} or {.6, .6, .6, 1}
right.textColor = canInc and {0, 0, 0, 1} or {.6, .6, .6, 1}
end
function picker:setValue(n, silent)
n = round(clamp(n))
opts.value = n
textbox.text = format(n)
textbox.cur_pos = #textbox.text
updateButtons()
if not silent and opts.onChange then opts.onChange(n) end
end
function picker:getValue()
return opts.value
end
picker:setValue(opts.value, true)
left:OnReleased(function() picker:setValue(opts.value - opts.inc) end)
right:OnReleased(function() picker:setValue(opts.value + opts.inc) end)
textbox:OnWheelMoved(function(_,dx, dy)
if dy > 0 then picker:setValue(opts.value + opts.inc)
elseif dy < 0 then picker:setValue(opts.value - opts.inc) end
end)
-- ---- restrict typing to numbers only -------------------------------
local allowNegative = opts.min == nil or opts.min < 0
local allowDecimal = opts.decimals > 0
local function sanitize(s)
local out, seenDot = {}, false
for i = 1, #s do
local ch = s:sub(i, i)
if ch:match("%d") then
out[#out + 1] = ch
elseif ch == "-" and i == 1 and allowNegative then
out[#out + 1] = ch
elseif ch == "." and allowDecimal and not seenDot then
out[#out + 1] = ch
seenDot = true
end
end
return table.concat(out)
end
local tiRef = gui.Events.OnTextInputed(function(input)
if gui:getObjectFocus() ~= textbox then return end
local clean = sanitize(textbox.text)
if clean ~= textbox.text then
textbox.text = clean
textbox.cur_pos = math.min(textbox.cur_pos, #clean)
end
end)
local kpRef = gui.Events.OnKeyPressed(function(key)
if gui:getObjectFocus() ~= textbox then return end
if key == "up" then picker:setValue(opts.value + opts.inc)
elseif key == "down" then picker:setValue(opts.value - opts.inc) end
end)
-- re-normalize (round/clamp/reformat) whatever was typed once they
-- hit enter or click away, so "3." or "-" don't linger as invalid text
local function commit()
picker:setValue(tonumber(textbox.text) or opts.value)
end
textbox:OnReturn(commit)
textbox:OnPressedOuter(commit)
textbox:OnDestroy(function()
gui.Events.OnTextInputed:Unconnect(tiRef)
gui.Events.OnKeyPressed:Unconnect(kpRef)
end)
return picker
end
-- ---- HSV <-> RGB helpers ----
local function hsvToRgb(h, s, v)
local c = v * s
local x = c * (1 - math.abs((h / 60) % 2 - 1))
local m = v - c
local r, g, b
if h < 60 then r, g, b = c, x, 0
elseif h < 120 then r, g, b = x, c, 0
elseif h < 180 then r, g, b = 0, c, x
elseif h < 240 then r, g, b = 0, x, c
elseif h < 300 then r, g, b = x, 0, c
else r, g, b = c, 0, x end
return r + m, g + m, b + m
end
local function rgbToHsv(r, g, b)
local max, min = math.max(r, g, b), math.min(r, g, b)
local d = max - min
local h = 0
if d ~= 0 then
if max == r then h = ((g - b) / d) % 6
elseif max == g then h = (b - r) / d + 2
else h = (r - g) / d + 4 end
h = h * 60
end
local s = (max == 0) and 0 or (d / max)
return h, s, max
end
local function clampNum(n, lo, hi) return math.max(lo, math.min(hi, n)) end
local function newCornerGradient(tl, tr, br, bl)
return love.graphics.newMesh({
{0, 0, 0, 0, tl[1], tl[2], tl[3], tl[4]},
{1, 0, 0, 0, tr[1], tr[2], tr[3], tr[4]},
{1, 1, 0, 0, br[1], br[2], br[3], br[4]},
{0, 1, 0, 0, bl[1], bl[2], bl[3], bl[4]},
}, "fan")
end
local _whiteFade, _blackFade
local function getFadeImages()
if not _whiteFade then
-- opaque white on the left fading to transparent on the right
_whiteFade = newCornerGradient(
{1,1,1,1}, {1,1,1,0}, {1,1,1,0}, {1,1,1,1}
)
-- transparent at top fading to opaque black at bottom
_blackFade = newCornerGradient(
{0,0,0,0}, {0,0,0,0}, {0,0,0,1}, {0,0,0,1}
)
end
return _whiteFade, _blackFade
end
function gui:newColorPicker(x, y, w, h, sx, sy, sw, sh, opts)
opts = opts or {}
local r0 = (opts.r or 255) / 255
local g0 = (opts.g or 0) / 255
local b0 = (opts.b or 0) / 255
local picker = self:newFrame(x, y, w, h, sx, sy, sw, sh)
picker.active = false -- container only; don't compete for focus with its children
picker.drawBorder = false
picker.visibility = 0
local hue, sat, val = rgbToHsv(r0, g0, b0)
local hueR, hueG, hueB = hsvToRgb(hue, 1, 1)
-- ---- Saturation/Value square ----
local svFrame = picker:newFrame(6, 6, -12, -12, 0, 0, .55, 1)
svFrame.drawBorder = false
svFrame.visibility = 0
local whiteFade, blackFade = getFadeImages()
svFrame.post = function(self)
local sx2, sy2, sw2, sh2 = self:getAbsolutes()
love.graphics.setColor(hueR, hueG, hueB, 1)
love.graphics.rectangle("fill", sx2, sy2, sw2, sh2)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(whiteFade, sx2, sy2, 0, sw2, sh2)
love.graphics.setColor(1, 1, 1, .95)
love.graphics.draw(blackFade, sx2, sy2, 0, sw2, sh2)
love.graphics.setColor(0, 0, 0, .4)
love.graphics.rectangle("line", sx2, sy2, sw2, sh2)
love.graphics.setColor(1, 1, 1, 1)
end
local svThumb = svFrame:newFrame(0, 0, 0, 0)
svThumb:makeCircle(0, 0, 6)
svThumb.color = {1, 1, 1}
svThumb.borderColor = {0, 0, 0}
svThumb.drawBorder = true
-- ---- Hue strip ----
local hueSlider = picker:newFrame(0, 6, 0, -12, .57, 0, .10, 1)
-- applyGradient's "horizontal"/"vertical" naming is backwards from what
-- you'd expect visually (it names how colors are laid out in the source
-- image, not the on-screen direction) -- if this renders sideways for
-- you, just flip the string to "vertical".
hueSlider:applyGradient("horizontal",
{1, 0, 0, 1}, {1, 1, 0, 1}, {0, 1, 0, 1},
{0, 1, 1, 1}, {0, 0, 1, 1}, {1, 0, 1, 1}, {1, 0, 0, 1})
hueSlider.drawBorder = false
local hueThumb = hueSlider:newFrame(0, 0, 0, 4, 0, 0, 1, 0)
hueThumb.color = {1, 1, 1}
hueThumb.borderColor = {0, 0, 0}
hueThumb.drawBorder = true
-- ---- Preview + numeric fields ----
local preview = picker:newFrame(6, 6, -12, -6, .70, 0, .30, .20)
preview:setRoundness(4, 4)
local rPicker, gPicker, bPicker
local function refresh(fireChange)
local r, g, b = hsvToRgb(hue, sat, val)
hueR, hueG, hueB = hsvToRgb(hue, 1, 1)
preview.color = {r, g, b}
local _, _, sw2, sh2 = svFrame:getAbsolutes()
svThumb:rawSetDualDim(sat * sw2 - 12, (1 - val) * sh2 - 6)
local _, _, _, hh2 = hueSlider:getAbsolutes()
hueThumb:rawSetDualDim(nil, (hue / 360) * hh2 - 2)
local r255 = math.floor(r * 255 + .5)
local g255 = math.floor(g * 255 + .5)
local b255 = math.floor(b * 255 + .5)
if rPicker then
rPicker:setValue(r255, true)
gPicker:setValue(g255, true)
bPicker:setValue(b255, true)
end
if fireChange ~= false and opts.onChange then
opts.onChange(r255, g255, b255)
end
end
local function onRGBEdited()
local r = rPicker:getValue() / 255
local g = gPicker:getValue() / 255
local b = bPicker:getValue() / 255
hue, sat, val = rgbToHsv(r, g, b)
refresh(true)
end
rPicker = picker:newNumberPicker(6, 0, -12, -6, .70, .26, .30, .20,
{value = math.floor(r0 * 255 + .5), min = 0, max = 255, onChange = onRGBEdited})
gPicker = picker:newNumberPicker(6, 0, -12, -6, .70, .52, .30, .20,
{value = math.floor(g0 * 255 + .5), min = 0, max = 255, onChange = onRGBEdited})
bPicker = picker:newNumberPicker(6, 0, -12, -6, .70, .78, .30, .20,
{value = math.floor(b0 * 255 + .5), min = 0, max = 255, onChange = onRGBEdited})
gui.apply({
scaleFont={.1}
},rPicker,gPicker,bPicker)
-- ---- drag interaction (same press/move/release pattern the built-in
-- textbox uses for its own text-selection dragging) ----
local function setSV(mx, my)
local sx2, sy2, sw2, sh2 = svFrame:getAbsolutes()
local lx = clampNum(mx - sx2, 0, sw2)
local ly = clampNum(my - sy2, 0, sh2)
sat = sw2 > 0 and (lx / sw2) or 0
val = sh2 > 0 and (1 - ly / sh2) or 0
refresh(true)
end
local svDragging = false
svFrame:OnPressed(function(_, mx, my) svDragging = true; setSV(mx, my) end)
svFrame:OnMoved(function(_, mx, my) if svDragging then setSV(mx, my) end end)
svFrame:OnReleased(function() svDragging = false end)
svFrame:OnReleasedOuter(function() svDragging = false end)
local function setHue(mx, my)
local _, sy2, _, sh2 = hueSlider:getAbsolutes()
local ly = clampNum(my - sy2, 0, sh2)
hue = sh2 > 0 and (ly / sh2) * 360 or 0
refresh(true)
end
local hueDragging = false
hueSlider:OnPressed(function(_, mx, my) hueDragging = true; setHue(mx, my) end)
hueSlider:OnMoved(function(_, mx, my) if hueDragging then setHue(mx, my) end end)
hueSlider:OnReleased(function() hueDragging = false end)
hueSlider:OnReleasedOuter(function() hueDragging = false end)
-- ---- public API ----
function picker:getColor()
local r, g, b = hsvToRgb(hue, sat, val)
return math.floor(r * 255 + .5), math.floor(g * 255 + .5), math.floor(b * 255 + .5)
end
function picker:setColor(r255, g255, b255)
hue, sat, val = rgbToHsv(r255 / 255, g255 / 255, b255 / 255)
refresh(false)
end
refresh(false)
return picker
end
function love.load()
love.keyboard.setKeyRepeat(true)
-- local z = zip.new()
-- z:add("main.lua",readFileData("main.lua"))
-- z:add("package.bat",readFileData("package.bat"))
@@ -105,6 +455,18 @@ function love.load()
-- print("File selected",file)
-- end).OnError(print)
-- local numpicker = gui:newNumberPicker(0, 0, 200, 40, .5, .5, 0, 0, {
-- value = 1, inc = 1, min = 1, max = 4,
-- onChange = function(v) print("value is now", v) end,
-- })
local frame = gui:newFrame(0, 0, 500, 250, .5, .5, 0, 0)
local colorpicker = frame:newColorPicker(0, 0, 0, 0, 0, 0, 1, 1, {
r = 255, g = 100, b = 50,
onChange = function(r, g, b) print("color:", r, g, b) end,
})
--[[ -- bootstrap
loadLog:info("Starting Jeopardy Game")
loadLog:debug("Setting game identity to jeopardy")
for i,v in ipairs(color.listColors(showAllColors)) do
@@ -129,6 +491,7 @@ function love.load()
menu.init(bg)
loadLog:debug("Displaying title menu")
title:visible(true)
]]
end
function love.update(dt)