mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-23 05:58:26 +02:00
maybe smoother mobile scroll on installed mods
This commit is contained in:
+107
-104
@@ -2142,124 +2142,127 @@ local function buildModsPanel(imp, x, y, w, availH, m)
|
|||||||
+ math.floor(8 * m.s)
|
+ math.floor(8 * m.s)
|
||||||
local listTop = cy
|
local listTop = cy
|
||||||
|
|
||||||
-- One continuous list: every row is laid out, the region scroll moves
|
-- One continuous list: derive the rows that can touch the viewport before
|
||||||
-- through all of it, and only rows inside the region's viewport draw --
|
-- entering the loop. Drawing was already culled, but scanning every
|
||||||
-- so the per-frame cost stays bounded by the window, not the list.
|
-- installed row to discover that defeats the point on a large mod library.
|
||||||
local view = imp._tabRegionRect
|
local view = imp._tabRegionRect
|
||||||
local viewTop = view and view.y or listTop
|
local viewTop = view and view.y or listTop
|
||||||
local viewBot = view and (view.y + view.h) or (listTop + availH)
|
local viewBot = view and (view.y + view.h) or (listTop + availH)
|
||||||
for i = 1, #mods do
|
local stride = rowH + gap
|
||||||
|
local first = math.max(1,
|
||||||
|
math.ceil((viewTop - rowH - listTop) / stride) + 1)
|
||||||
|
local last = math.min(#mods,
|
||||||
|
math.floor((viewBot - listTop) / stride) + 1)
|
||||||
|
for i = first, last do
|
||||||
local mod = mods[i]
|
local mod = mods[i]
|
||||||
local ry = listTop + (i - 1) * (rowH + gap)
|
local ry = listTop + (i - 1) * (rowH + gap)
|
||||||
if ry + rowH >= viewTop and ry <= viewBot then
|
local rowKey = rowKeyFor(imp, "mod-row-", mod.id)
|
||||||
local rowKey = rowKeyFor(imp, "mod-row-", mod.id)
|
local isFullyDisabled = true
|
||||||
local isFullyDisabled = true
|
if mod.enabledByVersion then
|
||||||
if mod.enabledByVersion then
|
for _, on in pairs(mod.enabledByVersion) do
|
||||||
for _, on in pairs(mod.enabledByVersion) do
|
if on then isFullyDisabled = false; break end
|
||||||
if on then isFullyDisabled = false; break end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
isFullyDisabled = not mod.enabled
|
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
isFullyDisabled = not mod.enabled
|
||||||
|
end
|
||||||
|
|
||||||
local focused = Kit.focusable(rowKey, x, ry, w, rowH)
|
local focused = Kit.focusable(rowKey, x, ry, w, rowH)
|
||||||
local hot = focused or Kit.hover(x, ry, w, rowH)
|
local hot = focused or Kit.hover(x, ry, w, rowH)
|
||||||
if isFullyDisabled then
|
if isFullyDisabled then
|
||||||
Kit.card(x, ry, w, rowH, hot and "mutedHot" or "muted")
|
Kit.card(x, ry, w, rowH, hot and "mutedHot" or "muted")
|
||||||
else
|
else
|
||||||
Kit.card(x, ry, w, rowH, hot)
|
Kit.card(x, ry, w, rowH, hot)
|
||||||
end
|
end
|
||||||
local pad = math.floor(12 * m.s)
|
local pad = math.floor(12 * m.s)
|
||||||
local px, inner = x + pad, w - 2 * pad
|
local px, inner = x + pad, w - 2 * pad
|
||||||
local ly = ry + math.floor(10 * m.s)
|
local ly = ry + math.floor(10 * m.s)
|
||||||
|
|
||||||
local togGap = math.floor(5 * m.s) + 1
|
local togGap = math.floor(5 * m.s) + 1
|
||||||
local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id)
|
local info = mod.github and mod.github ~= "" and imp:_modUpdateInfo(mod.id)
|
||||||
|
|
||||||
-- These answer separate games, not a single shared install flag. The
|
-- These answer separate games, not a single shared install flag. The
|
||||||
-- importer receives the game id so an experimental confirmation also
|
-- importer receives the game id so an experimental confirmation also
|
||||||
-- applies only to the checkbox the player pressed.
|
-- applies only to the checkbox the player pressed.
|
||||||
local flipped = false
|
local flipped = false
|
||||||
local gamesY = ry + math.floor(8 * m.s) + textH + math.floor(8 * m.s)
|
local gamesY = ry + math.floor(8 * m.s) + textH + math.floor(8 * m.s)
|
||||||
Kit.text("micro", gamesLabel, px,
|
Kit.text("micro", gamesLabel, px,
|
||||||
gamesY + (togH - Kit.textHeight("micro")) / 2, PAL.muted)
|
gamesY + (togH - Kit.textHeight("micro")) / 2, PAL.muted)
|
||||||
local tx = px + Kit.textWidth("micro", gamesLabel) + math.floor(10 * m.s)
|
local tx = px + Kit.textWidth("micro", gamesLabel) + math.floor(10 * m.s)
|
||||||
for _, game in ipairs(GameVersion.ORDER) do
|
for _, game in ipairs(GameVersion.ORDER) do
|
||||||
local togKey = "mod-toggle-" .. mod.id .. "-" .. game
|
local togKey = "mod-toggle-" .. mod.id .. "-" .. game
|
||||||
if modGameCheckbox(tx, gamesY, togH,
|
if modGameCheckbox(tx, gamesY, togH,
|
||||||
mod.enabledByVersion and mod.enabledByVersion[game] == true,
|
mod.enabledByVersion and mod.enabledByVersion[game] == true,
|
||||||
game, togKey, not safeMode) then
|
game, togKey, not safeMode) then
|
||||||
local version = game
|
local version = game
|
||||||
queueAction(imp, togKey, function() imp:_toggleMod(mod.id, nil, version) end)
|
queueAction(imp, togKey, function() imp:_toggleMod(mod.id, nil, version) end)
|
||||||
flipped = true
|
flipped = true
|
||||||
end
|
|
||||||
tx = tx + togH + togGap
|
|
||||||
end
|
end
|
||||||
-- The checkboxes sit inside the row's rect, so their press also passes the
|
tx = tx + togH + togGap
|
||||||
-- row hit test; `flipped` gates the row action to everywhere else.
|
end
|
||||||
if not flipped
|
-- The checkboxes sit inside the row's rect, so their press also passes the
|
||||||
and (Kit.press(x, ry, w, rowH) or Kit._activateId == rowKey) then
|
-- row hit test; `flipped` gates the row action to everywhere else.
|
||||||
local id = mod.id
|
if not flipped
|
||||||
queueAction(imp, rowKey, function() imp._modActions = id end)
|
and (Kit.press(x, ry, w, rowH) or Kit._activateId == rowKey) then
|
||||||
end
|
local id = mod.id
|
||||||
local textW = inner
|
queueAction(imp, rowKey, function() imp._modActions = id end)
|
||||||
|
end
|
||||||
|
local textW = inner
|
||||||
|
|
||||||
local badgeW = Kit.textWidth("micro", mod.badge) + math.floor(12 * m.s)
|
local badgeW = Kit.textWidth("micro", mod.badge) + math.floor(12 * m.s)
|
||||||
-- the games the mod is for, beside its category: the same chip the
|
-- the games the mod is for, beside its category: the same chip the
|
||||||
-- in-game manager shows (src/mods/ModTargets.lua)
|
-- in-game manager shows (src/mods/ModTargets.lua)
|
||||||
local gamesW = mod.targets
|
local gamesW = mod.targets
|
||||||
and Kit.textWidth("micro", mod.targets) + math.floor(12 * m.s) or 0
|
and Kit.textWidth("micro", mod.targets) + math.floor(12 * m.s) or 0
|
||||||
local nameShown = Kit.ellipsize("button", mod.name,
|
local nameShown = Kit.ellipsize("button", mod.name,
|
||||||
textW - badgeW - gamesW - math.floor(12 * m.s))
|
textW - badgeW - gamesW - math.floor(12 * m.s))
|
||||||
local headingCol = isFullyDisabled and PAL.muted or PAL.heading
|
local headingCol = isFullyDisabled and PAL.muted or PAL.heading
|
||||||
Kit.text("button", nameShown, px, ly, headingCol)
|
Kit.text("button", nameShown, px, ly, headingCol)
|
||||||
local tagX = px + Kit.textWidth("button", nameShown) + math.floor(8 * m.s)
|
local tagX = px + Kit.textWidth("button", nameShown) + math.floor(8 * m.s)
|
||||||
Kit.tag(tagX, ly, badgeW, Kit.textHeight("button"), mod.badge,
|
Kit.tag(tagX, ly, badgeW, Kit.textHeight("button"), mod.badge,
|
||||||
mod.experimental and PAL.yellow or PAL.muted)
|
mod.experimental and PAL.yellow or PAL.muted)
|
||||||
if mod.targets then
|
if mod.targets then
|
||||||
Kit.tag(tagX + badgeW + math.floor(4 * m.s), ly, gamesW,
|
Kit.tag(tagX + badgeW + math.floor(4 * m.s), ly, gamesW,
|
||||||
Kit.textHeight("button"), mod.targets,
|
Kit.textHeight("button"), mod.targets,
|
||||||
mod.targetsHere == false and PAL.steel or PAL.blue)
|
mod.targetsHere == false and PAL.steel or PAL.blue)
|
||||||
end
|
end
|
||||||
ly = ly + Kit.textHeight("button") + math.floor(4 * m.s)
|
ly = ly + Kit.textHeight("button") + math.floor(4 * m.s)
|
||||||
|
|
||||||
-- version + status + update state
|
-- version + status + update state
|
||||||
local statusText, statusCol = modStatusColor(mod.status)
|
local statusText, statusCol = modStatusColor(mod.status)
|
||||||
local line = "v" .. tostring(mod.version or "?") .. " " .. statusText
|
local line = "v" .. tostring(mod.version or "?") .. " " .. statusText
|
||||||
Kit.text("small", line, px, ly, statusCol)
|
Kit.text("small", line, px, ly, statusCol)
|
||||||
local lx = px + Kit.textWidth("small", line) + math.floor(12 * m.s)
|
local lx = px + Kit.textWidth("small", line) + math.floor(12 * m.s)
|
||||||
if imp:_modInfoPending(mod.id) then
|
if imp:_modInfoPending(mod.id) then
|
||||||
-- An inline spinner, because this row's release check is genuinely in
|
-- An inline spinner, because this row's release check is genuinely in
|
||||||
-- flight -- the list stays usable while it resolves.
|
-- flight -- the list stays usable while it resolves.
|
||||||
Loader.dot(lx, ly, Kit.textHeight("small"))
|
Loader.dot(lx, ly, Kit.textHeight("small"))
|
||||||
Kit.text("small", Strings("Checking..."),
|
Kit.text("small", Strings("Checking..."),
|
||||||
lx + Kit.textHeight("small") + math.floor(6 * m.s), ly, PAL.muted)
|
lx + Kit.textHeight("small") + math.floor(6 * m.s), ly, PAL.muted)
|
||||||
elseif info and info.status == "available" then
|
elseif info and info.status == "available" then
|
||||||
Kit.text("small", Strings("v%s available", tostring(info.latest)),
|
Kit.text("small", Strings("v%s available", tostring(info.latest)),
|
||||||
lx, ly, PAL.yellow)
|
lx, ly, PAL.yellow)
|
||||||
elseif info and info.status == "current" then
|
elseif info and info.status == "current" then
|
||||||
Kit.text("small", Strings("up to date"), lx, ly, PAL.muted)
|
Kit.text("small", Strings("up to date"), lx, ly, PAL.muted)
|
||||||
elseif info and info.status == "error" then
|
elseif info and info.status == "error" then
|
||||||
Kit.text("small", Strings("check failed"), lx, ly, PAL.red)
|
Kit.text("small", Strings("check failed"), lx, ly, PAL.red)
|
||||||
end
|
end
|
||||||
ly = ly + Kit.textHeight("small") + math.floor(2 * m.s)
|
ly = ly + Kit.textHeight("small") + math.floor(2 * m.s)
|
||||||
|
|
||||||
-- one line of description, or the download stats when we have them
|
-- one line of description, or the download stats when we have them
|
||||||
-- (download count in green so popularity reads at a glance)
|
-- (download count in green so popularity reads at a glance)
|
||||||
if info and info.downloads then
|
if info and info.downloads then
|
||||||
local d = info.dates
|
local d = info.dates
|
||||||
local dl = ModUpdate.downloadsLine(info.downloads.total)
|
local dl = ModUpdate.downloadsLine(info.downloads.total)
|
||||||
local dates = ModUpdate.datesLine(d and d.first, d and d.latest)
|
local dates = ModUpdate.datesLine(d and d.first, d and d.latest)
|
||||||
local segs = {}
|
local segs = {}
|
||||||
if dl then segs[#segs + 1] = { dl, PAL.green } end
|
if dl then segs[#segs + 1] = { dl, PAL.green } end
|
||||||
if dates then
|
if dates then
|
||||||
segs[#segs + 1] = { (dl and " - " or "") .. dates, PAL.detail }
|
segs[#segs + 1] = { (dl and " - " or "") .. dates, PAL.detail }
|
||||||
end
|
|
||||||
segLine("small", segs, px, ly, textW)
|
|
||||||
elseif (mod.description or "") ~= "" then
|
|
||||||
Kit.text("small", Kit.ellipsize("small", mod.description, textW),
|
|
||||||
px, ly, PAL.detail)
|
|
||||||
end
|
end
|
||||||
|
segLine("small", segs, px, ly, textW)
|
||||||
|
elseif (mod.description or "") ~= "" then
|
||||||
|
Kit.text("small", Kit.ellipsize("small", mod.description, textW),
|
||||||
|
px, ly, PAL.detail)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4154,7 +4154,6 @@ function RomImporter:_refreshMods()
|
|||||||
end
|
end
|
||||||
self.mods = kept
|
self.mods = kept
|
||||||
end
|
end
|
||||||
self:_syncModUpdateInfo(false)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Point the MODS panel at one game (or nil for all of them) and relist, so
|
-- Point the MODS panel at one game (or nil for all of them) and relist, so
|
||||||
@@ -4168,15 +4167,12 @@ function RomImporter:_ensureMods()
|
|||||||
if not self.mods then self:_refreshMods() end
|
if not self.mods then self:_refreshMods() end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Resolve cached (or freshly fetched) GitHub status for every mod that
|
-- Resolve GitHub status for every installed mod that declares a github field.
|
||||||
-- declares a github field. force=true bypasses the 6h cache on every repo.
|
-- This is deliberately opt-in: only the explicit "Check for updates" action
|
||||||
-- Results live on self.modUpdateInfo[id] = { status, latest, best, releases }.
|
-- calls it. Opening, scrolling, toggling, or relisting the MODS tab must not
|
||||||
-- ASYNC (was synchronous). This runs on every _refreshMods -- boot, and any
|
-- create a burst of release work behind the list. force=true bypasses the 6h
|
||||||
-- toggle or install -- and used to make one blocking curl call per mod with a
|
-- cache on every repo. Results live on self.modUpdateInfo[id] = {
|
||||||
-- github field, in a loop, on the render thread. A handful of mods was a
|
-- status, latest, best, releases } and resolve asynchronously across frames.
|
||||||
-- multi-second freeze of the whole launcher. Now each mod gets a handle and
|
|
||||||
-- they resolve together across later frames; a mod whose cache is still fresh
|
|
||||||
-- resolves on the first pump with no network at all.
|
|
||||||
function RomImporter:_syncModUpdateInfo(force)
|
function RomImporter:_syncModUpdateInfo(force)
|
||||||
local ModUpdate = require("src.mods.ModUpdate")
|
local ModUpdate = require("src.mods.ModUpdate")
|
||||||
self.modUpdateInfo = self.modUpdateInfo or {}
|
self.modUpdateInfo = self.modUpdateInfo or {}
|
||||||
|
|||||||
@@ -388,6 +388,35 @@ mdown = false
|
|||||||
LauncherView.update(gameImp, 0.016)
|
LauncherView.update(gameImp, 0.016)
|
||||||
love.mouse.isDown = nil
|
love.mouse.isDown = nil
|
||||||
|
|
||||||
|
-- Opening or relisting MODS is display work only. Release checks are an
|
||||||
|
-- explicit action, otherwise each installed GitHub mod creates background
|
||||||
|
-- work exactly when the player starts scrolling the panel.
|
||||||
|
do
|
||||||
|
local savedMods = package.loaded["src.mods.LauncherMods"]
|
||||||
|
local savedSaveData = package.loaded["src.core.SaveData"]
|
||||||
|
local listCalls, updateCalls = 0, 0
|
||||||
|
package.loaded["src.mods.LauncherMods"] = {
|
||||||
|
list = function()
|
||||||
|
listCalls = listCalls + 1
|
||||||
|
return { { id = "test", github = "owner/repo" } }
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
package.loaded["src.core.SaveData"] = {
|
||||||
|
loadOptions = function() return {} end,
|
||||||
|
isSafeMode = function() return false end,
|
||||||
|
}
|
||||||
|
local refreshImp = setmetatable({
|
||||||
|
modStraysChecked = true,
|
||||||
|
_syncModUpdateInfo = function() updateCalls = updateCalls + 1 end,
|
||||||
|
}, RomImporter)
|
||||||
|
refreshImp:_refreshMods()
|
||||||
|
eq(listCalls, 1, "relisting MODS still obtains its installed rows")
|
||||||
|
eq(updateCalls, 0,
|
||||||
|
"relisting MODS does not start release checks without the explicit button")
|
||||||
|
package.loaded["src.mods.LauncherMods"] = savedMods
|
||||||
|
package.loaded["src.core.SaveData"] = savedSaveData
|
||||||
|
end
|
||||||
|
|
||||||
local function read(path)
|
local function read(path)
|
||||||
local f = assert(io.open(path, "r"))
|
local f = assert(io.open(path, "r"))
|
||||||
local src = f:read("*a")
|
local src = f:read("*a")
|
||||||
|
|||||||
Reference in New Issue
Block a user