mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 04:06:10 +02:00
@@ -2709,6 +2709,7 @@ function Element:_drawChildren(backdropCanvas)
|
||||
|
||||
-- Set up clipping: rounded-corners (stencil) > overflow (scissor) > none
|
||||
local clipMode = "none"
|
||||
local prevSx, prevSy, prevSw, prevSh
|
||||
if hasRoundedCorners then
|
||||
local roundedBoxWidth = self._borderBoxWidth or (self.width + self.padding.left + self.padding.right)
|
||||
local roundedBoxHeight = self._borderBoxHeight or (self.height + self.padding.top + self.padding.bottom)
|
||||
@@ -2721,7 +2722,18 @@ function Element:_drawChildren(backdropCanvas)
|
||||
love.graphics.setStencilTest("greater", 0)
|
||||
clipMode = "stencil"
|
||||
elseif needsOverflowClipping then
|
||||
love.graphics.setScissor(self.x + self.padding.left, self.y + self.padding.top, self.width, self.height)
|
||||
-- Intersect with (and later restore) any ancestor scissor: replacing
|
||||
-- it let a nested scroller widen its parent's clip, and the bare
|
||||
-- setScissor() restore then cleared it entirely - scrolled page
|
||||
-- content drew straight over the pinned header.
|
||||
-- transformPoint: scissor rects live in window coordinates and ignore
|
||||
-- the scroll translate an ancestor scroller has pushed, so a nested
|
||||
-- scroller's clip sat at its untranslated layout position and cut its
|
||||
-- own rows away once the page scrolled.
|
||||
prevSx, prevSy, prevSw, prevSh = love.graphics.getScissor()
|
||||
local scx, scy = love.graphics.transformPoint(
|
||||
self.x + self.padding.left, self.y + self.padding.top)
|
||||
love.graphics.intersectScissor(scx, scy, self.width, self.height)
|
||||
clipMode = "scissor"
|
||||
end
|
||||
|
||||
@@ -2745,7 +2757,11 @@ function Element:_drawChildren(backdropCanvas)
|
||||
if clipMode == "stencil" then
|
||||
love.graphics.setStencilTest()
|
||||
elseif clipMode == "scissor" then
|
||||
love.graphics.setScissor()
|
||||
if prevSx then
|
||||
love.graphics.setScissor(prevSx, prevSy, prevSw, prevSh)
|
||||
else
|
||||
love.graphics.setScissor()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -112,7 +112,8 @@ function ScrollManager.new(config, deps)
|
||||
self.touchScrollEnabled = config.touchScrollEnabled ~= false -- Default true
|
||||
self.momentumScrollEnabled = config.momentumScrollEnabled ~= false -- Default true
|
||||
self.bounceEnabled = config.bounceEnabled ~= false -- Default true
|
||||
self.scrollFriction = config.scrollFriction or 0.95 -- Exponential decay per frame
|
||||
self.scrollFriction = config.scrollFriction or 0.95 -- legacy per-frame decay; superseded by _momentumDecel
|
||||
self._momentumDecel = config.momentumDecel or 2.0 -- fling decay rate (1/s), dt-based; ~iOS "normal"
|
||||
self.bounceStiffness = config.bounceStiffness or 0.2 -- Spring constant
|
||||
self.maxOverscroll = config.maxOverscroll or 100 -- pixels
|
||||
|
||||
@@ -926,9 +927,15 @@ function ScrollManager:handleTouchMove(touchX, touchY)
|
||||
dx = -dx
|
||||
dy = -dy
|
||||
|
||||
-- Calculate velocity (pixels per second)
|
||||
self._scrollVelocityX = dx / dt
|
||||
self._scrollVelocityY = dy / dt
|
||||
-- Velocity tracking (pixels per second). Not the raw last-sample d/dt:
|
||||
-- touch samples jitter, and the final pre-release sample often reads near
|
||||
-- zero, which is why single-sample flings feel dead compared to a native
|
||||
-- list. Blend toward the instantaneous velocity with a ~50 ms time
|
||||
-- constant instead, like OS velocity trackers that average recent samples.
|
||||
local instX, instY = dx / dt, dy / dt
|
||||
local blend = math.min(1, dt / 0.05)
|
||||
self._scrollVelocityX = self._scrollVelocityX + (instX - self._scrollVelocityX) * blend
|
||||
self._scrollVelocityY = self._scrollVelocityY + (instY - self._scrollVelocityY) * blend
|
||||
|
||||
-- Apply scroll with bounce if enabled
|
||||
if self.bounceEnabled then
|
||||
@@ -969,6 +976,15 @@ function ScrollManager:handleTouchRelease()
|
||||
|
||||
self._touchScrolling = false
|
||||
|
||||
-- A finger that pauses and then lifts is a stop, not a fling: without
|
||||
-- this, the smoothed velocity from the earlier drag still launches the
|
||||
-- list after a deliberate hold (native lists kill the tracker the same
|
||||
-- way).
|
||||
if love.timer.getTime() - (self._lastTouchTime or 0) > 0.1 then
|
||||
self._scrollVelocityX = 0
|
||||
self._scrollVelocityY = 0
|
||||
end
|
||||
|
||||
-- Start momentum scrolling if enabled and velocity is significant
|
||||
if self.momentumScrollEnabled then
|
||||
local velocityThreshold = 50 -- pixels per second
|
||||
@@ -997,8 +1013,13 @@ function ScrollManager:update(dt)
|
||||
-- rate; a fixed fraction made scrolling visibly slower whenever frame
|
||||
-- time rose. _smoothScrollRate is the decay constant in 1/seconds:
|
||||
-- ~90% of the remaining distance is covered in 2.3/rate seconds.
|
||||
-- dt can legitimately be 0 here: in immediate mode beginFrame resets the
|
||||
-- accumulated dt BEFORE endFrame updates the elements, so the launcher's
|
||||
-- update order always hands this 0. Treat that (and nil) as one nominal
|
||||
-- 60 Hz step so the interpolation still advances one frame's worth.
|
||||
local step = (dt and dt > 0) and dt or (1 / 60)
|
||||
if self._targetScrollX or self._targetScrollY then
|
||||
local alpha = 1 - math.exp(-(self._smoothScrollRate or 30) * (dt or 0.016))
|
||||
local alpha = 1 - math.exp(-(self._smoothScrollRate or 30) * step)
|
||||
if self._targetScrollY then
|
||||
local diff = self._targetScrollY - self._scrollY
|
||||
if math.abs(diff) > 0.5 then
|
||||
@@ -1028,25 +1049,43 @@ function ScrollManager:update(dt)
|
||||
return
|
||||
end
|
||||
|
||||
-- Apply velocity to scroll position
|
||||
local dx = self._scrollVelocityX * dt
|
||||
local dy = self._scrollVelocityY * dt
|
||||
-- Apply velocity to scroll position (step, not dt: dt is 0 in the
|
||||
-- immediate-mode endFrame path, which left touch flings completely dead)
|
||||
local dx = self._scrollVelocityX * step
|
||||
local dy = self._scrollVelocityY * step
|
||||
|
||||
if self.bounceEnabled then
|
||||
-- Allow overscroll during momentum
|
||||
self._scrollX = self._scrollX + dx
|
||||
self._scrollY = self._scrollY + dy
|
||||
-- Allow overscroll during momentum, but never past maxOverscroll: an
|
||||
-- unclamped fling carried a list hundreds of pixels past its edge
|
||||
-- (rows vanished into a blank card until the spring crawled back).
|
||||
self._scrollX = self._utils.clamp(self._scrollX + dx,
|
||||
-self.maxOverscroll, self._maxScrollX + self.maxOverscroll)
|
||||
self._scrollY = self._utils.clamp(self._scrollY + dy,
|
||||
-self.maxOverscroll, self._maxScrollY + self.maxOverscroll)
|
||||
else
|
||||
self:scrollBy(dx, dy)
|
||||
end
|
||||
|
||||
-- Apply friction (exponential decay)
|
||||
self._scrollVelocityX = self._scrollVelocityX * self.scrollFriction
|
||||
self._scrollVelocityY = self._scrollVelocityY * self.scrollFriction
|
||||
-- Frame-rate-independent exponential decay tuned to native list feel:
|
||||
-- iOS "normal" deceleration is ~0.998 per millisecond, i.e. exp(-2.0 t).
|
||||
-- The old per-frame 0.95 factor killed a fling in a fraction of a second
|
||||
-- (and faster the higher the frame rate). Total fling travel is
|
||||
-- velocity / rate.
|
||||
local decay = math.exp(-(self._momentumDecel or 2.0) * step)
|
||||
-- Past the content edge, kill the fling an order of magnitude faster so
|
||||
-- the rubber band absorbs it instead of stretching to the clamp and
|
||||
-- sitting there (native lists do the same).
|
||||
local overX = self._scrollX < 0 or self._scrollX > self._maxScrollX
|
||||
local overY = self._scrollY < 0 or self._scrollY > self._maxScrollY
|
||||
if overX or overY then
|
||||
decay = decay * math.exp(-20 * step)
|
||||
end
|
||||
self._scrollVelocityX = self._scrollVelocityX * decay
|
||||
self._scrollVelocityY = self._scrollVelocityY * decay
|
||||
|
||||
-- Stop momentum when velocity is very low
|
||||
local totalVelocity = math.sqrt(self._scrollVelocityX ^ 2 + self._scrollVelocityY ^ 2)
|
||||
if totalVelocity < 1 then
|
||||
if totalVelocity < 5 then
|
||||
self._momentumScrolling = false
|
||||
self._scrollVelocityX = 0
|
||||
self._scrollVelocityY = 0
|
||||
|
||||
@@ -214,10 +214,13 @@ local function onDraw(element, _ctx)
|
||||
if not (scrollbarDims.vertical.visible or scrollbarDims.horizontal.visible) then
|
||||
return
|
||||
end
|
||||
-- Clear any parent scissor clipping before drawing scrollbars so they render
|
||||
-- fully visible (scrollbars must not be clipped by ancestor overflow).
|
||||
-- Lift the parent scissor while drawing scrollbars so they render fully
|
||||
-- visible, then RESTORE it: clearing it outright let every later sibling
|
||||
-- draw unclipped (scrolled page content over the pinned header).
|
||||
local sx, sy, sw, sh = love.graphics.getScissor()
|
||||
love.graphics.setScissor()
|
||||
element._renderer:drawScrollbars(element, element.x, element.y, element.width, element.height, scrollbarDims)
|
||||
if sx then love.graphics.setScissor(sx, sy, sw, sh) end
|
||||
end
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user