local input = require("lib-core.input") -- fsm.lua lives in this module's dir; engine_lua_lib's searcher only resolves -- declared lib-deps, so multi-file modules use dofile + engine.module.dir_of. local fsm = dofile(engine.module.dir_of("lib-sporel.launcher") .. "/fsm.lua") local STATE_LIST, STATE_QUIT, EXIT = fsm.STATE_LIST, fsm.STATE_QUIT_CONFIRM, fsm.EXIT local state = STATE_LIST local modules = {} -- [{id, version, name, rect={x,y,w,h}}] local quit_buttons = {} -- {yes = rect, no = rect} local screen_w, screen_h -- queried in init() via engine.window.size() (P.3.5) -- CI-conditional self-exit (matches spine-prototype's pattern). -- Interactive runs (no SPOREL_CI=1) loop until ESC or quit-confirm. local frames = 0 local CI_FRAMES = 60 local TITLE = "Sporel Launcher" local QUIT_PROMPT = "Sporel beenden?" local YES_LABEL = "Ja" local NO_LABEL = "Nein" local EMPTY_LABEL = "Keine Module installiert." local FONT_TITLE = 28 local FONT_BUTTON = 20 local PAD_X = 16 local PAD_Y = 8 local COLOR_BG = 0x202020FF local COLOR_BTN = 0x404040FF local COLOR_HOVER = 0x606060FF local COLOR_TEXT = 0xE0E0E0FF local COLOR_DIM = 0x000000B4 -- semi-transparent overlay local COLOR_MODAL = 0x303030FF local hit = engine.spatial.aabb_contains_point function init(ctx) screen_w, screen_h = engine.window.size() local list = engine.module.list() local y = 80 for i, m in ipairs(list) do local label = m.name or m.id local w, h = engine.render.measure_text(label, FONT_BUTTON) local btn_w = w + 2 * PAD_X local btn_h = h + 2 * PAD_Y modules[i] = { id = m.id, version = m.version, name = label, rect = { x = (screen_w - btn_w) / 2, y = y, w = btn_w, h = btn_h } } y = y + btn_h + 8 end local wy, hy = engine.render.measure_text(YES_LABEL, FONT_BUTTON) local wn, hn = engine.render.measure_text(NO_LABEL, FONT_BUTTON) local btn_w = math.max(wy, wn) + 2 * PAD_X local btn_h = math.max(hy, hn) + 2 * PAD_Y local total_w = 2 * btn_w + 24 quit_buttons.yes = { x = (screen_w - total_w) / 2, y = screen_h / 2 + 24, w = btn_w, h = btn_h } quit_buttons.no = { x = (screen_w - total_w) / 2 + btn_w + 24, y = screen_h / 2 + 24, w = btn_w, h = btn_h } input.bind("ui_click", { "mouse_left" }) input.bind("ui_back", { "escape" }) input.bind("ui_confirm", { "enter" }) engine.print(string.format("launcher: init ok, modules=%d", #modules)) end function update(ctx, dt) frames = frames + 1 if os.getenv("SPOREL_CI") == "1" and frames >= CI_FRAMES then engine.exit(0) return end local mx, my = engine.input.get_mouse_pos() if state == STATE_LIST then if input.was_action_pressed("ui_back") then state = fsm.next(state, "esc") elseif input.was_action_pressed("ui_click") then for _, m in ipairs(modules) do if hit(m.rect, mx, my) then engine.switch_module(m.id) return end end end elseif state == STATE_QUIT then if input.was_action_pressed("ui_back") then state = fsm.next(state, "esc") elseif input.was_action_pressed("ui_confirm") then engine.exit(0); return elseif input.was_action_pressed("ui_click") then if hit(quit_buttons.yes, mx, my) then engine.exit(0); return elseif hit(quit_buttons.no, mx, my) then state = fsm.next(state, "click_no") end end end end function render(ctx) local mx, my = engine.input.get_mouse_pos() engine.render.clear_color(COLOR_BG) local tw = engine.render.measure_text(TITLE, FONT_TITLE) engine.render.draw_text(TITLE, (screen_w - tw) / 2, 24, FONT_TITLE, COLOR_TEXT) if #modules == 0 then local ew = engine.render.measure_text(EMPTY_LABEL, FONT_BUTTON) engine.render.draw_text(EMPTY_LABEL, (screen_w - ew) / 2, screen_h / 2, FONT_BUTTON, COLOR_TEXT) else for _, m in ipairs(modules) do local color = hit(m.rect, mx, my) and COLOR_HOVER or COLOR_BTN engine.render.draw_rect(m.rect.x, m.rect.y, m.rect.w, m.rect.h, color) local lw = engine.render.measure_text(m.name, FONT_BUTTON) engine.render.draw_text(m.name, m.rect.x + (m.rect.w - lw) / 2, m.rect.y + PAD_Y, FONT_BUTTON, COLOR_TEXT) end 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 engine.render.draw_rect((screen_w - modal_w) / 2, (screen_h - modal_h) / 2, modal_w, modal_h, COLOR_MODAL) local pw = engine.render.measure_text(QUIT_PROMPT, FONT_BUTTON) engine.render.draw_text(QUIT_PROMPT, (screen_w - pw) / 2, screen_h / 2 - 24, FONT_BUTTON, COLOR_TEXT) for _, key in ipairs({ "yes", "no" }) do local b = quit_buttons[key] local color = hit(b, mx, my) and COLOR_HOVER or COLOR_BTN engine.render.draw_rect(b.x, b.y, b.w, b.h, color) local label = (key == "yes") and YES_LABEL or NO_LABEL local lw = engine.render.measure_text(label, FONT_BUTTON) engine.render.draw_text(label, b.x + (b.w - lw) / 2, b.y + PAD_Y, FONT_BUTTON, COLOR_TEXT) end end end function cleanup(ctx) engine.print("launcher: cleanup") end