From f7f32d27860efc3ea306772ddaecba217dc9caa9 Mon Sep 17 00:00:00 2001 From: bryanthaboi Date: Thu, 13 Aug 2026 16:29:59 -0400 Subject: [PATCH] scroll list --- src/import/LauncherView.lua | 57 +++++++++++++++++++++++++++++-------- src/import/RomImporter.lua | 4 +-- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/import/LauncherView.lua b/src/import/LauncherView.lua index 706af73b..c7dedf52 100644 --- a/src/import/LauncherView.lua +++ b/src/import/LauncherView.lua @@ -14,10 +14,9 @@ -- height, and flex-shrink compressing text until it overlapped. -- -- THE RULES THIS FILE FOLLOWS: --- * NO SCROLLING. Every list paginates (Kit.pager). Rows per page come --- from the real viewport height, so a tall window shows more and a phone --- shows fewer -- but a page's row count is bounded either way, which is --- what makes a 500-mod index cost the same as a 10-mod one. +-- * Lists paginate (Kit.pager). The installed-mod list also scrolls inside +-- its viewport, so each of its pages can hold at least ten entries without +-- requiring a tall window. Pages still bound how many mod rows we visit. -- * Every click handler only QUEUES work (imp._uiActions); update() drains -- the queue, so an action that tears the view down (Play, Edit save) -- never runs inside the frame that dispatched it. @@ -46,9 +45,17 @@ local COMMUNITY_URL = "https://bois.icu" local ACT_DEDUP = 0.35 -- Finger travel past this (px) is a drag, not a tap. local TAP_SLOP2 = 16 * 16 +-- Installed mods should not turn into a one- or two-item pager on a compact +-- display. Keep a useful page size, then let the list viewport scroll. +local MIN_MODS_PER_PAGE = 10 local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end +local function inRect(rect, x, y) + return rect ~= nil and x >= rect.x and x <= rect.x + rect.w + and y >= rect.y and y <= rect.y + rect.h +end + -- ------------------------------------------------------------- lifecycle local function ensureState(imp) @@ -146,7 +153,10 @@ end function LauncherView.touchpressed(imp, id, x, y) if not imp._flex then return end imp._touchAt = imp._touchAt or {} - imp._touchAt[tostring(id)] = { x = x, y = y } + imp._touchAt[tostring(id)] = { + x = x, y = y, + modsList = (imp._modScrollMax or 0) > 0 and inRect(imp._modListRect, x, y), + } end function LauncherView.touchmoved(imp, id, x, y) @@ -157,8 +167,14 @@ function LauncherView.touchmoved(imp, id, x, y) if ddx * ddx + ddy * ddy > TAP_SLOP2 then start.dragged = true end - -- Short-window mode: a vertical drag scrolls the page (draw() clamps). - if start.dragged and (imp._pageScrollMax or 0) > 0 then + -- A drag that began in the installed-mod viewport scrolls that page's + -- rows. Its pager remains available for moving to the next ten-plus + -- entries; a drag elsewhere keeps the normal short-window page scroll. + if start.dragged and start.modsList then + local last = start.lastY or start.y + imp.modScroll = clamp((imp.modScroll or 0) - (y - last), 0, + imp._modScrollMax or 0) + elseif start.dragged and (imp._pageScrollMax or 0) > 0 then local last = start.lastY or start.y imp._pageScroll = (imp._pageScroll or 0) - (y - last) end @@ -1441,6 +1457,7 @@ local function buildModsPanel(imp, x, y, w, availH, m) cy = cy + buildModScopeRow(imp, x, cy, w, m) if #mods == 0 then + imp.modScroll, imp._modScrollMax, imp._modListRect = 0, 0, nil Kit.emptyBox(x, cy, w, math.floor(110 * m.s), imp:_modsEmptyHint()) return end @@ -1492,16 +1509,30 @@ local function buildModsPanel(imp, x, y, w, availH, m) + math.floor(8 * m.s) local pagerH = math.max(Kit.tapMin(), math.floor(30 * m.s)) local listH = availH - (cy - y) - pagerH - gap - local perPage = Kit.rowsThatFit(listH, rowH, gap, 1, 20) + local perPage = Kit.rowsThatFit(listH, rowH, gap, MIN_MODS_PER_PAGE, 20) local first, last, cur, pages = Kit.pageBounds(page(imp, "mods"), #mods, perPage) setPage(imp, "mods", cur) local listTop = cy - setPage(imp, "mods", - Kit.wheelPage(x, listTop, w, listH, cur, #mods, perPage)) + local shown = math.max(0, last - first + 1) + local contentH = shown * rowH + math.max(0, shown - 1) * gap + local scrollMax = math.max(0, contentH - listH) + local scroll = clamp(imp.modScroll or 0, 0, scrollMax) + imp._modListRect = { x = x, y = listTop, w = w, h = listH } + imp._modScrollMax = scrollMax + if scrollMax > 0 and (Kit.wheelY or 0) ~= 0 and Kit.hit(x, listTop, w, listH) then + scroll = clamp(scroll - Kit.wheelY * math.floor(48 * m.s), 0, scrollMax) + Kit.wheelY = 0 + elseif scrollMax == 0 then + local wheelPage = Kit.wheelPage(x, listTop, w, listH, cur, #mods, perPage) + if wheelPage ~= cur then imp.modScroll = 0 end + setPage(imp, "mods", wheelPage) + end + imp.modScroll = scroll + Kit.pushClip(x, listTop, w, listH) for i = first, last do local mod = mods[i] - local ry = listTop + (i - first) * (rowH + gap) + local ry = listTop + (i - first) * (rowH + gap) - scroll local rowKey = "mod-row-" .. mod.id -- The whole row is the control: it opens the per-mod actions popup -- (update / versions / delete moved there). Only the enable toggle @@ -1600,9 +1631,11 @@ local function buildModsPanel(imp, x, y, w, availH, m) px, ly, PAL.detail) end end + Kit.popClip() - local pagerY = listTop + (last - first + 1) * (rowH + gap) + local pagerY = listTop + listH + gap local newPage = Kit.pager(x, pagerY, w, cur, #mods, perPage, "mods") + if newPage ~= cur then imp.modScroll = 0 end setPage(imp, "mods", newPage) end diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index 15fa5c25..24e894ef 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -1228,8 +1228,8 @@ function RomImporter.new(onComplete, opts) saveNotice = {}, -- MODS panel state (pass 3): mods is the cached LauncherMods.list() array -- (refreshed lazily on first draw and after any toggle/install/delete); - -- modScroll is the list scroll offset (px, clamped in draw); modNotice is - -- the last install/delete result { ok, text } shown as a line above the list. + -- modScroll is the current paged list's inner scroll offset (px, clamped + -- in draw); modNotice is the last install/delete result { ok, text }. mods = nil, modScroll = 0, modNotice = nil, -- Which game the MODS panel is answering for (a GameVersion id, nil = -- every game). Rows resolve their enable-state and their "runs here"