This commit is contained in:
bryanthaboi
2026-08-15 10:44:20 -04:00
parent 43cbc554c3
commit 5198b35945
18 changed files with 1524 additions and 185 deletions
+29 -6
View File
@@ -132,9 +132,11 @@ local UI_SCALE = 1.3
function Kit.layout(width, height)
local s = Theme.clamp(math.min(width / 640, height / 768), 0.9, 1.6) * UI_SCALE
local key = ("%dx%d"):format(math.floor(width), math.floor(height))
if Kit._fontKey ~= key then
Kit._fontKey = key
-- Two numbers, not a formatted key: this runs once per frame and the
-- string:format allocated on every one of them.
local kw, kh = math.floor(width), math.floor(height)
if Kit._fontW ~= kw or Kit._fontH ~= kh then
Kit._fontW, Kit._fontH = kw, kh
Kit.fonts = Theme.fonts(s)
clearCaches() -- every cached Text/width belongs to the old font set
end
@@ -857,6 +859,8 @@ end
-- never silently truncated. This is the ONLY way the launcher moves through
-- a long list: no scrollbars, no momentum, bounded row count per frame.
-- Returns the new page (1-based) and the row height consumed.
local pagerLabels = {}
function Kit.pager(x, y, w, page, total, perPage, idPrefix)
local h = math.max(Kit.tapMin(), 30 * Kit.scale)
local bw = 74 * Kit.scale
@@ -876,7 +880,19 @@ function Kit.pager(x, y, w, page, total, perPage, idPrefix)
local first = total > 0 and ((page - 1) * perPage + 1) or 0
local last = math.min(total, page * perPage)
local label = ("%d-%d of %d (page %d/%d)"):format(first, last, total, page, pages)
-- One memo per pager id. The counts only change when the user pages or the
-- list does; formatting them every frame minted a new string that then
-- missed the width / ellipsis / Text caches by content.
local memo = pagerLabels[idPrefix]
if not memo then memo = {}; pagerLabels[idPrefix] = memo end
if memo.first ~= first or memo.last ~= last or memo.total ~= total
or memo.page ~= page or memo.pages ~= pages then
memo.first, memo.last, memo.total = first, last, total
memo.page, memo.pages = page, pages
memo.label = ("%d-%d of %d (page %d/%d)")
:format(first, last, total, page, pages)
end
local label = memo.label
local labelX = x + 2 * bw + 2 * gap + gap
Kit.text("mono", Kit.ellipsize("mono", label, math.max(0, x + w - labelX)),
labelX, y + (h - Kit.textHeight("mono")) / 2, PAL.caption)
@@ -942,7 +958,10 @@ end
-- region can never unclip its parent. The tracked rect also bounds Kit.hit,
-- so a widget clipped out of view is inert instead of taking taps aimed at
-- whatever is drawn where it left.
-- The stack rects are pooled by depth and fully overwritten on every push,
-- so a frame that clips a dozen lists allocates nothing.
local clipStack = {}
local clipPool = {}
local function applyClip(rect)
Kit._clipRect = rect
@@ -967,8 +986,12 @@ function Kit.pushClip(x, y, w, h)
x2 = math.min(x2, prev.x + prev.w)
y2 = math.min(y2, prev.y + prev.h)
end
local rect = { x = x, y = y, w = math.max(0, x2 - x), h = math.max(0, y2 - y) }
clipStack[#clipStack + 1] = rect
local n = #clipStack + 1
local rect = clipPool[n]
if not rect then rect = {}; clipPool[n] = rect end
rect.x, rect.y = x, y
rect.w, rect.h = math.max(0, x2 - x), math.max(0, y2 - y)
clipStack[n] = rect
applyClip(rect)
end
+29 -15
View File
@@ -29,6 +29,15 @@ Layout.BP = {
-- Build the frame's metrics. `maxAppW` caps the content column on an
-- ultrawide monitor so the UI stays a readable measure instead of stretching.
-- One metrics table, reused. Every field is a pure function of the window
-- size, the safe area and maxAppW, so the table only has to be rebuilt when
-- one of those changes; the launcher asked for a fresh one 60 times a second
-- and threw all of them away. Callers must treat `m` as read-only (nothing
-- writes to it today) -- a caller that needs a shifted field should save,
-- assign and restore it around the call, not wrap `m` in a proxy.
local M = {}
local lastW, lastH, lastOx, lastOy, lastSw, lastSh, lastMax
function Layout.metrics(maxAppW)
local W, H = 0, 0
if love and love.graphics and love.graphics.getDimensions then
@@ -36,23 +45,28 @@ function Layout.metrics(maxAppW)
end
local ox, oy, sw, sh = SafeArea.rect()
local s = Kit.layout(sw, sh)
if W == lastW and H == lastH and ox == lastOx and oy == lastOy
and sw == lastSw and sh == lastSh and maxAppW == lastMax then
return M
end
lastW, lastH, lastOx, lastOy = W, H, ox, oy
lastSw, lastSh, lastMax = sw, sh, maxAppW
local appW = math.min(sw, (maxAppW or 1200) * s)
local m = {
W = W, H = H, s = s,
x = math.floor(ox + (sw - appW) / 2),
top = math.floor(oy),
w = math.floor(appW),
h = math.floor(sh),
pad = math.floor(Theme.clamp(appW * 0.03, 10, 24)),
gap = math.floor(12 * s),
colGap = math.floor(16 * s),
rowH = math.max(Kit.tapMin(), math.floor(44 * s)),
btnH = math.max(Kit.tapMin(), math.floor(38 * s)),
chip = math.max(Kit.tapMin(), math.floor(40 * s)),
railH = math.max(3, math.floor(4 * s)),
logoH = math.floor(Theme.clamp(sh * 0.10, 36, 84)),
}
local m = M
m.W, m.H, m.s = W, H, s
m.x = math.floor(ox + (sw - appW) / 2)
m.top = math.floor(oy)
m.w = math.floor(appW)
m.h = math.floor(sh)
m.pad = math.floor(Theme.clamp(appW * 0.03, 10, 24))
m.gap = math.floor(12 * s)
m.colGap = math.floor(16 * s)
m.rowH = math.max(Kit.tapMin(), math.floor(44 * s))
m.btnH = math.max(Kit.tapMin(), math.floor(38 * s))
m.chip = math.max(Kit.tapMin(), math.floor(40 * s))
m.railH = math.max(3, math.floor(4 * s))
m.logoH = math.floor(Theme.clamp(sh * 0.10, 36, 84))
m.cols = (appW >= Layout.BP.threeCol * s and 3)
or (appW >= Layout.BP.twoCol * s and 2)
or 1