feat(launcher): launch button + dep-conflict modal

Detail-panel bottom-right Launch button: switches to the selected
module via engine.switch_module if dep-check is clean, otherwise
opens a modal listing conflicts with Cancel + 'Launch anyway'. The
latter is greyed out unless engine.module.switch_module_supports_fallback
is exposed (separate engine slice). FSM adds STATE_MODAL_CONFLICT
with transitions to/from STATE_LIST via cancel/launch_anyway/esc.

Generic panels/modal.lua takes a def-table (title/body/buttons) and
returns the clicked button id from handle_click; reusable for future
modals beyond the conflict case.
This commit is contained in:
Calic
2026-06-11 02:54:55 +02:00
parent 10d0ab6529
commit 89b7b060d1
4 changed files with 142 additions and 15 deletions

View File

@@ -15,6 +15,7 @@ end
local list_panel = require_panel("list")
local detail_panel = require_panel("detail")
local carousel = require_panel("carousel")
local modal_panel = require_panel("modal")
local STATE_LIST, STATE_QUIT, EXIT = fsm.STATE_LIST, fsm.STATE_QUIT_CONFIRM, fsm.EXIT
local state = STATE_LIST
@@ -339,6 +340,30 @@ function update(ctx, dt)
local mx, my = engine.input.get_mouse_pos()
-- L.7: Conflict-modal takes priority over STATE_LIST input. esc /
-- Cancel close the modal back to STATE_LIST; "Launch anyway" stays
-- a no-op until engine.module.switch_module_supports_fallback ships.
if ctx_panels.modal_open == "conflict" then
if input.was_action_pressed("ui_back") then
ctx_panels.modal_open = nil
state = fsm.next(state, "esc")
elseif input.was_action_pressed("ui_click") then
local id = modal_panel.handle_click(ctx_panels.modal_def, mx, my)
if id == "cancel" then
ctx_panels.modal_open = nil
state = fsm.next(state, "cancel")
elseif id == "launch_anyway" then
local mid = ctx_panels.modal_def.module_id
ctx_panels.modal_open = nil
state = fsm.next(state, "launch_anyway")
if engine.module.switch_module_supports_fallback then
engine.switch_module(mid, { mode = "fallback" })
end
end
end
return
end
if state == STATE_LIST then
if input.was_action_pressed("ui_back") then
state = fsm.next(state, "esc")
@@ -370,6 +395,11 @@ function render(ctx)
list_panel.render(ctx_panels)
detail_panel.render(ctx_panels)
-- L.7: Conflict modal renders above the panels (backdrop dims them).
if ctx_panels.modal_open == "conflict" then
modal_panel.render(ctx_panels.modal_def, screen_w, screen_h)
end
if state == STATE_QUIT then
engine.render.draw_rect(0, 0, screen_w, screen_h, COLOR_DIM)
local modal_w, modal_h = 360, 140