From a45f1b521566248a9056a8e3a920573b4aea9e7c Mon Sep 17 00:00:00 2001 From: EiSiMo Date: Mon, 13 Apr 2026 19:35:35 +0200 Subject: [PATCH] Tap-to-rotate: tap icon to select, tap selected to open Tapping an unselected icon in the menu ring rotates to it with animation. Tapping the already-selected icon opens it. Icon positions are tracked from last render for hit detection. Co-Authored-By: Claude Opus 4.6 (1M context) --- source/MenuDelegate.mc | 26 +++++++++++++++++++++++--- source/MenuView.mc | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/source/MenuDelegate.mc b/source/MenuDelegate.mc index 4f8a3d7..a13a4df 100644 --- a/source/MenuDelegate.mc +++ b/source/MenuDelegate.mc @@ -1,9 +1,9 @@ import Toybox.Lang; import Toybox.WatchUi; -// Routes menu input. UP/DOWN rotate the ring. START/STOP dispatches: -// - event types (arrest, start, …) push the GPS LoadingView -// - history / delete are stubbed until later phases +// Routes menu input. UP/DOWN rotate the ring. START/STOP opens +// the selected item. Tap on an icon rotates to it; tap on the +// already-selected icon opens it. class MenuDelegate extends WatchUi.BehaviorDelegate { private var _view as MenuView; @@ -23,7 +23,27 @@ class MenuDelegate extends WatchUi.BehaviorDelegate { return true; } + function onTap(evt as WatchUi.ClickEvent) as Boolean { + var coords = evt.getCoordinates(); + var tapX = coords[0] as Number; + var tapY = coords[1] as Number; + var idx = _view.itemIndexAt(tapX, tapY); + if (idx < 0) { return false; } + + if (idx == _view.selectedIndex()) { + // Tap on already-selected item → open it. + return _openSelected(); + } + // Tap on another item → rotate to it. + _view.rotateTo(idx); + return true; + } + function onSelect() as Boolean { + return _openSelected(); + } + + private function _openSelected() as Boolean { var item = _view.selectedItem(); var key = item[:key] as String; diff --git a/source/MenuView.mc b/source/MenuView.mc index af09571..9627c6b 100644 --- a/source/MenuView.mc +++ b/source/MenuView.mc @@ -76,6 +76,41 @@ class MenuView extends WatchUi.View { return _items[_selectedIndex]; } + function selectedIndex() as Number { + return _selectedIndex; + } + + private var _iconPositions as Array = []; + + // Returns the index of the icon closest to (tapX, tapY), or -1. + function itemIndexAt(tapX as Number, tapY as Number) as Number { + var bestIdx = -1; + var bestDist = 999999; + for (var i = 0; i < _iconPositions.size(); i++) { + var pos = _iconPositions[i] as Array; + var ix = pos[0] as Number; + var iy = pos[1] as Number; + var size = pos[2] as Number; + var dx = tapX - ix; + var dy = tapY - iy; + var dist = dx * dx + dy * dy; + var maxDist = (size / 2) * (size / 2); + if (dist < maxDist && dist < bestDist) { + bestDist = dist; + bestIdx = i; + } + } + return bestIdx; + } + + // Rotate to a specific index with animation. + function rotateTo(index as Number) as Void { + if (index == _selectedIndex) { return; } + _startAnim(_selectedIndex); + _selectedIndex = index; + Application.Storage.setValue(STORAGE_KEY, _selectedIndex); + } + function onUpdate(dc as Dc) as Void { dc.setColor(Config.COLOR_FG, Config.COLOR_BG); dc.clear(); @@ -102,6 +137,7 @@ class MenuView extends WatchUi.View { if (diff < -n / 2.0) { diff = diff + n; } var currentOffset = fromOffset + diff * ease; + _iconPositions = new [n]; for (var i = 0; i < n; i++) { var angle = selectionAngle + (i.toFloat() - currentOffset) * step; var x = (cx + radius * Math.cos(angle)).toNumber(); @@ -119,6 +155,8 @@ class MenuView extends WatchUi.View { targetSize = (selSize - (selSize - baseSize) * ease).toNumber(); } + _iconPositions[i] = [x, y, targetSize]; + // Colored circle behind icon. var color = _items[i][:color] as Number; dc.setColor(color, Config.COLOR_BG);