This commit is contained in:
firestar5683
2026-04-11 21:56:46 -05:00
parent d2e5f06395
commit d43b7d0d3f
187 changed files with 5499 additions and 6222 deletions
+32 -3
View File
@@ -20,6 +20,21 @@ MAX_SPEED = 10000.0 # px/s
DEBUG = os.getenv("DEBUG_SCROLL", "0") == "1"
# Weights older (steadier) velocity samples more heavily on release.
# Finger-lift samples are noisy; trusting earlier samples gives consistent fling velocity.
# Reverse-engineered from iOS UIScrollView (tuned at 120Hz touch) by Flutter team:
# https://github.com/flutter/flutter/pull/60501
# 3 samples ≈ 25ms at 120Hz (iOS) / ~21ms at 140Hz (comma). Scale if touch rate changes.
def weighted_velocity(buffer: deque) -> float:
if len(buffer) >= 3:
return buffer[-3] * 0.6 + buffer[-2] * 0.35 + buffer[-1] * 0.05
elif len(buffer) == 2:
return buffer[-2] * 0.7 + buffer[-1] * 0.3
elif len(buffer) == 1:
return buffer[-1]
return 0.0
# from https://ariya.io/2011/10/flick-list-with-its-momentum-scrolling-and-deceleration
class ScrollState(Enum):
STEADY = 0
@@ -73,8 +88,14 @@ class GuiScrollPanel2:
def _update_state(self, bounds_size: float, content_size: float) -> None:
"""Runs per render frame, independent of mouse events. Updates auto-scrolling state and velocity."""
if self._state == ScrollState.AUTO_SCROLL:
max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size)
max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size)
if self._state == ScrollState.STEADY:
# if we find ourselves out of bounds, scroll back in (from external layout dimension changes, etc.)
if self.get_offset() > max_offset or self.get_offset() < min_offset:
self._state = ScrollState.AUTO_SCROLL
elif self._state == ScrollState.AUTO_SCROLL:
# simple exponential return if out of bounds
out_of_bounds = self.get_offset() > max_offset or self.get_offset() < min_offset
if out_of_bounds and self._handle_out_of_bounds:
@@ -145,7 +166,13 @@ class GuiScrollPanel2:
# Touch rejection: when releasing finger after swiping and stopping, panel
# reports a few erroneous touch events with high velocity, try to ignore.
# If velocity decelerates very quickly, assume user doesn't intend to auto scroll
# If velocity decelerates very quickly, assume user doesn't intend to auto scroll.
# Catches two cases: 1) swipe, stop finger, then lift (stale high velocity in buffer)
# 2) dirty finger lift where finger rotates/slides producing spurious velocity spike.
# TODO: this heuristic false-positives on fast swipes because 140Hz touch polling
# jitter causes velocity to oscillate (not real deceleration). Better approaches:
# - Use evdev kernel timestamps to eliminate velocity oscillation at the source
# - Replace with a time-since-last-event check (40ms timeout) for swipe-stop-lift
high_decel = False
if len(self._velocity_buffer) > 2:
# We limit max to first half since final few velocities can surpass first few
@@ -160,6 +187,8 @@ class GuiScrollPanel2:
print('deceleration too high, going to STEADY')
high_decel = True
self._velocity = weighted_velocity(self._velocity_buffer)
# If final velocity is below some threshold, switch to steady state too
low_speed = abs(self._velocity) <= MIN_VELOCITY_FOR_CLICKING * 1.5 # plus some margin