@@ -1495,6 +1495,13 @@ void menu_layer_set_center_focused(MenuLayer *menu_layer, bool center_focused) {
14951495 menu_layer_update_caches (menu_layer );
14961496}
14971497
1498+ void menu_layer_set_tap_select_only (MenuLayer * menu_layer , bool tap_select_only ) {
1499+ if (!menu_layer ) {
1500+ return ;
1501+ }
1502+ menu_layer -> tap_select_only = tap_select_only ;
1503+ }
1504+
14981505bool menu_layer_get_scroll_wrap_around (MenuLayer * menu_layer ) {
14991506 return menu_layer -> scroll_wrap_around ;
15001507}
@@ -1550,13 +1557,19 @@ void menu_layer_set_scroll_vibe_on_blocked(MenuLayer *menu_layer, bool scroll_vi
15501557// public per-menu gesture surface (and the unit-test entry points).
15511558//
15521559// Live scrolling happens on pan Updated. On a plain (non-center-focused) menu the selection is
1553- // frozen for the whole pan and never changes on liftoff — a pan scrolls, a tap selects. A
1554- // center-focused menu is a carousel instead: the focus stays pinned at the viewport centre, so the
1555- // row crossing the centre becomes the selection live during the pan (through the full
1556- // selection_will_change contract), and liftoff settles the selected row to the exact centre.
1557-
1558- // Two independent taps within this window on the same menu count as a "double tap" and activate the
1559- // last tap-selected row (there is no dedicated double-tap recognizer). CALIBRATION: ~300ms is the
1560+ // frozen for the whole pan and never changes on liftoff — a pan only scrolls. A center-focused
1561+ // menu is a carousel instead: the focus stays pinned at the viewport centre, so the row crossing
1562+ // the centre becomes the selection live during the pan (through the full selection_will_change
1563+ // contract), and liftoff settles the selected row to the exact centre.
1564+ //
1565+ // Taps differ by shape. A plain menu behaves like a phone list: a single tap on any row both
1566+ // selects it (selection_will_change honoured) and activates it — one tap opens the item. A
1567+ // carousel keeps the two-step model (a tap on an off-centre row centres it, a tap on the centred
1568+ // row activates), bridged by the double-tap window below.
1569+
1570+ // Two independent taps within this window on the same carousel count as a "double tap" and activate
1571+ // the last tap-selected row (there is no dedicated double-tap recognizer). Plain menus activate on
1572+ // the first tap, so the window only ever arms on center-focused menus. CALIBRATION: ~300ms is the
15601573// usual comfortable double-tap spacing; tune on hardware if it feels too eager/sluggish.
15611574#define DOUBLE_TAP_WINDOW_MS 300
15621575
@@ -1904,31 +1917,47 @@ void menu_layer_touch_handle_tap(MenuLayer *menu_layer, GPoint point_on_screen)
19041917 return ;
19051918 }
19061919
1907- // Priority 3 — select the tapped row through the full will_change contract (veto/redirect honoured)
1908- // and centre it, WITHOUT activating. A veto (final == old selection) must change nothing: it must
1909- // not re-centre the old selection (that mini-snap is exactly what Fix 1 removed from pans). Record
1910- // the double-tap state only when a selection actually changes (normal or redirect).
1920+ // Priority 3 — the tapped row is not the selection: run the full will_change contract
1921+ // (veto/redirect honoured). A veto (final == old selection) must change nothing: no select, no
1922+ // activate, no re-centre of the old selection (that mini-snap is exactly what Fix 1 removed from
1923+ // pans), no animation cancel, and the double-tap window stays as it was — cancelling would abort
1924+ // an in-flight highlight animation and violate "veto changes nothing", so the cancel lives past
1925+ // the veto check.
19111926 const MenuIndex old_index = menu_layer -> selection .index ;
19121927 const MenuIndex final = prv_menu_run_will_change (menu_layer , candidate );
19131928 if (menu_index_compare (& final , & old_index ) == 0 ) {
1914- // Vetoed: no select, no activate, no re-centre, no animation cancel, and the window stays as it
1915- // was. Cancelling here would abort an in-flight highlight animation and violate "veto changes
1916- // nothing", so the cancel lives in the select branch below.
19171929 return ;
19181930 }
19191931 prv_cancel_selection_animation (menu_layer );
1920- // Animated: a center-focused menu plays the same jump/bounce as a button step (the nudge-in,
1921- // half-way snap and bounce-out of prv_schedule_center_focus_animation); plain menus slide as
1922- // before.
1932+
1933+ if (!menu_layer -> center_focused ) {
1934+ // Plain menus behave like a phone list: the tap selects AND opens the row in one gesture.
1935+ // Commit without scrolling — the row is already visible under the finger, and a re-centre
1936+ // would visibly shift the content just as the activated window pushes. Activate only when the
1937+ // contract kept the tapped row: a redirect selects the redirected row without opening it
1938+ // (opening a row the finger never touched would be a misfire), so there activation stays a
1939+ // deliberate second tap (priority 2). tap_select_only menus (short-item action menus, whose
1940+ // rows hold several columns the row-granular hit-test cannot tell apart) keep that two-step
1941+ // model for every row.
1942+ menu_layer_set_selected_index (menu_layer , final , MenuRowAlignNone , false);
1943+ if (!menu_layer -> tap_select_only &&
1944+ menu_index_compare (& menu_layer -> selection .index , & candidate ) == 0 ) {
1945+ prv_menu_activate_selected (menu_layer );
1946+ }
1947+ return ;
1948+ }
1949+
1950+ // Carousel: select the tapped row and centre it WITHOUT activating — the same jump/bounce as a
1951+ // button step (the nudge-in, half-way snap and bounce-out of
1952+ // prv_schedule_center_focus_animation); opening is the second tap (or the double-tap window).
19231953 menu_layer_set_selected_index (menu_layer , final , MenuRowAlignCenter , true);
19241954 // Record the committed (range-clamped) selection, not the raw will_change output: a client that
19251955 // redirects to an out-of-range index would otherwise be handed that OOB index by a fast double tap
1926- // (priority 1), diverging from priority 2 which activates the clamped selection.index. On a
1927- // center-focused menu the index commit is deferred to the half-way point of the jump animation;
1928- // until then the committed target lives in animation.new_selection (a priority-1 tap during the
1929- // jump cancels it, which drives the animation to its end state and commits that same target).
1930- const bool jump_in_flight = menu_layer -> center_focused &&
1931- animation_is_scheduled (menu_layer -> animation .animation );
1956+ // (priority 1), diverging from priority 2 which activates the clamped selection.index. The index
1957+ // commit is deferred to the half-way point of the jump animation; until then the committed
1958+ // target lives in animation.new_selection (a priority-1 tap during the jump cancels it, which
1959+ // drives the animation to its end state and commits that same target).
1960+ const bool jump_in_flight = animation_is_scheduled (menu_layer -> animation .animation );
19321961 menu_layer -> last_selected_index = jump_in_flight ? menu_layer -> animation .new_selection .index
19331962 : menu_layer -> selection .index ;
19341963 prv_menu_set_last_select_ticks (menu_layer , now );
0 commit comments