mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 20:50:21 +02:00
for that one guy who has a mouse but for some reason it doesnt have a scroll wheel
This commit is contained in:
+85
-12
@@ -124,12 +124,48 @@ function LauncherView.detach(imp)
|
||||
Kit.clearCaches()
|
||||
end
|
||||
|
||||
local function markNoDrag(imp, x, y, w, h)
|
||||
if Kit.blockClicks then return end
|
||||
local t = imp._noDragRects
|
||||
if not t then t = {}; imp._noDragRects = t end
|
||||
local n = (imp._noDragN or 0) + 1
|
||||
imp._noDragN = n
|
||||
local r = t[n]
|
||||
if not r then r = {}; t[n] = r end
|
||||
r.x, r.y, r.w, r.h = x, y, w, h
|
||||
end
|
||||
|
||||
local function noDragAt(imp, x, y)
|
||||
local rects = imp._noDragRects
|
||||
for i = 1, imp._noDragN or 0 do
|
||||
if inRect(rects[i], x, y) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function armMouse(imp, x, y)
|
||||
if noDragAt(imp, x, y) then
|
||||
imp._clickPt = { x = x, y = y }
|
||||
return
|
||||
end
|
||||
local shielded = imp._modalUpNow
|
||||
imp._mouseAt = {
|
||||
x = x, y = y,
|
||||
region = not shielded and tabScrollMax(imp) > 0
|
||||
and inRect(imp._tabRegionRect, x, y) or false,
|
||||
page = not shielded and (imp._pageScrollMax or 0) > 0 or false,
|
||||
}
|
||||
Kit.dragBegin(x, y)
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- input
|
||||
-- The kit is polled, not evented: update() samples the mouse and turns a
|
||||
-- rising edge into a click point that the next draw consumes. Host-forwarded
|
||||
-- mousepressed stays unused, exactly as before, so Android's synthesized
|
||||
-- mouse path cannot double-fire a tap (#553) -- the dedup window below is the
|
||||
-- other half of that guarantee.
|
||||
-- The kit is polled, not evented: update() samples the mouse. A press arms
|
||||
-- a drag that scrolls like a finger, and the click dispatches on RELEASE so
|
||||
-- the drag can disqualify it, exactly like the touch path below; only the
|
||||
-- cartridge (which owns its own spin-drag) keeps the press-down click.
|
||||
-- Host-forwarded mousepressed stays unused, exactly as before, so Android's
|
||||
-- synthesized mouse path cannot double-fire a tap (#553) -- the dedup window
|
||||
-- below is the other half of that guarantee.
|
||||
function LauncherView.update(imp, dt)
|
||||
if not imp._flex then return end
|
||||
if imp._launchFade then return end
|
||||
@@ -154,7 +190,39 @@ function LauncherView.update(imp, dt)
|
||||
if not touching and now >= (imp._suppressMouseUntil or 0)
|
||||
and now >= (imp._suppressClickUntil or 0) then
|
||||
local mx, my = love.mouse.getPosition()
|
||||
imp._clickPt = { x = mx, y = my }
|
||||
armMouse(imp, mx, my)
|
||||
end
|
||||
elseif down and imp._mouseAt then
|
||||
local start = imp._mouseAt
|
||||
local mx, my = love.mouse.getPosition()
|
||||
local ddx, ddy = mx - start.x, my - start.y
|
||||
if ddx * ddx + ddy * ddy > TAP_SLOP2 then
|
||||
start.dragged = true
|
||||
end
|
||||
if start.dragged then
|
||||
local last = start.lastY or start.y
|
||||
local move = -(my - last)
|
||||
if move ~= 0 and start.region then
|
||||
local at, leftover = Kit.scrollHandoff(tabScrollAt(imp),
|
||||
tabScrollMax(imp), move)
|
||||
setTabScroll(imp, at)
|
||||
move = leftover
|
||||
end
|
||||
if move ~= 0 and start.page and (imp._pageScrollMax or 0) > 0 then
|
||||
local at, leftover = Kit.scrollHandoff(imp._pageScroll or 0,
|
||||
imp._pageScrollMax, move)
|
||||
imp._pageScroll = at
|
||||
move = leftover
|
||||
end
|
||||
if move ~= 0 then Kit.dragAdd(move) end
|
||||
end
|
||||
start.lastY = my
|
||||
elseif not down and imp._mouseAt then
|
||||
local start = imp._mouseAt
|
||||
imp._mouseAt = nil
|
||||
Kit.dragEnd()
|
||||
if not start.dragged then
|
||||
imp._clickPt = { x = start.x, y = start.y }
|
||||
end
|
||||
end
|
||||
imp._prevMouseDown = down
|
||||
@@ -233,10 +301,12 @@ function LauncherView.clickAt(imp, x, y)
|
||||
imp._clickPt = { x = x, y = y }
|
||||
end
|
||||
|
||||
-- Event-driven click: a macOS trackpad tap delivers press+release inside one
|
||||
-- frame, so update()'s love.mouse.isDown poll never sees it. Mint the click
|
||||
-- from the press event under the poll's own suppression rules, and mark the
|
||||
-- press seen so the poll cannot mint a second one when isDown does catch it.
|
||||
-- Event-driven press: a macOS trackpad tap delivers press+release inside one
|
||||
-- frame, so update()'s love.mouse.isDown poll never sees it. Arm the drag
|
||||
-- from the press event under the poll's own suppression rules -- the poll's
|
||||
-- release branch then mints the tap, still within the same frame for a
|
||||
-- one-frame tap -- and mark the press seen so the poll cannot arm a second
|
||||
-- one when isDown does catch it.
|
||||
function LauncherView.mousepressed(imp, x, y)
|
||||
if not imp._flex then return end
|
||||
local now = love.timer.getTime()
|
||||
@@ -245,7 +315,7 @@ function LauncherView.mousepressed(imp, x, y)
|
||||
or now < (imp._suppressClickUntil or 0) then
|
||||
return
|
||||
end
|
||||
imp._clickPt = { x = x, y = y }
|
||||
if not imp._mouseAt then armMouse(imp, x, y) end
|
||||
imp._prevMouseDown = true
|
||||
end
|
||||
|
||||
@@ -495,6 +565,7 @@ end
|
||||
|
||||
local function cartridgeButton(imp, x, y, w, h, key, version, gameName, action)
|
||||
local state = cartridgeState(imp, version)
|
||||
markNoDrag(imp, x, y, w, h)
|
||||
local focused = Kit.focusable(key, x, y, w, h)
|
||||
local hot = Kit.hover(x, y, w, h)
|
||||
local active = state.active
|
||||
@@ -4868,13 +4939,15 @@ function LauncherView.draw(imp)
|
||||
Kit.beginFrame(mx, my, click ~= nil, imp._wheelY or 0)
|
||||
imp._clickPt = nil
|
||||
imp._wheelY = 0
|
||||
imp._noDragN = 0
|
||||
|
||||
Theme.field()
|
||||
|
||||
-- Everything from here to buildModals sits UNDER any open modal, so the
|
||||
-- whole stage draws shielded (no clicks, no hover, no focus ring) while
|
||||
-- one is up; buildModals lowers the shield for the modal's own controls.
|
||||
Kit.blockClicks = modalUp(imp)
|
||||
imp._modalUpNow = modalUp(imp)
|
||||
Kit.blockClicks = imp._modalUpNow
|
||||
|
||||
local step = Kit.scrollStep(m.s)
|
||||
do
|
||||
|
||||
+47
-5
@@ -918,16 +918,58 @@ function Kit.rowsThatFit(h, rowH, gap, minRows, maxRows)
|
||||
return math.max(minRows or 1, math.min(maxRows or 99, per))
|
||||
end
|
||||
|
||||
Kit.dragX = nil
|
||||
Kit.dragY = nil
|
||||
Kit.dragAccum = 0
|
||||
|
||||
function Kit.dragBegin(x, y)
|
||||
Kit.dragX, Kit.dragY, Kit.dragAccum = x, y, 0
|
||||
end
|
||||
|
||||
function Kit.dragAdd(dy)
|
||||
if Kit.dragX then Kit.dragAccum = Kit.dragAccum + (dy or 0) end
|
||||
end
|
||||
|
||||
function Kit.dragEnd()
|
||||
Kit.dragX, Kit.dragY, Kit.dragAccum = nil, nil, 0
|
||||
end
|
||||
|
||||
local function dragOriginIn(x, y, w, h)
|
||||
if not Kit.dragX then return false end
|
||||
local x1, y1, x2, y2 = x, y, x + w, y + h
|
||||
local c = Kit._clipRect
|
||||
if c then
|
||||
x1, y1 = math.max(x1, c.x), math.max(y1, c.y)
|
||||
x2, y2 = math.min(x2, c.x + c.w), math.min(y2, c.y + c.h)
|
||||
end
|
||||
return Kit.dragX >= x1 and Kit.dragX <= x2
|
||||
and Kit.dragY >= y1 and Kit.dragY <= y2
|
||||
end
|
||||
|
||||
-- Mouse wheel over a paginated list turns PAGES. The wheel still has to do
|
||||
-- something (users expect it), but it moves a bounded page index rather than
|
||||
-- driving a pixel offset, so there is no scroll state and no interpolation.
|
||||
function Kit.wheelPage(x, y, w, h, page, total, perPage)
|
||||
if Kit.blockClicks or (Kit.wheelY or 0) == 0 then return page end
|
||||
if not Kit.hit(x, y, w, h) then return page end
|
||||
if Kit.blockClicks then return page end
|
||||
local pages = math.max(1, math.ceil(total / math.max(1, perPage)))
|
||||
local moved = Theme.clamp((page or 1) + (Kit.wheelY > 0 and -1 or 1), 1, pages)
|
||||
Kit.wheelY = 0
|
||||
return math.floor(moved)
|
||||
local out = page
|
||||
if (Kit.wheelY or 0) ~= 0 and Kit.hit(x, y, w, h) then
|
||||
out = math.floor(Theme.clamp((out or 1) + (Kit.wheelY > 0 and -1 or 1),
|
||||
1, pages))
|
||||
Kit.wheelY = 0
|
||||
end
|
||||
local acc = Kit.dragAccum or 0
|
||||
if acc ~= 0 and dragOriginIn(x, y, w, h) then
|
||||
local stepPx = math.max(1, math.floor((h or 0) / 2))
|
||||
local flips = acc >= 0 and math.floor(acc / stepPx)
|
||||
or -math.floor(-acc / stepPx)
|
||||
if flips ~= 0 then
|
||||
local want = (out or 1) + flips
|
||||
out = math.floor(Theme.clamp(want, 1, pages))
|
||||
Kit.dragAccum = out ~= want and 0 or acc - flips * stepPx
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
function Kit.scrollExtent(contentH, viewH)
|
||||
|
||||
@@ -300,6 +300,88 @@ LauncherView.draw(edgeImp)
|
||||
check((edgeImp._tabScroll.skins or 0) > 0,
|
||||
"which reaches the tab region even though the cursor is below it")
|
||||
|
||||
Kit.blockClicks = false
|
||||
Kit._clipRect = nil
|
||||
Kit.wheelY = 0
|
||||
Kit.mouseX, Kit.mouseY = 400, 400
|
||||
Kit.dragBegin(50, 50)
|
||||
Kit.dragAdd(120)
|
||||
local pg = Kit.wheelPage(0, 0, 100, 100, 1, 100, 10)
|
||||
eq(pg, 3, "a drag that crossed two half-heights turns two pages")
|
||||
eq(Kit.dragAccum, 20, "and keeps the remainder for the next flip")
|
||||
Kit.dragAdd(-140)
|
||||
pg = Kit.wheelPage(0, 0, 100, 100, pg, 100, 10)
|
||||
eq(pg, 1, "dragging back down returns those pages")
|
||||
eq(Kit.dragAccum, -20, "with the remainder's sign preserved")
|
||||
Kit.dragAdd(-80)
|
||||
pg = Kit.wheelPage(0, 0, 100, 100, pg, 100, 10)
|
||||
eq(pg, 1, "a drag past the first page clamps to it")
|
||||
eq(Kit.dragAccum, 0, "and drops the pile-up so reversing is instant")
|
||||
Kit.dragAdd(200)
|
||||
pg = Kit.wheelPage(200, 200, 100, 100, pg, 100, 10)
|
||||
eq(pg, 1, "a drag that began outside the list is not the list's")
|
||||
eq(Kit.dragAccum, 200, "and its travel stays queued")
|
||||
Kit.dragEnd()
|
||||
eq(Kit.dragAccum, 0, "releasing the button retires the gesture")
|
||||
|
||||
window(360, 780)
|
||||
local mouseImp = skinLauncher(12)
|
||||
LauncherView.draw(mouseImp)
|
||||
LauncherView.draw(mouseImp)
|
||||
local mreg = mouseImp._tabRegionRect
|
||||
local mmax = mouseImp._tabScrollMax.skins
|
||||
check(mmax > 0, "the mouse-dragged panel has travel")
|
||||
local mdown = false
|
||||
love.mouse.isDown = function() return mdown end
|
||||
|
||||
pointer(mreg.x + 20, mreg.y + 40)
|
||||
mdown = true
|
||||
LauncherView.update(mouseImp, 0.016)
|
||||
check(mouseImp._clickPt == nil,
|
||||
"a press over a scrollable region mints no click")
|
||||
check(mouseImp._mouseAt ~= nil, "it arms a drag instead")
|
||||
pointer(mreg.x + 20, mreg.y + 40 - 200)
|
||||
LauncherView.update(mouseImp, 0.016)
|
||||
eq(mouseImp._tabScroll.skins, math.min(200, mmax),
|
||||
"dragging the held mouse scrolls the panel by its travel")
|
||||
eq(mouseImp._pageScroll or 0, 0,
|
||||
"while the panel still has travel, the page waits")
|
||||
pointer(mreg.x + 20, mreg.y + 40 - 200 - mmax * 2)
|
||||
LauncherView.update(mouseImp, 0.016)
|
||||
eq(mouseImp._tabScroll.skins, mmax,
|
||||
"a longer mouse drag reaches the panel's bottom")
|
||||
check((mouseImp._pageScroll or 0) > 0, "and spills into the page from there")
|
||||
mdown = false
|
||||
LauncherView.update(mouseImp, 0.016)
|
||||
check(mouseImp._clickPt == nil, "a released drag is not a click")
|
||||
check(mouseImp._mouseAt == nil, "and the gesture is retired")
|
||||
|
||||
pointer(mreg.x + 20, mreg.y + 40)
|
||||
mdown = true
|
||||
LauncherView.update(mouseImp, 0.016)
|
||||
check(mouseImp._clickPt == nil, "a fresh press still holds its click back")
|
||||
mdown = false
|
||||
LauncherView.update(mouseImp, 0.016)
|
||||
check(mouseImp._clickPt ~= nil,
|
||||
"press and release without travel is still a tap, on release")
|
||||
mouseImp._clickPt = nil
|
||||
|
||||
LauncherView.draw(gameImp)
|
||||
LauncherView.draw(gameImp)
|
||||
check((gameImp._noDragN or 0) > 0, "the game tab publishes its cartridge rect")
|
||||
local cart = gameImp._noDragRects[1]
|
||||
pointer(cart.x + cart.w / 2, cart.y + cart.h / 2)
|
||||
mdown = true
|
||||
LauncherView.update(gameImp, 0.016)
|
||||
check(gameImp._clickPt ~= nil,
|
||||
"a press on the cartridge clicks at once so its own spin-drag still owns "
|
||||
.. "the gesture")
|
||||
check(gameImp._mouseAt == nil, "and never arms the scroll drag")
|
||||
gameImp._clickPt = nil
|
||||
mdown = false
|
||||
LauncherView.update(gameImp, 0.016)
|
||||
love.mouse.isDown = nil
|
||||
|
||||
local function read(path)
|
||||
local f = assert(io.open(path, "r"))
|
||||
local src = f:read("*a")
|
||||
|
||||
Reference in New Issue
Block a user