mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-02 23:55:22 -04:00
226 lines
6.0 KiB
Lua
226 lines
6.0 KiB
Lua
-- Launcher-style navigation: one dropdown opens a modal list over the page.
|
|
-- The overlay owns clicks, scrolling and dismissal until an option is chosen.
|
|
local Theme = require("Theme")
|
|
local SafeArea = require("src.core.SafeArea")
|
|
local Motion = require("Motion")
|
|
local Kit = require("Kit")
|
|
local PAL = Theme.PAL
|
|
local Chooser = {}
|
|
|
|
local function id(option)
|
|
return option.id or option[1]
|
|
end
|
|
|
|
local function label(option)
|
|
local text = option.label or option[2]
|
|
return (option.errors or 0) > 0 and (text .. " (" .. option.errors .. ")") or text
|
|
end
|
|
|
|
function Chooser.close(S)
|
|
S.navPopup = nil
|
|
Kit.blur()
|
|
Kit._drag = nil
|
|
Kit.mouseClicked, Kit.wheelY = false, 0
|
|
end
|
|
|
|
function Chooser.open(S, key, title, options, after)
|
|
if Motion.active() then
|
|
return false
|
|
end
|
|
local index = 1
|
|
for i, option in ipairs(options) do
|
|
if id(option) == S[key] then
|
|
index = i
|
|
end
|
|
end
|
|
Kit.blur()
|
|
Kit._drag = nil
|
|
S.navPopup = {
|
|
key = key,
|
|
title = title,
|
|
options = options,
|
|
index = index,
|
|
scroll = 0,
|
|
reveal = true,
|
|
opened = true,
|
|
after = after,
|
|
}
|
|
Kit.blockClicks = true
|
|
return true
|
|
end
|
|
|
|
function Chooser.navigation(S, kit, key, title, options, x, y, w, h, after)
|
|
local current = options[1]
|
|
for _, option in ipairs(options) do
|
|
if id(option) == S[key] then
|
|
current = option
|
|
end
|
|
end
|
|
local text = label(current)
|
|
local icon = current.icon or kit.navigationIcon(text)
|
|
-- Compact toolbars keep a readable current label beside the dropdown icon.
|
|
if w < 160 * kit.scale then
|
|
icon = nil
|
|
end
|
|
if
|
|
kit.button(x, y, w, h, text, {
|
|
id = "navigate-" .. key,
|
|
invalid = (current.errors or 0) > 0,
|
|
face = "invert",
|
|
font = "small",
|
|
icon = icon,
|
|
align = "left",
|
|
trailingIcon = S.navPopup and S.navPopup.key == key and "chevron-up" or "chevron-down",
|
|
})
|
|
then
|
|
return Chooser.open(S, key, title, options, after)
|
|
end
|
|
return false
|
|
end
|
|
|
|
function Chooser.actions(S, kit, key, title, options, x, y, w, h)
|
|
if
|
|
kit.button(x, y, w, h, title, {
|
|
id = "navigate-" .. key,
|
|
face = "invert",
|
|
font = "small",
|
|
icon = "sliders-horizontal",
|
|
trailingIcon = "chevron-down",
|
|
})
|
|
then
|
|
if Chooser.open(S, key, title, options) then
|
|
S.navPopup.mode = "actions"
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function active(S, popup, option)
|
|
if popup.mode == "actions" then
|
|
return option.active == true
|
|
end
|
|
return S[popup.key] == id(option)
|
|
end
|
|
|
|
local function choose(S, index)
|
|
local popup = S.navPopup
|
|
if not popup then
|
|
return
|
|
end
|
|
local option = popup.options[index]
|
|
if not option then
|
|
return
|
|
end
|
|
if popup.mode == "actions" then
|
|
Chooser.close(S)
|
|
option.fn(S)
|
|
return
|
|
end
|
|
local value, previous = id(option), S[popup.key]
|
|
local oldIndex = 1
|
|
for i, entry in ipairs(popup.options) do
|
|
if id(entry) == previous then
|
|
oldIndex = i
|
|
end
|
|
end
|
|
Chooser.close(S)
|
|
if Motion.change(S, popup.key, value, index >= oldIndex and 1 or -1) and popup.after then
|
|
popup.after(value, index)
|
|
end
|
|
end
|
|
|
|
function Chooser.keypressed(S, key)
|
|
local popup = S.navPopup
|
|
if not popup then
|
|
return false
|
|
end
|
|
if key == "escape" then
|
|
Chooser.close(S)
|
|
elseif key == "return" or key == "kpenter" then
|
|
choose(S, popup.index)
|
|
elseif key == "up" or key == "down" or key == "home" or key == "end" then
|
|
if key == "home" then
|
|
popup.index = 1
|
|
elseif key == "end" then
|
|
popup.index = #popup.options
|
|
else
|
|
popup.index = (popup.index - 1 + (key == "up" and -1 or 1)) % #popup.options + 1
|
|
end
|
|
popup.reveal = true
|
|
popup.keyboard = true
|
|
end
|
|
return true
|
|
end
|
|
|
|
function Chooser.draw(S, kit, width, height)
|
|
local popup = S.navPopup
|
|
if not popup then
|
|
return
|
|
end
|
|
kit.resetClip()
|
|
kit.blockClicks = popup.opened == true
|
|
popup.opened = nil
|
|
local s, row, gap = kit.scale, math.ceil(kit.controlH()), math.floor(8 * kit.scale)
|
|
local ox, oy, sw, sh = SafeArea.rect()
|
|
local gutter, pad = math.max(8, math.floor(12 * s)), math.floor(14 * s)
|
|
local w = math.floor(math.min(360 * s, sw - 2 * gutter))
|
|
local listH = #popup.options * (row + gap) - gap
|
|
local h = math.min(sh - 2 * gutter, 2 * pad + row + gap + listH)
|
|
local x, y = math.floor(ox + (sw - w) / 2), math.floor(oy + (sh - h) / 2)
|
|
popup.rect = { x = x, y = y, w = w, h = h }
|
|
Theme.col(PAL.bg, 0.72)
|
|
love.graphics.rectangle("fill", 0, 0, width, height)
|
|
if kit.press(0, 0, width, height) and not kit.hit(x, y, w, h) then
|
|
Chooser.close(S)
|
|
return
|
|
end
|
|
kit.card(x, y, w, h)
|
|
local cx, cy, inner = x + pad, y + pad, w - 2 * pad
|
|
kit.text(
|
|
"button",
|
|
kit.ellipsize("button", popup.title, inner - row - gap),
|
|
cx,
|
|
cy + (row - kit.textHeight("button")) / 2,
|
|
PAL.heading
|
|
)
|
|
if kit.iconButton(cx + inner - row, cy, row, row, "x", "Close chooser") then
|
|
Chooser.close(S)
|
|
return
|
|
end
|
|
cy = cy + row + gap
|
|
local bodyH = math.max(0, h - 2 * pad - row - gap)
|
|
local extent = math.max(0, listH - bodyH)
|
|
if popup.reveal then
|
|
local top = (popup.index - 1) * (row + gap)
|
|
popup.scroll = Theme.clamp(math.max(top + row - bodyH, math.min(popup.scroll, top)), 0, extent)
|
|
popup.reveal = nil
|
|
end
|
|
popup.scroll = kit.scrollPixels(cx, cy, inner, bodyH, popup.scroll, listH)
|
|
local contentW = inner - (extent > 0 and 8 * s or 0)
|
|
kit.pushClip(cx, cy, inner, bodyH)
|
|
for i, option in ipairs(popup.options) do
|
|
local text = label(option)
|
|
if
|
|
kit.button(cx, cy + (i - 1) * (row + gap) - popup.scroll, contentW, row, text, {
|
|
face = "selection",
|
|
invalid = (option.errors or 0) > 0,
|
|
active = active(S, popup, option),
|
|
font = "small",
|
|
align = "left",
|
|
icon = option.icon or kit.navigationIcon(text),
|
|
trailingIcon = active(S, popup, option) and "check" or nil,
|
|
ring = popup.keyboard and popup.index == i or nil,
|
|
})
|
|
then
|
|
kit.popClip()
|
|
choose(S, i)
|
|
return
|
|
end
|
|
end
|
|
kit.popClip()
|
|
kit.scrollbar(cx, cy, inner, bodyH, popup.scroll, listH, bodyH)
|
|
end
|
|
|
|
return Chooser
|