Files
gen1recomp/src/import/ShowcaseEditor.lua
T
2026-10-06 11:00:16 -04:00

131 lines
5.7 KiB
Lua

local Kit = require("src.ui.kit.Kit")
local Theme = require("src.ui.kit.Theme")
local UI = require("src.import.BoxUI")
local Store = require("src.box.Store")
local Showcase = require("src.box.Showcase")
local Editor = {}
local PAL = Theme.PAL
local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end
local function angle(x, y) return math.atan2(y, x) end
local function wrap(v) return (v + math.pi) % (2 * math.pi) - math.pi end
local function record(s)
s.stageUndo = s.stageUndo or {}
s.stageUndo[#s.stageUndo + 1] = Store.copy(s.stageDraft)
if #s.stageUndo > 20 then table.remove(s.stageUndo, 1) end
end
local function start(s, kind, index, mx, my, rect)
local p = s.stageDraft.pieces[index]
if not p then s.stageGesture = nil; return end
s.stageGesture = { kind = kind, x = mx, y = my, px = p.x, py = p.y,
scale = p.scale, rotation = p.rotation, index = index,
cx = rect.x + p.x * rect.w, cy = rect.y + p.y * rect.h,
draft = s.stageDraft, stage = s.stageIndex }
end
function Editor.begin(s, kind, mx, my, rect)
if not s.stageDraft.pieces[s.stagePiece] then return end
start(s, kind, s.stagePiece, mx, my, rect)
end
local function current(s, mx, my, rect)
local t = s.stageGesture
if not t or t.draft == s.stageDraft then return t end
if t.stage ~= s.stageIndex then s.stageGesture = nil; return nil end
start(s, t.kind, t.index, mx, my, rect)
return s.stageGesture
end
function Editor.move(s, mx, my, rect)
local t = current(s, mx, my, rect)
local p = t and s.stageDraft.pieces[t.index]
if not p then return end
local x, y, scale, rotation = p.x, p.y, p.scale, p.rotation
if t.kind == "move" then
x, y = clamp(t.px + (mx - t.x) / rect.w, 0, 1), clamp(t.py + (my - t.y) / rect.h, 0, 1)
elseif t.kind == "resize" then
scale = clamp(t.scale * math.exp((mx - t.x - my + t.y) / math.max(70, rect.w * .2)), .25, 4)
elseif t.kind == "rotate" then
rotation = wrap(t.rotation + angle(mx - t.cx, my - t.cy) - angle(t.x - t.cx, t.y - t.cy))
end
if x == p.x and y == p.y and scale == p.scale and rotation == p.rotation then return end
if not t.recorded then record(s); t.recorded = true end
p.x, p.y, p.scale, p.rotation = x, y, scale, rotation
end
function Editor.finish(s) s.stageGesture = nil end
function Editor.pointerPressed(imp,id,x,y)
local s=imp._boxState
if imp.tab~="box" or not s or s.toolsPage~="Showcase" or not s.stageRect
or imp._boxPopup or imp._modalUpNow or Kit.blockClicks then return false end
local function inside(r)
local mx,my=Kit.mouseX,Kit.mouseY
Kit.mouseX,Kit.mouseY=x,y
local hit=Kit.hit(r.x,r.y,r.w,r.h)
Kit.mouseX,Kit.mouseY=mx,my
return hit
end
if imp._tabRegionRect and not inside(imp._tabRegionRect) then return false end
for _,handle in ipairs(s.stageHandles or {}) do
if inside(handle) then
if handle.kind=="resize" or handle.kind=="rotate" then
Editor.begin(s,handle.kind,x,y,s.stageRect);s._stagePointer=id
else handle.action() end
return true
end
end
if not inside(s.stageRect) then return false end
local r=s.stageRect
s.stagePiece=Showcase.hit(s.stageDraft,r.x,r.y,r.w,r.h,x,y)
if s.stagePiece then Editor.begin(s,"move",x,y,r);s._stagePointer=id end
return true
end
function Editor.pointerMoved(imp,id,x,y)
local s=imp._boxState
if not s or s._stagePointer~=id then return false end
Editor.move(s,x,y,s.stageRect)
return true
end
function Editor.pointerReleased(imp,id)
local s=imp._boxState
if not s or s._stagePointer~=id then return false end
Editor.finish(s);s._stagePointer=nil
return true
end
function Editor.draw(imp, s, x, y, w, h, m, edit)
local rect = { x = x, y = y, w = w, h = h }
Showcase.draw(s.service.state, s.stageDraft, x, y, w, h)
s.stageRect = rect
s.stageHandles = {}
local p = s.stageDraft.pieces[s.stagePiece or 0]
local toolbar
if p then
local size = math.min(w / 8, h / 2) * p.scale
local cx, cy = x + p.x * w, y + p.y * h
Theme.strokeRounded(cx - size / 2 - 4, cy - size / 2 - 4, size + 8, size + 8, PAL.blue, 1, 2, 6)
local bh, gap = math.max(Kit.tapMin(), 30 * m.s), 3 * m.s
local tw = 5 * bh + 4 * gap
local tx, ty = clamp(cx - tw / 2, x + 4, math.max(x + 4, x + w - tw - 4)),
clamp(cy + size / 2 + 10, y + 4, math.max(y + 4, y + h - bh - 4))
toolbar = { x = tx, y = ty, w = tw, h = bh }
local actions = {
{ "resize", "expand", function() Editor.begin(s, "resize", Kit.mouseX, Kit.mouseY, rect) end },
{ "flip-h", "arrow-left-right", function() edit(function() p.flip = not p.flip end) end },
{ "flip-v", "arrow-up-down", function() edit(function() p.flipY = not p.flipY end) end },
{ "rotate", "rotate-ccw", function() Editor.begin(s, "rotate", Kit.mouseX, Kit.mouseY, rect) end },
{ "fine", "sliders-horizontal", function() s.stageSection = "Placement"; s.stageFine = true end },
}
for index, action in ipairs(actions) do
s.stageHandles[index]={x=tx+(index-1)*(bh+gap),y=ty,w=bh,h=bh,kind=action[1],action=action[3]}
UI.button(imp, tx + (index - 1) * (bh + gap), ty, bh, bh, "stage-handle-" .. action[1], "", action[3],
{ icon = action[2], face = "invert" })
end
end
if not Kit.blockClicks and not imp._boxPopup then
if s.stageGesture then current(s, Kit.mouseX, Kit.mouseY, rect) end
if s.stageGesture and not s._stagePointer then
if Kit.mouseDown then Editor.move(s, Kit.mouseX, Kit.mouseY, rect) else Editor.finish(s) end
elseif Kit.press(x, y, w, h) and not (toolbar and Kit.hit(toolbar.x, toolbar.y, toolbar.w, toolbar.h)) then
local selected = Showcase.hit(s.stageDraft, x, y, w, h, Kit.mouseX, Kit.mouseY)
s.stagePiece = selected
if selected then Editor.begin(s, "move", Kit.mouseX, Kit.mouseY, rect) end
end
end
end
return Editor