summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/ui/Gesture.mc88
-rw-r--r--source/ui/InfoView.mc45
-rw-r--r--source/ui/ListDelegate.mc38
-rw-r--r--source/ui/ListView.mc6
4 files changed, 169 insertions, 8 deletions
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;
+ }
+}
diff --git a/source/ui/InfoView.mc b/source/ui/InfoView.mc
index cab4b52..a965713 100644
--- a/source/ui/InfoView.mc
+++ b/source/ui/InfoView.mc
@@ -16,6 +16,7 @@ class InfoView extends WatchUi.View {
private var _first as Number = 0;
private var _visible as Number = 1;
private var _shownLogged as Boolean = false;
+ private var _lineH as Number = 1;
// screen names the view in "DIAG shown" lines for UI scripts.
function initialize(title as String, text as Array<String>, screen as String) {
@@ -38,6 +39,7 @@ class InfoView extends WatchUi.View {
var max = lines.size() - _visible > 0 ? lines.size() - _visible : 0;
var next = _first + delta;
next = next < 0 ? 0 : (next > max ? max : next);
+ Diag.data("info_scroll", "first=" + next);
if (next == _first) {
return false;
}
@@ -46,12 +48,18 @@ class InfoView extends WatchUi.View {
return true;
}
+ // The height of a text line, as last drawn (for drags).
+ function lineHeight() as Number {
+ return _lineH;
+ }
+
function onUpdate(dc as Graphics.Dc) as Void {
Ui.clear(dc);
var w = dc.getWidth();
var h = dc.getHeight();
var font = Graphics.FONT_XTINY;
var lineH = dc.getFontHeight(font);
+ _lineH = lineH;
var y = Ui.isRound() ? h / 8 : 6;
dc.setColor(Ui.accent(), Graphics.COLOR_TRANSPARENT);
dc.drawText(w / 2, y, Graphics.FONT_SMALL, Ui.fit(dc, _title, Graphics.FONT_SMALL, 2 * Ui.halfWidthAt(dc, y)),
@@ -114,20 +122,47 @@ class InfoDelegate extends WatchUi.BehaviorDelegate {
}
function onNextPage() as Boolean {
- AppState.activity();
- var v = view();
- if (v != null) {
- v.scroll(3);
+ if (!Gesture.recent()) {
+ scrollBy(3);
}
return true;
}
function onPreviousPage() as Boolean {
+ if (!Gesture.recent()) {
+ scrollBy(-3);
+ }
+ return true;
+ }
+
+ private function scrollBy(lines as Number) as Void {
AppState.activity();
var v = view();
if (v != null) {
- v.scroll(-3);
+ v.scroll(lines);
+ }
+ }
+
+ // A vertical swipe or drag scrolls too (the Forerunner 70 sends no page
+ // behaviour for them); Gesture makes a gesture count once.
+ function onSwipe(event as WatchUi.SwipeEvent) as Boolean {
+ if (!Gesture.isVertical(event)) {
+ return BehaviorDelegate.onSwipe(event);
+ }
+ var step = Gesture.swipeStep(event);
+ if (step != 0) {
+ scrollBy(3 * step);
+ }
+ return true;
+ }
+
+ function onDrag(event as WatchUi.DragEvent) as Boolean {
+ var v = view();
+ var step = Gesture.dragStep(event, v != null ? v.lineHeight() : 1);
+ if (step == 0) {
+ return false;
}
+ scrollBy(step);
return true;
}
diff --git a/source/ui/ListDelegate.mc b/source/ui/ListDelegate.mc
index a7c8bf0..ca7cee9 100644
--- a/source/ui/ListDelegate.mc
+++ b/source/ui/ListDelegate.mc
@@ -14,14 +14,43 @@ class ListDelegate extends WatchUi.BehaviorDelegate {
}
function onNextPage() as Boolean {
- AppState.activity();
- _view.move(1);
+ if (!Gesture.recent()) {
+ AppState.activity();
+ _view.move(1);
+ }
return true;
}
function onPreviousPage() as Boolean {
+ if (!Gesture.recent()) {
+ AppState.activity();
+ _view.move(-1);
+ }
+ return true;
+ }
+
+ // The Forerunner 70 sends no page behaviour for a vertical swipe, so the
+ // swipe and the drag scroll here; Gesture makes a gesture count once.
+ function onSwipe(event as WatchUi.SwipeEvent) as Boolean {
+ if (!Gesture.isVertical(event)) {
+ return BehaviorDelegate.onSwipe(event);
+ }
+ var step = Gesture.swipeStep(event);
+ if (step != 0) {
+ AppState.activity();
+ _view.move(step);
+ }
+ return true;
+ }
+
+ // A drag scrolls by the rows it covered, at least one.
+ function onDrag(event as WatchUi.DragEvent) as Boolean {
+ var step = Gesture.dragStep(event, _view.rowHeight());
+ if (step == 0) {
+ return false;
+ }
AppState.activity();
- _view.move(-1);
+ _view.move(step);
return true;
}
@@ -55,6 +84,9 @@ class ListDelegate extends WatchUi.BehaviorDelegate {
// A tap opens the row under the finger.
function onTap(event as WatchUi.ClickEvent) as Boolean {
AppState.activity();
+ if (Gesture.tapIgnored()) {
+ return true;
+ }
var index = _view.rowAt(event.getCoordinates()[1]);
if (index < 0) {
return true;
diff --git a/source/ui/ListView.mc b/source/ui/ListView.mc
index 94b8c99..de73cce 100644
--- a/source/ui/ListView.mc
+++ b/source/ui/ListView.mc
@@ -82,9 +82,15 @@ class ListView extends WatchUi.View {
return;
}
focus = ((focus + delta) % n + n) % n;
+ Diag.data("list_move", "focus=" + focus);
WatchUi.requestUpdate();
}
+ // The height of a row as last drawn (for drags).
+ function rowHeight() as Number {
+ return _rowHeight;
+ }
+
function focusedId() as String? {
return focus < _ids.size() ? _ids[focus] : null;
}