feat(launcher): add list + detail panel stubs (master-detail L.2)
Splits rendering into panels/list.lua and panels/detail.lua. List implements row hit-test, scroll-state, whole-list bg-tint hook for the dep-conflict UX (L-Q6). Detail renders an empty-state placeholder until L.4-L.7 fill in title, description, deps, carousel, and launch button.
This commit is contained in:
82
panels/list.lua
Normal file
82
panels/list.lua
Normal file
@@ -0,0 +1,82 @@
|
||||
local M = {}
|
||||
|
||||
local PADDING = 16
|
||||
local ROW_HEIGHT = 56
|
||||
local ROW_GAP = 4
|
||||
local COLOR_BG = 0x202020FF
|
||||
local COLOR_ROW = 0x303030FF
|
||||
local COLOR_ROW_HOVER = 0x404040FF
|
||||
local COLOR_ROW_ACTIVE = 0x505080FF
|
||||
local COLOR_TEXT = 0xE0E0E0FF
|
||||
local COLOR_DIM = 0xA0A0A0FF
|
||||
local FONT = 18
|
||||
|
||||
-- Returns the rect rect-table {x,y,w,h} of the row for entry index i.
|
||||
function M.row_rect(ctx, i)
|
||||
local p = ctx.list_panel_rect
|
||||
return {
|
||||
x = p.x + PADDING,
|
||||
y = p.y + PADDING + (i - 1) * (ROW_HEIGHT + ROW_GAP) - ctx.scroll_y,
|
||||
w = p.w - 2 * PADDING,
|
||||
h = ROW_HEIGHT,
|
||||
}
|
||||
end
|
||||
|
||||
-- Pick the display label per spec L-Q8: summary || name || id.
|
||||
local function label_for(entry)
|
||||
return entry.summary or entry.name or entry.id
|
||||
end
|
||||
|
||||
function M.render(ctx)
|
||||
local p = ctx.list_panel_rect
|
||||
-- Apply whole-list bg tint per L-Q6 if conflicts exist.
|
||||
local bg = COLOR_BG
|
||||
if ctx.list_tint == "warn" then bg = 0x4A3A1AFF end
|
||||
if ctx.list_tint == "error" then bg = 0x4A1A1AFF end
|
||||
engine.render.draw_rect(p.x, p.y, p.w, p.h, bg)
|
||||
|
||||
-- News row pinned on top.
|
||||
if ctx.news_entry then
|
||||
local rect = {
|
||||
x = p.x + PADDING, y = p.y + PADDING - ctx.scroll_y,
|
||||
w = p.w - 2 * PADDING, h = ROW_HEIGHT,
|
||||
}
|
||||
local col = (ctx.selected_entry == "news") and COLOR_ROW_ACTIVE or COLOR_ROW
|
||||
engine.render.draw_rect(rect.x, rect.y, rect.w, rect.h, col)
|
||||
engine.render.draw_text(ctx.news_entry.title or "News",
|
||||
rect.x + PADDING, rect.y + PADDING, FONT, COLOR_TEXT)
|
||||
ctx.news_entry.rect = rect
|
||||
end
|
||||
|
||||
for i, entry in ipairs(ctx.module_entries) do
|
||||
local rect = M.row_rect(ctx, i + (ctx.news_entry and 1 or 0))
|
||||
if rect.y + rect.h >= p.y and rect.y <= p.y + p.h then
|
||||
local col = COLOR_ROW
|
||||
if ctx.selected_entry == entry.id then col = COLOR_ROW_ACTIVE end
|
||||
engine.render.draw_rect(rect.x, rect.y, rect.w, rect.h, col)
|
||||
engine.render.draw_text(label_for(entry),
|
||||
rect.x + PADDING, rect.y + PADDING, FONT, COLOR_TEXT)
|
||||
end
|
||||
entry.rect = rect
|
||||
end
|
||||
end
|
||||
|
||||
function M.handle_click(ctx, mx, my)
|
||||
if ctx.news_entry and engine.spatial.aabb_contains_point(ctx.news_entry.rect, mx, my) then
|
||||
ctx.selected_entry = "news"
|
||||
return true
|
||||
end
|
||||
for _, entry in ipairs(ctx.module_entries) do
|
||||
if entry.rect and engine.spatial.aabb_contains_point(entry.rect, mx, my) then
|
||||
ctx.selected_entry = entry.id
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M.handle_scroll(ctx, dy)
|
||||
ctx.scroll_y = math.max(0, ctx.scroll_y - dy * 24)
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user