@@ -43,6 +43,85 @@ static UiMenuFrame g_stack[8];
4343static int g_depth = 0 ;
4444static UiCtx g_ctx_overlay = {0 };
4545
46+ static int
47+ ui_frame_items_rows (const UiMenuFrame * f ) {
48+ int rows = 1 ;
49+ if (f && f -> h > 7 ) {
50+ rows = f -> h - 7 ;
51+ }
52+ if (rows < 1 ) {
53+ rows = 1 ;
54+ }
55+ return rows ;
56+ }
57+
58+ static int
59+ ui_first_enabled_idx (const NcMenuItem * items , size_t n ) {
60+ if (!items || n == 0 ) {
61+ return 0 ;
62+ }
63+ for (size_t i = 0 ; i < n ; i ++ ) {
64+ if (ui_is_enabled (& items [i ], & g_ctx_overlay )) {
65+ return (int )i ;
66+ }
67+ }
68+ return 0 ;
69+ }
70+
71+ static int
72+ ui_last_enabled_idx (const NcMenuItem * items , size_t n ) {
73+ if (!items || n == 0 ) {
74+ return 0 ;
75+ }
76+ for (size_t i = n ; i > 0 ; i -- ) {
77+ if (ui_is_enabled (& items [i - 1 ], & g_ctx_overlay )) {
78+ return (int )(i - 1 );
79+ }
80+ }
81+ return 0 ;
82+ }
83+
84+ static int
85+ ui_step_enabled (const NcMenuItem * items , size_t n , int from , int dir , int steps ) {
86+ if (!items || n == 0 ) {
87+ return 0 ;
88+ }
89+ if (steps < 1 ) {
90+ steps = 1 ;
91+ }
92+ const int delta = (dir < 0 ) ? -1 : 1 ;
93+ int idx = from ;
94+ for (int i = 0 ; i < steps ; i ++ ) {
95+ int next = idx ;
96+ while (1 ) {
97+ next += delta ;
98+ if (next < 0 || next >= (int )n ) {
99+ next = idx ;
100+ break ;
101+ }
102+ if (ui_is_enabled (& items [next ], & g_ctx_overlay )) {
103+ break ;
104+ }
105+ }
106+ if (next == idx ) {
107+ break ;
108+ }
109+ idx = next ;
110+ }
111+ return idx ;
112+ }
113+
114+ static void
115+ ui_frame_keep_highlight_visible (UiMenuFrame * f ) {
116+ if (!f || !f -> items || f -> n == 0 ) {
117+ return ;
118+ }
119+ int page_rows = ui_frame_items_rows (f );
120+ int vis_total = ui_visible_count_and_maxlab (f -> items , f -> n , & g_ctx_overlay , NULL );
121+ int hi_pos = ui_visible_index_for_item (f -> items , f -> n , & g_ctx_overlay , f -> hi );
122+ f -> top = ui_scroll_follow_selection (vis_total , page_rows , f -> top , hi_pos );
123+ }
124+
46125static void
47126ui_overlay_breadcrumb (char * buf , size_t n ) {
48127 if (!buf || n == 0 ) {
@@ -80,6 +159,65 @@ ui_overlay_close_all(void) {
80159 g_overlay_open = 0 ;
81160}
82161
162+ static void
163+ ui_overlay_pop_one (void ) {
164+ if (g_depth <= 0 ) {
165+ return ;
166+ }
167+ UiMenuFrame * cur = & g_stack [g_depth - 1 ];
168+ if (cur -> win ) {
169+ delwin (cur -> win );
170+ cur -> win = NULL ;
171+ }
172+ g_depth -- ;
173+ if (g_depth <= 0 ) {
174+ g_depth = 0 ;
175+ g_overlay_open = 0 ;
176+ }
177+ }
178+
179+ static int
180+ ui_menu_activate_current (void ) {
181+ if (!g_overlay_open || g_depth <= 0 ) {
182+ return 0 ;
183+ }
184+ UiMenuFrame * f = & g_stack [g_depth - 1 ];
185+ const NcMenuItem * it = & f -> items [f -> hi ];
186+ if (!ui_is_enabled (it , & g_ctx_overlay )) {
187+ return 1 ;
188+ }
189+ if (it -> submenu && it -> submenu_len > 0 ) {
190+ if (g_depth < (int )(sizeof g_stack / sizeof g_stack [0 ])) {
191+ UiMenuFrame * nf = & g_stack [g_depth ++ ];
192+ memset (nf , 0 , sizeof (* nf ));
193+ nf -> items = it -> submenu ;
194+ nf -> n = it -> submenu_len ;
195+ nf -> hi = ui_first_enabled_idx (nf -> items , nf -> n );
196+ nf -> top = 0 ;
197+ nf -> title = it -> label ? it -> label : it -> id ;
198+ ui_overlay_layout (nf , & g_ctx_overlay );
199+ }
200+ }
201+ if (it -> on_select ) {
202+ it -> on_select (& g_ctx_overlay );
203+ if (exitflag ) {
204+ ui_overlay_close_all ();
205+ return 1 ;
206+ }
207+ UiMenuFrame * cf = & g_stack [g_depth - 1 ];
208+ if (!ui_is_enabled (& cf -> items [cf -> hi ], & g_ctx_overlay )) {
209+ cf -> hi = ui_next_enabled (cf -> items , cf -> n , & g_ctx_overlay , cf -> hi , +1 );
210+ }
211+ ui_overlay_layout (cf , & g_ctx_overlay );
212+ ui_frame_keep_highlight_visible (cf );
213+ ui_overlay_recreate_if_needed (cf );
214+ }
215+ if (!it -> on_select && (!it -> submenu || it -> submenu_len == 0 ) && it -> help && * it -> help ) {
216+ ui_help_open (it -> help );
217+ }
218+ return 1 ;
219+ }
220+
83221void
84222ui_menu_open_async (dsd_opts * opts , dsd_state * state ) {
85223 // Initialize overlay context and push root menu
@@ -105,7 +243,8 @@ ui_menu_open_async(dsd_opts* opts, dsd_state* state) {
105243 memset (g_stack , 0 , sizeof (g_stack ));
106244 g_stack [0 ].items = items ;
107245 g_stack [0 ].n = n ;
108- g_stack [0 ].hi = 0 ;
246+ g_stack [0 ].hi = ui_first_enabled_idx (items , n );
247+ g_stack [0 ].top = 0 ;
109248 g_stack [0 ].title = "Main Menu" ;
110249 ui_overlay_layout (& g_stack [0 ], & g_ctx_overlay );
111250}
@@ -151,17 +290,45 @@ ui_menu_handle_key(int ch, dsd_opts* opts, dsd_state* state) {
151290 f -> win = NULL ;
152291 }
153292 ui_overlay_layout (f , & g_ctx_overlay );
293+ ui_frame_keep_highlight_visible (f );
154294 return 1 ;
155295 }
156296 if (ch == ERR ) {
157297 return 0 ;
158298 }
159299 if (ch == KEY_UP ) {
160300 f -> hi = ui_next_enabled (f -> items , f -> n , & g_ctx_overlay , f -> hi , -1 );
301+ ui_frame_keep_highlight_visible (f );
161302 return 1 ;
162303 }
163304 if (ch == KEY_DOWN ) {
164305 f -> hi = ui_next_enabled (f -> items , f -> n , & g_ctx_overlay , f -> hi , +1 );
306+ ui_frame_keep_highlight_visible (f );
307+ return 1 ;
308+ }
309+ if (ch == KEY_HOME ) {
310+ f -> hi = ui_first_enabled_idx (f -> items , f -> n );
311+ f -> top = 0 ;
312+ return 1 ;
313+ }
314+ if (ch == KEY_END ) {
315+ f -> hi = ui_last_enabled_idx (f -> items , f -> n );
316+ f -> top = ui_scroll_last_page_top (ui_visible_count_and_maxlab (f -> items , f -> n , & g_ctx_overlay , NULL ),
317+ ui_frame_items_rows (f ));
318+ return 1 ;
319+ }
320+ if (ch == KEY_PPAGE ) {
321+ int page = ui_scroll_page_step_from_rows (ui_frame_items_rows (f ));
322+ f -> hi = ui_step_enabled (f -> items , f -> n , f -> hi , -1 , page );
323+ f -> top -= page ;
324+ ui_frame_keep_highlight_visible (f );
325+ return 1 ;
326+ }
327+ if (ch == KEY_NPAGE ) {
328+ int page = ui_scroll_page_step_from_rows (ui_frame_items_rows (f ));
329+ f -> hi = ui_step_enabled (f -> items , f -> n , f -> hi , +1 , page );
330+ f -> top += page ;
331+ ui_frame_keep_highlight_visible (f );
165332 return 1 ;
166333 }
167334 if (ch == 'h' || ch == 'H' ) {
@@ -171,59 +338,16 @@ ui_menu_handle_key(int ch, dsd_opts* opts, dsd_state* state) {
171338 }
172339 return 1 ;
173340 }
174- if (ch == DSD_KEY_ESC || ch == 'q' || ch == 'Q' ) {
175- // Pop submenu or close root
341+ if (ch == KEY_LEFT || ch == DSD_KEY_ESC || ch == 'q' || ch == 'Q' ) {
176342 if (g_depth > 1 ) {
177- UiMenuFrame * cur = & g_stack [g_depth - 1 ];
178- if (cur -> win ) {
179- delwin (cur -> win );
180- cur -> win = NULL ;
181- }
182- g_depth -- ;
343+ ui_overlay_pop_one ();
183344 } else {
184345 ui_overlay_close_all ();
185346 }
186347 return 1 ;
187348 }
188- if (ch == 10 || ch == KEY_ENTER || ch == '\r' ) {
189- const NcMenuItem * it = & f -> items [f -> hi ];
190- if (!ui_is_enabled (it , & g_ctx_overlay )) {
191- return 1 ;
192- }
193- if (it -> submenu && it -> submenu_len > 0 ) {
194- if (g_depth < (int )(sizeof g_stack / sizeof g_stack [0 ])) {
195- UiMenuFrame * nf = & g_stack [g_depth ++ ];
196- memset (nf , 0 , sizeof (* nf ));
197- nf -> items = it -> submenu ;
198- nf -> n = it -> submenu_len ;
199- nf -> hi = 0 ;
200- nf -> title = it -> label ? it -> label : it -> id ;
201- if (!ui_is_enabled (& nf -> items [nf -> hi ], & g_ctx_overlay )) {
202- nf -> hi = ui_next_enabled (nf -> items , nf -> n , & g_ctx_overlay , nf -> hi , +1 );
203- }
204- ui_overlay_layout (nf , & g_ctx_overlay );
205- }
206- }
207- if (it -> on_select ) {
208- it -> on_select (& g_ctx_overlay );
209- if (exitflag ) {
210- // Let caller exit soon
211- ui_overlay_close_all ();
212- return 1 ;
213- }
214- // After a toggle or action, visible items may have changed.
215- // Ensure the highlight points at a visible item and recompute size.
216- UiMenuFrame * cf = & g_stack [g_depth - 1 ];
217- if (!ui_is_enabled (& cf -> items [cf -> hi ], & g_ctx_overlay )) {
218- cf -> hi = ui_next_enabled (cf -> items , cf -> n , & g_ctx_overlay , cf -> hi , +1 );
219- }
220- ui_overlay_layout (cf , & g_ctx_overlay );
221- ui_overlay_recreate_if_needed (cf );
222- }
223- if (!it -> on_select && (!it -> submenu || it -> submenu_len == 0 ) && it -> help && * it -> help ) {
224- ui_help_open (it -> help );
225- }
226- return 1 ;
349+ if (ch == KEY_RIGHT || ch == 10 || ch == KEY_ENTER || ch == '\r' ) {
350+ return ui_menu_activate_current ();
227351 }
228352 return 0 ;
229353}
@@ -256,12 +380,13 @@ ui_menu_tick(dsd_opts* opts, dsd_state* state) {
256380 }
257381 // Ensure window exists with up-to-date geometry
258382 ui_overlay_layout (f , & g_ctx_overlay );
383+ ui_frame_keep_highlight_visible (f );
259384 ui_overlay_recreate_if_needed (f );
260385 ui_overlay_ensure_window (f );
261386 if (!f -> win ) {
262387 return ;
263388 }
264389 char breadcrumb [256 ];
265390 ui_overlay_breadcrumb (breadcrumb , sizeof breadcrumb );
266- ui_draw_menu (f -> win , f -> items , f -> n , f -> hi , breadcrumb , & g_ctx_overlay );
391+ ui_draw_menu (f -> win , f -> items , f -> n , f -> hi , & f -> top , breadcrumb , & g_ctx_overlay );
267392}
0 commit comments