mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-14 08:23:59 +08:00
Recognize short deliberate bookmark swipes
This commit is contained in:
@@ -44,6 +44,10 @@ CAM_Y_OFFSET = 20
|
||||
|
||||
class BookmarkIcon(Widget):
|
||||
PEEK_THRESHOLD = 150 # If icon peeks out this much, snap it fully visible
|
||||
FLICK_DISTANCE = 60
|
||||
FLICK_VELOCITY = 800
|
||||
FLICK_DURATION = 0.15
|
||||
FLICK_PAUSE = 0.06
|
||||
FULL_VISIBLE_OFFSET = 200 # How far onscreen when fully visible
|
||||
HIDDEN_OFFSET = -50 # How far offscreen when hidden
|
||||
|
||||
@@ -68,6 +72,9 @@ class BookmarkIcon(Widget):
|
||||
self._triggered_time: float = 0.0
|
||||
self._triggered_duration = 1.5
|
||||
self._swipe_expired = False
|
||||
self._swipe_start_t = 0.0
|
||||
self._last_swipe_move = (0.0, 0.0)
|
||||
self._swipe_moves = 0
|
||||
|
||||
def is_swiping_left(self) -> bool:
|
||||
"""Check if currently swiping left (for scroller to disable)."""
|
||||
@@ -154,6 +161,9 @@ class BookmarkIcon(Widget):
|
||||
self._is_swiping = True
|
||||
self._is_swiping_left = False
|
||||
self._swipe_expired = False
|
||||
self._swipe_start_t = mouse_event.t
|
||||
self._last_swipe_move = (mouse_event.pos.x, mouse_event.t)
|
||||
self._swipe_moves = 0
|
||||
|
||||
elif (mouse_event.left_down or mouse_event.left_released) and self._is_swiping:
|
||||
self._swipe_current_x = mouse_event.pos.x
|
||||
@@ -162,6 +172,12 @@ class BookmarkIcon(Widget):
|
||||
if swipe_offset < -12 or (vertical > 12 and vertical > abs(swipe_offset)):
|
||||
self.cancel_gesture()
|
||||
return
|
||||
if mouse_event.left_down and not mouse_event.left_released:
|
||||
if mouse_event.t <= self._last_swipe_move[1]:
|
||||
self._swipe_moves = 0
|
||||
else:
|
||||
self._swipe_moves += 1
|
||||
self._last_swipe_move = (mouse_event.pos.x, mouse_event.t)
|
||||
self._is_swiping_left = swipe_offset > 12
|
||||
if self._is_swiping_left:
|
||||
self._interacting = True
|
||||
@@ -174,7 +190,14 @@ class BookmarkIcon(Widget):
|
||||
swipe_distance = self._swipe_start_x - self._swipe_current_x
|
||||
|
||||
# If peeking past threshold, transition to animating to fully visible and bookmark
|
||||
if swipe_distance > self.PEEK_THRESHOLD:
|
||||
move_x, move_t = self._last_swipe_move
|
||||
elapsed = move_t - self._swipe_start_t
|
||||
held_distance = self._swipe_start_x - move_x
|
||||
flick = (self._swipe_moves >= 2 and 0.01 <= elapsed <= self.FLICK_DURATION and
|
||||
0 <= mouse_event.t - move_t <= self.FLICK_PAUSE and
|
||||
held_distance >= self.FLICK_DISTANCE and swipe_distance >= self.FLICK_DISTANCE and
|
||||
swipe_distance >= held_distance - 12 and held_distance / elapsed >= self.FLICK_VELOCITY)
|
||||
if swipe_distance > self.PEEK_THRESHOLD or flick:
|
||||
self._state = BookmarkState.TRIGGERED
|
||||
self._triggered_time = rl.get_time()
|
||||
self._triggered_duration = 1.5
|
||||
|
||||
@@ -49,8 +49,8 @@ def navigation(monkeypatch):
|
||||
with patch.object(type(road.gui_app), 'frame', new_callable=PropertyMock, return_value=frame_number), \
|
||||
patch.object(type(road.gui_app), 'mouse_events', new_callable=PropertyMock, return_value=list(events)):
|
||||
layout.render()
|
||||
def event(x, y=100, *, pressed=False, down=False, released=False):
|
||||
return MouseEvent(MousePos(x, y), 0, pressed, released, down, frame_number / 60)
|
||||
def event(x, y=100, *, pressed=False, down=False, released=False, timestamp=None):
|
||||
return MouseEvent(MousePos(x, y), 0, pressed, released, down, frame_number / 60 if timestamp is None else timestamp)
|
||||
with patch.object(road, 'ui_state', NS(started=True)), patch.object(bookmark, '_render'), \
|
||||
patch.object(rl, 'begin_scissor_mode'), patch.object(rl, 'end_scissor_mode'), \
|
||||
patch.object(rl, 'get_frame_time', return_value=1/60):
|
||||
@@ -125,3 +125,37 @@ def test_bookmark_requires_full_swipe_threshold(navigation, distance, expected):
|
||||
for event in (n.event(400, pressed=True, down=True), n.event(400-distance, released=True)):
|
||||
n.frame(event)
|
||||
assert n.callback.call_count == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('batched', [False, True])
|
||||
def test_short_fast_left_swipe_bookmarks_without_dragging_page(navigation, batched):
|
||||
n = navigation
|
||||
events = [n.event(400, pressed=True, down=True, timestamp=10),
|
||||
n.event(360, down=True, timestamp=10.04), n.event(310, down=True, timestamp=10.08),
|
||||
n.event(305, released=True, timestamp=10.09)]
|
||||
if batched:
|
||||
n.frame(*events)
|
||||
else:
|
||||
for event in events:
|
||||
n.frame(event)
|
||||
n.callback.assert_called_once()
|
||||
assert n.page.rect.x == 0
|
||||
n.frame(n.event(305, released=True, timestamp=10.1))
|
||||
n.callback.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('positions,times', [
|
||||
([400, 360, 310, 305], [10, 10.4, 10.8, 10.81]),
|
||||
([400, 380, 360, 355], [10, 10.02, 10.04, 10.05]),
|
||||
([400, 390, 385, 300], [10, 10.02, 10.04, 10.05]),
|
||||
([400, 360, 310, 305], [10, 10.04, 10.08, 10.3]),
|
||||
([400, 360, 310, 330], [10, 10.04, 10.08, 10.09]),
|
||||
([400, 360, 310, 305], [10, 10, 10, 10]),
|
||||
([400, 360, 310, 305], [10, 9.9, 9.8, 9.7]),
|
||||
])
|
||||
def test_short_swipe_rejects_slow_drag_jitter_release_jump_pause_reversal_and_bad_clock(navigation, positions, times):
|
||||
n = navigation
|
||||
for index, (x, timestamp) in enumerate(zip(positions, times, strict=True)):
|
||||
n.frame(n.event(x, pressed=index == 0, down=index != 3, released=index == 3, timestamp=timestamp))
|
||||
n.callback.assert_not_called()
|
||||
assert n.page.rect.x == 0
|
||||
|
||||
Reference in New Issue
Block a user