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:
78
init.lua
78
init.lua
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user