From 404b505a41362f340e79374ee3ad4dbd468da3e4 Mon Sep 17 00:00:00 2001 From: cirakg Date: Sun, 25 Oct 2026 13:13:39 +0100 Subject: fix(ui): vertical swipes and drags scroll the list and text screens The Forerunner 70 sends no page behaviour for a swipe on a custom view, so the list and info screens only scrolled with the buttons. Swipes and drags now scroll them directly; one gesture scrolls once even when the device also sends the page behaviour, and a drag's release opens no row. Co-Authored-By: Claude Opus 5.5 --- source/ui/Gesture.mc | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 source/ui/Gesture.mc (limited to 'source/ui/Gesture.mc') diff --git a/source/ui/Gesture.mc b/source/ui/Gesture.mc new file mode 100644 index 0000000..0824eb1 --- /dev/null +++ b/source/ui/Gesture.mc @@ -0,0 +1,88 @@ +// Author: cirakg & claude ai +// Summary: Vertical swipes and drags that scroll a custom view (the Forerunner 70 sends no next/previous-page behaviour for them), scrolling once per gesture. + +import Toybox.Lang; +import Toybox.System; +import Toybox.WatchUi; + +// A touch gesture can reach a delegate several ways at once: as drag events, +// as a swipe, and as the next/previous-page behaviour. Some watches send only +// the drag. The first one to scroll marks the time; the others arriving within +// DEDUPE_MS are ignored, so one gesture scrolls once. The UP/DOWN buttons +// never mark, so they are only held back right after a touch gesture. +module Gesture { + + const DEDUPE_MS = 300; + // A drag shorter than this part of the screen height does not scroll. + const MIN_DRAG_DIVISOR = 8; + // A drag longer than this part still counts as a drag, not a tap. + const SLOP_DIVISOR = 32; + + var _scrolledMs as Number? = null; + var _draggedMs as Number? = null; + var _startY as Number? = null; + + function _within(since as Number?) as Boolean { + if (since == null) { + return false; + } + var elapsed = System.getTimer() - since; + return elapsed >= 0 && elapsed < DEDUPE_MS; + } + + // True right after a touch gesture scrolled: next/previous-page and + // swipe events in this window belong to that gesture. + function recent() as Boolean { + return _within(_scrolledMs); + } + + // True right after a drag: a tap arriving now is its end, not a tap. + function tapIgnored() as Boolean { + return _within(_draggedMs); + } + + function isVertical(event as WatchUi.SwipeEvent) as Boolean { + var direction = event.getDirection(); + return direction == WatchUi.SWIPE_UP || direction == WatchUi.SWIPE_DOWN; + } + + // The step for a vertical swipe: 1 (next, swipe up), -1 (previous), or 0 + // when this gesture already scrolled. + function swipeStep(event as WatchUi.SwipeEvent) as Number { + if (recent()) { + return 0; + } + _scrolledMs = System.getTimer(); + return event.getDirection() == WatchUi.SWIPE_UP ? 1 : -1; + } + + // Feed a drag event. At the end of a long enough vertical drag returns + // the signed number of units to scroll (positive = next, finger moved + // up), at least 1 and distance / unit otherwise; 0 for anything else. + function dragStep(event as WatchUi.DragEvent, unit as Number) as Number { + var y = event.getCoordinates()[1]; + var type = event.getType(); + if (type == WatchUi.DRAG_TYPE_START) { + _startY = y; + return 0; + } + var start = _startY; + if (type != WatchUi.DRAG_TYPE_STOP || start == null) { + return 0; + } + _startY = null; + var height = System.getDeviceSettings().screenHeight; + var distance = start - y; + var length = distance < 0 ? -distance : distance; + if (length > height / SLOP_DIVISOR) { + _draggedMs = System.getTimer(); + } + if (length < height / MIN_DRAG_DIVISOR || recent()) { + return 0; + } + _scrolledMs = System.getTimer(); + var units = unit > 0 ? length / unit : 1; + units = units < 1 ? 1 : units; + return distance < 0 ? -units : units; + } +} -- cgit v1.2.3