refactor(map-editor): generalize cheatsheet into MODALS framework

Introduces a single-stack modal table where each entry declares
title, dimensions, render fn and on_click fn. The existing
Cheatsheet becomes the framework's first entry; behaviour is
preserved exactly. Dispatcher absorbs clicks while any modal is
open and routes them to the modal's on_click for close-decision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Calic
2026-06-01 19:38:42 +02:00
parent e373aa8a1f
commit 15159d7fd5

View File

@@ -478,6 +478,29 @@ local ui = (function()
{ key="LMB on Layer row", action="Set as active layer" },
}
-- =====================================================================
-- Modal framework (0.3.0)
-- Each modal: { title, w, h, render(mx, my), on_click(mx, my, btn) -> close? }
-- Single-stack: only state.modal_open at a time. Esc + click-outside
-- handled centrally in the dispatcher.
-- =====================================================================
local MODALS = {} -- populated below
-- Dispatcher returns true if the click was consumed (always true when a
-- modal is open — modals fully absorb input).
local function dispatch_modal_click(mx, my, button)
local id = state.get_modal_open()
if not id then return false end
local m = MODALS[id]
if not m then
-- Defensive: unknown id, close it.
state.close_modal()
return true
end
local close = m.on_click(mx, my, button)
if close then state.close_modal() end
return true
end
-- =====================================================================
-- Helpers
@@ -601,6 +624,33 @@ local ui = (function()
}
end
-- MODALS entries (defined here, after helpers + layout fns are declared,
-- so closures can reference rgba / cheatsheet_layout as upvalues).
MODALS.cheatsheet = {
title = "Hotkeys (I to close)",
w = CHEAT_W,
h = CHEAT_H,
render = function(mx, my)
local c = cheatsheet_layout()
-- Dim the whole screen behind the modal
local screen_w, screen_h = engine.window.size()
engine.render.draw_rect(0, 0, screen_w, screen_h, rgba(0x000000, 0xA0))
engine.render.draw_rect(c.x, c.y, c.w, c.h, rgba(COL_MODAL_BG, 0xFF))
engine.render.draw_rect_lines(c.x, c.y, c.w, c.h, rgba(COL_MODAL_BORDER, 0xFF))
engine.render.draw_text("Hotkeys (I to close)", c.x + 12, c.y + 10, 14,
rgba(COL_TEXT, 0xFF))
local row_y = c.y + 38
for _, entry in ipairs(CHEATSHEET) do
engine.render.draw_text(entry.key, c.x + 16, row_y, 12, rgba(COL_TEXT, 0xFF))
engine.render.draw_text(entry.action, c.x + 140, row_y, 12, rgba(COL_TEXT_MUTED, 0xFF))
row_y = row_y + 20
end
end,
on_click = function(mx, my, button)
return true -- any click closes
end,
}
-- =====================================================================
-- Drawing
-- =====================================================================
@@ -742,24 +792,6 @@ local ui = (function()
rgba(COL_TEXT, 0xFF))
end
local function draw_cheatsheet(mx, my)
if state.get_modal_open() ~= "cheatsheet" then return end
local c = cheatsheet_layout()
-- Dim the whole screen behind the modal
local screen_w, screen_h = engine.window.size()
engine.render.draw_rect(0, 0, screen_w, screen_h, rgba(0x000000, 0xA0))
engine.render.draw_rect(c.x, c.y, c.w, c.h, rgba(COL_MODAL_BG, 0xFF))
engine.render.draw_rect_lines(c.x, c.y, c.w, c.h, rgba(COL_MODAL_BORDER, 0xFF))
engine.render.draw_text("Hotkeys (I to close)", c.x + 12, c.y + 10, 14,
rgba(COL_TEXT, 0xFF))
local row_y = c.y + 38
for _, entry in ipairs(CHEATSHEET) do
engine.render.draw_text(entry.key, c.x + 16, row_y, 12, rgba(COL_TEXT, 0xFF))
engine.render.draw_text(entry.action, c.x + 140, row_y, 12, rgba(COL_TEXT_MUTED, 0xFF))
row_y = row_y + 20
end
end
-- =====================================================================
-- Public API
-- =====================================================================
@@ -770,7 +802,10 @@ local ui = (function()
draw_side_panel(mx, my)
draw_palette(mx, my)
draw_status(mx, my)
draw_cheatsheet(mx, my) -- last so it overlays everything when open
local mid = state.get_modal_open()
if mid and MODALS[mid] then
MODALS[mid].render(mx, my)
end
end
-- 0.2.0c.3 debug overlay: per-cell slot/rot annotation. Independent
@@ -869,6 +904,11 @@ local ui = (function()
-- Hit-test mouse click. Returns true if the click was consumed by UI.
-- button: "left" | "right"
function M.handle_click(mx, my, button)
-- Modal absorbs all clicks while open.
if dispatch_modal_click(mx, my, button) then
return true
end
-- Top toolbar buttons
for _, b in ipairs(toolbar_buttons_layout()) do
if in_rect(mx, my, b.x, b.y, b.w, b.h) then