mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 03:02:39 +02:00
Merge branch 'dev' into feat/switch-nx
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+77
-10
@@ -54,10 +54,11 @@ local function C(name, a)
|
||||
return rgba(c[1], c[2], c[3], a)
|
||||
end
|
||||
|
||||
-- Every element in this view refuses to flex-shrink: overflow is always a
|
||||
-- scroll container's job here, and the engine otherwise compresses
|
||||
-- Non-scroll elements refuse to flex-shrink: the engine otherwise compresses
|
||||
-- auto-height children inside height-constrained columns until their text
|
||||
-- overlaps (the portrait single-column layout was the visible case).
|
||||
-- overlaps (the portrait single-column layout was the visible case). Scroll
|
||||
-- regions are the opposite — they MUST shrink to the viewport, or their
|
||||
-- height grows with content, maxScrollY stays 0, and drag/wheel do nothing.
|
||||
-- horizontal padding of a props table, for content-width bookkeeping
|
||||
local function propsPadH(p)
|
||||
local pad = p.padding
|
||||
@@ -68,8 +69,18 @@ local function propsPadH(p)
|
||||
return 0
|
||||
end
|
||||
|
||||
local function isScrollOverflow(props)
|
||||
local o = props.overflowY or props.overflowX or props.overflow
|
||||
return o == "scroll" or o == "auto"
|
||||
end
|
||||
|
||||
local function mk(props)
|
||||
if props.flexShrink == nil then props.flexShrink = 0 end
|
||||
if props.flexShrink == nil then
|
||||
props.flexShrink = isScrollOverflow(props) and 1 or 0
|
||||
end
|
||||
if isScrollOverflow(props) and props.minHeight == nil then
|
||||
props.minHeight = 0
|
||||
end
|
||||
-- Resolve "100%" here, against the parent's CONTENT width: the engine
|
||||
-- resolves a percentage against the parent's border box and ignores its
|
||||
-- padding, so every percent child of a padded container overflowed to the
|
||||
@@ -180,11 +191,48 @@ function LauncherView.update(imp, dt)
|
||||
end
|
||||
end
|
||||
|
||||
-- One dedup window covers a touch release plus the mouse click SDL
|
||||
-- synthesizes for the same tap.
|
||||
local ACT_DEDUP = 0.35
|
||||
-- Finger travel past this (px) is a scroll drag, not a tap — so dragging a
|
||||
-- list row does not also fire that row's button.
|
||||
local TAP_SLOP2 = 16 * 16
|
||||
|
||||
function LauncherView.wheelmoved(imp, dx, dy)
|
||||
if not imp._flex then return end
|
||||
pcall(FlexLove.wheelmoved, dx, dy)
|
||||
end
|
||||
|
||||
-- Touch drag scroll: FlexLove's ScrollManager only moves when these are
|
||||
-- hooked. Clicks still come from EventHandler's love.touch / mouse polling;
|
||||
-- the view's action dedupe covers a tap that also synthesizes a mouse click,
|
||||
-- and a drag past TAP_SLOP suppresses the click that would otherwise fire
|
||||
-- on the row under the finger.
|
||||
function LauncherView.touchpressed(imp, id, x, y, dx, dy, pressure)
|
||||
if not imp._flex then return end
|
||||
imp._touchAt = imp._touchAt or {}
|
||||
imp._touchAt[tostring(id)] = { x = x, y = y }
|
||||
pcall(FlexLove.touchpressed, id, x, y, dx, dy, pressure)
|
||||
end
|
||||
|
||||
function LauncherView.touchmoved(imp, id, x, y, dx, dy, pressure)
|
||||
if not imp._flex then return end
|
||||
local start = imp._touchAt and imp._touchAt[tostring(id)]
|
||||
if start then
|
||||
local ddx, ddy = x - start.x, y - start.y
|
||||
if ddx * ddx + ddy * ddy > TAP_SLOP2 then
|
||||
imp._suppressClickUntil = love.timer.getTime() + ACT_DEDUP
|
||||
end
|
||||
end
|
||||
pcall(FlexLove.touchmoved, id, x, y, dx, dy, pressure)
|
||||
end
|
||||
|
||||
function LauncherView.touchreleased(imp, id, x, y, dx, dy, pressure)
|
||||
if not imp._flex then return end
|
||||
if imp._touchAt then imp._touchAt[tostring(id)] = nil end
|
||||
pcall(FlexLove.touchreleased, id, x, y, dx, dy, pressure)
|
||||
end
|
||||
|
||||
-- Synthetic click for the gamepad virtual cursor: find the element under the
|
||||
-- pad pointer and run its handler with a click-shaped event.
|
||||
function LauncherView.clickAt(imp, x, y)
|
||||
@@ -200,10 +248,6 @@ end
|
||||
|
||||
-- ------- shared widget helpers
|
||||
|
||||
-- One dedup window covers a touch release plus the mouse click SDL
|
||||
-- synthesizes for the same tap.
|
||||
local ACT_DEDUP = 0.35
|
||||
|
||||
local function queueAction(imp, key, fn, keepArm)
|
||||
local now = love.timer.getTime()
|
||||
local last = imp._actAt[key]
|
||||
@@ -222,6 +266,15 @@ local function handler(imp, key, action, keepArm)
|
||||
elseif ev.type == "unhover" then
|
||||
imp._hot[key] = nil
|
||||
elseif action and (ev.type == "click" or ev.type == "touchrelease") then
|
||||
if ev.type == "touchrelease" then
|
||||
local dx, dy = ev.dx or 0, ev.dy or 0
|
||||
if dx * dx + dy * dy > TAP_SLOP2 then
|
||||
imp._suppressClickUntil = love.timer.getTime() + ACT_DEDUP
|
||||
return
|
||||
end
|
||||
end
|
||||
local untilT = imp._suppressClickUntil
|
||||
if untilT and love.timer.getTime() < untilT then return end
|
||||
queueAction(imp, key, action, keepArm)
|
||||
end
|
||||
end
|
||||
@@ -777,11 +830,23 @@ local function buildSlotCard(imp, parent, m, version)
|
||||
math.ceil(textHeight(pillSize)) + 8)
|
||||
local metaH = math.ceil(textHeight(metaSize))
|
||||
local rowH = 10 + headH + 5 + metaH + 8 + btnH + 10
|
||||
-- Fixed-height scroller so 40 slots actually overflow (page-level flex
|
||||
-- scroll alone was growing with content, leaving nothing to drag).
|
||||
local listParent = c
|
||||
if n > 0 then
|
||||
local listH = math.floor(clamp(m.h * (m.twoCol and 0.58 or 0.42), 200, 720))
|
||||
listParent = mk({
|
||||
parent = c, id = "slots-" .. version, width = "100%", height = listH,
|
||||
overflowY = "scroll", hideScrollbars = true,
|
||||
positioning = "flex", flexDirection = "vertical", gap = 10 * m.s,
|
||||
padding = { right = 4 },
|
||||
})
|
||||
end
|
||||
for _, slot in ipairs(slots) do
|
||||
local selected = slot.id == active
|
||||
local rowKey = "slot-" .. version .. "-" .. slot.id
|
||||
local row = mk({
|
||||
parent = c, width = "100%", height = rowH,
|
||||
parent = listParent, width = "100%", height = rowH,
|
||||
backgroundColor = C("rowBg", imp._hot[rowKey] and 0.85 or 0.6),
|
||||
border = 1,
|
||||
borderColor = selected and C("green", 0.9) or C("border", 0.25),
|
||||
@@ -1818,9 +1883,11 @@ function LauncherView.draw(imp)
|
||||
|
||||
-- One scroll region per tab (stable id keeps its offset across frames and
|
||||
-- separate per tab), holding the panel, the updater banner and the footer.
|
||||
-- flexShrink/minHeight keep this viewport-sized so overflowY can scroll.
|
||||
local page = mk({
|
||||
parent = root, id = "page-" .. imp.tab,
|
||||
width = "100%", flex = 1, overflowY = "scroll", hideScrollbars = true,
|
||||
width = "100%", flex = 1, flexShrink = 1, minHeight = 0,
|
||||
overflowY = "scroll", hideScrollbars = true,
|
||||
positioning = "flex", flexDirection = "vertical",
|
||||
gap = 12 * m.s,
|
||||
padding = { left = m.pad, right = m.pad + m.gutter,
|
||||
|
||||
+25
-12
@@ -1040,12 +1040,9 @@ function RomImporter.new(onComplete, opts)
|
||||
-- boot armed and let the first poll tick consume it, rather than making the
|
||||
-- player tap Import a second time to trigger the scan by hand (#553).
|
||||
pickPending = mobileFileBridge or nil,
|
||||
-- Android drag: the launcher is handed no move events at all (main.lua
|
||||
-- forwards neither touchmoved nor mousemoved while it is up), and its mouse
|
||||
-- emulation is what "no reliable pointer polling" below refers to.
|
||||
-- love.touch IS pollable, so where it exists a touch drag can be resolved
|
||||
-- inside draw the same way the desktop mouse is. Where it does not, every
|
||||
-- Android path stays exactly as it was: act on press, never arm.
|
||||
-- Mobile drag-scroll goes through FlexLove.touch* (main.lua forwards the
|
||||
-- full touch stream while the launcher is up). love.touch remains pollable
|
||||
-- for click hit-testing inside EventHandler.
|
||||
touchPollable = mobileFileBridge and love.touch ~= nil
|
||||
and love.touch.getTouches ~= nil and love.touch.getPosition ~= nil,
|
||||
tab = "red", -- active launcher tab: "red"/"blue"/"yellow"/"mods"
|
||||
@@ -2212,13 +2209,29 @@ function RomImporter:pressDelete(kind, id, version, commit)
|
||||
return false
|
||||
end
|
||||
|
||||
-- Pointer input is polled by the FlexLove view (mouse and touch alike), so
|
||||
-- the host-forwarded press events are inert. The methods stay because
|
||||
-- main.lua forwards to them unconditionally while the launcher is up.
|
||||
-- Clicks are polled inside FlexLove (mouse + love.touch); host-forwarded
|
||||
-- mousepressed stays inert so Android's synthesized mouse path cannot
|
||||
-- double-fire a tap (#553). Touch move/press/release must still reach
|
||||
-- FlexLove.touch* or scroll containers never drag on phones.
|
||||
function RomImporter:mousepressed() end
|
||||
function RomImporter:touchpressed() end
|
||||
function RomImporter:touchmoved() end
|
||||
function RomImporter:touchreleased() end
|
||||
|
||||
function RomImporter:touchpressed(id, x, y, dx, dy, pressure)
|
||||
if not self._flex then return end
|
||||
require("src.import.LauncherView").touchpressed(
|
||||
self, id, x, y, dx, dy, pressure)
|
||||
end
|
||||
|
||||
function RomImporter:touchmoved(id, x, y, dx, dy, pressure)
|
||||
if not self._flex then return end
|
||||
require("src.import.LauncherView").touchmoved(
|
||||
self, id, x, y, dx, dy, pressure)
|
||||
end
|
||||
|
||||
function RomImporter:touchreleased(id, x, y, dx, dy, pressure)
|
||||
if not self._flex then return end
|
||||
require("src.import.LauncherView").touchreleased(
|
||||
self, id, x, y, dx, dy, pressure)
|
||||
end
|
||||
|
||||
-- Switch the active tab (chips, shoulder buttons). The find search caret and
|
||||
-- the soft keyboard drop with the panel they belonged to; each tab's scroll
|
||||
|
||||
Reference in New Issue
Block a user