2121#include "system/logging.h"
2222#include "system/passert.h"
2323#include "util/math.h"
24+ #include "util/size.h"
25+ #include "vibes.h"
2426
2527#include <string.h>
2628
@@ -76,16 +78,103 @@ static void prv_menu_select_long_click_handler(ClickRecognizerRef recognizer,
7678 }
7779}
7880
81+ static inline uint16_t prv_menu_layer_get_num_sections (MenuLayer * menu_layer );
82+ static inline uint16_t prv_menu_layer_get_num_rows (MenuLayer * menu_layer , uint16_t section_index );
83+
84+ static bool prv_menu_index_is_first_index (MenuLayer * menu_layer , const MenuIndex * index ) {
85+ (void )menu_layer ;
86+
87+ MenuIndex first_index = MenuIndex (0 , 0 );
88+ return menu_index_compare (index , & first_index ) == 0 ;
89+ }
90+
91+ static bool prv_menu_index_is_last_index (MenuLayer * menu_layer , const MenuIndex * index ) {
92+ int last_index_section = prv_menu_layer_get_num_sections (menu_layer ) - 1 ;
93+ int last_index_row = prv_menu_layer_get_num_rows (menu_layer , last_index_section ) - 1 ;
94+ MenuIndex last_index = MenuIndex (last_index_section , last_index_row );
95+ return menu_index_compare (index , & last_index ) == 0 ;
96+ }
97+
98+ static void prv_vibe_pulse (void ) {
99+ uint32_t const segments [] = { 50 };
100+ VibePattern pat = {
101+ .durations = segments ,
102+ .num_segments = ARRAY_LENGTH (segments ),
103+ };
104+ vibes_enqueue_custom_pattern (pat );
105+ }
106+
107+ //! Handle the menu scroll wrap around
108+ //! @param menu_layer reference to the current MenuLayer
109+ //! @param recognizer reference to the ClickRecognizer struct
110+ //! @param scrolling_up `true` if scrolling up, `false` if scrolling down
111+ //! @return `true` if a wrap around has been applied
112+ static bool prv_menu_scroll_handle_wrap_around (MenuLayer * menu_layer , ClickRecognizerRef recognizer , bool scrolling_up ) {
113+ const uint8_t current_scroll_action = scrolling_up ? MenuLayerRepeatScrollingUp : MenuLayerRepeatScrollingDown ;
114+ const bool is_repeating = click_recognizer_is_repeating (recognizer );
115+
116+ if (is_repeating ) {
117+ menu_layer -> cache .button_repeat_scrolling = current_scroll_action ;
118+ if (!menu_layer -> scroll_force_wrap_on_repeat ) {
119+ return false;
120+ }
121+ }
122+ menu_layer -> cache .button_repeat_scrolling = MenuLayerNoRepeatScrolling ;
123+
124+ MenuIndex current_index = menu_layer -> selection .index ;
125+ int last_index_section = prv_menu_layer_get_num_sections (menu_layer ) - 1 ;
126+ int last_index_row = prv_menu_layer_get_num_rows (menu_layer , last_index_section ) - 1 ;
127+ MenuIndex first_index = MenuIndex (0 , 0 );
128+ MenuIndex last_index = MenuIndex (last_index_section , last_index_row );
129+ MenuIndex * wraparound_dest_index ;
130+ if ((menu_index_compare (& current_index , & first_index ) == 0 ) && scrolling_up ) {
131+ wraparound_dest_index = & last_index ;
132+ } else if ((menu_index_compare (& current_index , & last_index ) == 0 ) && !scrolling_up ) {
133+ wraparound_dest_index = & first_index ;
134+ } else {
135+ return false;
136+ }
137+
138+ const bool animated = true;
139+ menu_layer_set_selected_index (menu_layer , * wraparound_dest_index , MenuRowAlignCenter , animated );
140+ if (menu_layer -> scroll_vibe_on_wrap_around ) {
141+ prv_vibe_pulse ();
142+ }
143+ return true;
144+ }
145+
79146void menu_up_click_handler (ClickRecognizerRef recognizer , MenuLayer * menu_layer ) {
80147 const bool up = true;
148+ if (menu_layer -> scroll_wrap_around && prv_menu_scroll_handle_wrap_around (menu_layer , recognizer , up )) {
149+ return ;
150+ }
151+
152+ MenuIndex prev_index = menu_layer -> selection .index ;
81153 const bool animated = true;
82154 menu_layer_set_selected_next (menu_layer , up , MenuRowAlignCenter , animated );
155+ MenuIndex current_index = menu_layer -> selection .index ;
156+ if ((menu_layer -> scroll_vibe_on_blocked ) &&
157+ (menu_index_compare (& current_index , & prev_index ) == 0 ) &&
158+ (prv_menu_index_is_first_index (menu_layer , & current_index ))) {
159+ prv_vibe_pulse ();
160+ }
83161}
84162
85163void menu_down_click_handler (ClickRecognizerRef recognizer , MenuLayer * menu_layer ) {
86164 const bool up = false;
165+ if (menu_layer -> scroll_wrap_around && prv_menu_scroll_handle_wrap_around (menu_layer , recognizer , up )) {
166+ return ;
167+ }
168+
169+ MenuIndex prev_index = menu_layer -> selection .index ;
87170 const bool animated = true;
88171 menu_layer_set_selected_next (menu_layer , up , MenuRowAlignCenter , animated );
172+ MenuIndex current_index = menu_layer -> selection .index ;
173+ if ((menu_layer -> scroll_vibe_on_blocked ) &&
174+ (menu_index_compare (& current_index , & prev_index ) == 0 ) &&
175+ (prv_menu_index_is_last_index (menu_layer , & current_index ))) {
176+ prv_vibe_pulse ();
177+ }
89178}
90179
91180static void prv_menu_click_config_provider (MenuLayer * menu_layer ) {
@@ -1293,3 +1382,46 @@ void menu_layer_set_center_focused(MenuLayer *menu_layer, bool center_focused) {
12931382 prv_set_center_focused (menu_layer , center_focused );
12941383 menu_layer_update_caches (menu_layer );
12951384}
1385+
1386+ bool menu_layer_get_scroll_wrap_around (MenuLayer * menu_layer ) {
1387+ return menu_layer -> scroll_wrap_around ;
1388+ }
1389+
1390+ void menu_layer_set_scroll_wrap_around (MenuLayer * menu_layer , bool scroll_wrap_around ) {
1391+ if (!menu_layer ) {
1392+ return ;
1393+ }
1394+ menu_layer -> scroll_wrap_around = scroll_wrap_around ;
1395+ }
1396+
1397+ uint8_t menu_layer_get_scroll_vibe_behavior (MenuLayer * menu_layer ) {
1398+ if (menu_layer -> scroll_vibe_on_blocked ) {
1399+ return 2 ;
1400+ } else if (menu_layer -> scroll_vibe_on_wrap_around ) {
1401+ return 1 ;
1402+ } else {
1403+ return 0 ;
1404+ }
1405+ }
1406+
1407+ void menu_layer_set_scroll_vibe_on_wrap (MenuLayer * menu_layer , bool scroll_vibe_on_wrap ) {
1408+ if (!menu_layer ) {
1409+ return ;
1410+ }
1411+
1412+ if (scroll_vibe_on_wrap ) {
1413+ menu_layer -> scroll_vibe_on_blocked = false;
1414+ }
1415+ menu_layer -> scroll_vibe_on_wrap_around = scroll_vibe_on_wrap ;
1416+ }
1417+
1418+ void menu_layer_set_scroll_vibe_on_blocked (MenuLayer * menu_layer , bool scroll_vibe_on_blocked ) {
1419+ if (!menu_layer ) {
1420+ return ;
1421+ }
1422+
1423+ if (scroll_vibe_on_blocked ) {
1424+ menu_layer -> scroll_vibe_on_wrap_around = false;
1425+ }
1426+ menu_layer -> scroll_vibe_on_blocked = scroll_vibe_on_blocked ;
1427+ }
0 commit comments