feat(launcher): hero carousel with auto-rotate + manual nav

panels/carousel.lua holds per-module state (index, timer, manual pause,
texture cache). Auto-rotate at carousel_interval_seconds (5s default,
frontmatter-overrideable). Click on overlay arrows steps and pauses
auto-rotate for 30s. Max 7 indicator dots, then 'X / N' overlay (L-Q4).
Single-teaser hides the arrows and dots. Zero-teaser falls through to
ctx.default_teaser_texture which L.10 ships.
This commit is contained in:
Calic
2026-06-11 02:26:09 +02:00
parent 72fa4d3135
commit 94b55c42f2
3 changed files with 144 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ end
local list_panel = require_panel("list")
local detail_panel = require_panel("detail")
local carousel = require_panel("carousel")
local STATE_LIST, STATE_QUIT, EXIT = fsm.STATE_LIST, fsm.STATE_QUIT_CONFIRM, fsm.EXIT
local state = STATE_LIST
@@ -255,6 +256,18 @@ function init(ctx)
end
end
-- L.5: per-module carousel state. Reads teaser_images +
-- carousel_interval_seconds from the cached manifest.launcher.md
-- meta. Modules without teasers still get an empty-state for the
-- detail panel to render the fallback texture.
ctx_panels.carousel_mod = carousel
for _, entry in ipairs(ctx_panels.module_entries) do
local mc = ctx_panels.manifest_cache[entry.id]
local paths = (mc and mc.meta and mc.meta.teaser_images) or {}
local interval = (mc and mc.meta and mc.meta.carousel_interval_seconds) or 5.0
ctx_panels.carousel_state[entry.id] = carousel.new_state(entry.id, paths, interval)
end
local wy, hy = engine.render.measure_text(YES_LABEL, FONT_BUTTON)
local wn, hn = engine.render.measure_text(NO_LABEL, FONT_BUTTON)
local qbtn_w = math.max(wy, wn) + 2 * PAD_X
@@ -298,6 +311,13 @@ function update(ctx, dt)
end
end
-- L.5: pump only the selected module's carousel (perf — no point
-- advancing offscreen rotation timers).
if ctx_panels.selected_entry and ctx_panels.selected_entry ~= "news" then
local s = ctx_panels.carousel_state[ctx_panels.selected_entry]
if s then ctx_panels.carousel_mod.update(s, dt) end
end
local mx, my = engine.input.get_mouse_pos()
if state == STATE_LIST then