source: mainline/uspace/lib/ui/src/menu.c@ 5de852c

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5de852c was 5de852c, checked in by Jiri Svoboda <jiri@…>, 3 years ago

Coordinate keyboard event delivery between application and UI framework

If an application sets its own keyboard event handler, it needs to call
ui_window_def_kbd() to deliver events to the window's UI control tree.
Menubar should be able to capture these events so that they are not
accidentally acted upon by the application keyboard handler.

  • Property mode set to 100644
File size: 14.1 KB
Line 
1/*
2 * Copyright (c) 2022 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libui
30 * @{
31 */
32/**
33 * @file Menu
34 */
35
36#include <adt/list.h>
37#include <errno.h>
38#include <gfx/color.h>
39#include <gfx/context.h>
40#include <gfx/render.h>
41#include <gfx/text.h>
42#include <io/pos_event.h>
43#include <stdlib.h>
44#include <str.h>
45#include <ui/control.h>
46#include <ui/paint.h>
47#include <ui/popup.h>
48#include <ui/menu.h>
49#include <ui/menubar.h>
50#include <ui/menuentry.h>
51#include <ui/resource.h>
52#include <ui/window.h>
53#include "../private/menubar.h"
54#include "../private/menu.h"
55#include "../private/resource.h"
56
57enum {
58 menu_frame_w = 4,
59 menu_frame_h = 4,
60 menu_frame_w_text = 2,
61 menu_frame_h_text = 1,
62 menu_frame_h_margin_text = 1
63};
64
65static void ui_menu_popup_close(ui_popup_t *, void *);
66static void ui_menu_popup_kbd(ui_popup_t *, void *, kbd_event_t *);
67static void ui_menu_popup_pos(ui_popup_t *, void *, pos_event_t *);
68static void ui_menu_key_press_unmod(ui_menu_t *, kbd_event_t *);
69
70static ui_popup_cb_t ui_menu_popup_cb = {
71 .close = ui_menu_popup_close,
72 .kbd = ui_menu_popup_kbd,
73 .pos = ui_menu_popup_pos
74};
75
76/** Create new menu.
77 *
78 * @param mbar Menu bar
79 * @param caption Caption
80 * @param rmenu Place to store pointer to new menu
81 * @return EOK on success, ENOMEM if out of memory
82 */
83errno_t ui_menu_create(ui_menu_bar_t *mbar, const char *caption,
84 ui_menu_t **rmenu)
85{
86 ui_menu_t *menu;
87
88 menu = calloc(1, sizeof(ui_menu_t));
89 if (menu == NULL)
90 return ENOMEM;
91
92 menu->caption = str_dup(caption);
93 if (menu->caption == NULL) {
94 free(menu);
95 return ENOMEM;
96 }
97
98 menu->mbar = mbar;
99 list_append(&menu->lmenus, &mbar->menus);
100 list_initialize(&menu->entries);
101
102 *rmenu = menu;
103 return EOK;
104}
105
106/** Destroy menu.
107 *
108 * @param menu Menu or @c NULL
109 */
110void ui_menu_destroy(ui_menu_t *menu)
111{
112 ui_menu_entry_t *mentry;
113
114 if (menu == NULL)
115 return;
116
117 /* Destroy entries */
118 mentry = ui_menu_entry_first(menu);
119 while (mentry != NULL) {
120 ui_menu_entry_destroy(mentry);
121 mentry = ui_menu_entry_first(menu);
122 }
123
124 list_remove(&menu->lmenus);
125 free(menu->caption);
126 free(menu);
127}
128
129/** Get first menu in menu bar.
130 *
131 * @param mbar Menu bar
132 * @return First menu or @c NULL if there is none
133 */
134ui_menu_t *ui_menu_first(ui_menu_bar_t *mbar)
135{
136 link_t *link;
137
138 link = list_first(&mbar->menus);
139 if (link == NULL)
140 return NULL;
141
142 return list_get_instance(link, ui_menu_t, lmenus);
143}
144
145/** Get next menu in menu bar.
146 *
147 * @param cur Current menu
148 * @return Next menu or @c NULL if @a cur is the last one
149 */
150ui_menu_t *ui_menu_next(ui_menu_t *cur)
151{
152 link_t *link;
153
154 link = list_next(&cur->lmenus, &cur->mbar->menus);
155 if (link == NULL)
156 return NULL;
157
158 return list_get_instance(link, ui_menu_t, lmenus);
159}
160
161/** Get last menu in menu bar.
162 *
163 * @param mbar Menu bar
164 * @return Last menu or @c NULL if there is none
165 */
166ui_menu_t *ui_menu_last(ui_menu_bar_t *mbar)
167{
168 link_t *link;
169
170 link = list_last(&mbar->menus);
171 if (link == NULL)
172 return NULL;
173
174 return list_get_instance(link, ui_menu_t, lmenus);
175}
176
177/** Get previous menu in menu bar.
178 *
179 * @param cur Current menu
180 * @return Previous menu or @c NULL if @a cur is the fist one
181 */
182ui_menu_t *ui_menu_prev(ui_menu_t *cur)
183{
184 link_t *link;
185
186 link = list_prev(&cur->lmenus, &cur->mbar->menus);
187 if (link == NULL)
188 return NULL;
189
190 return list_get_instance(link, ui_menu_t, lmenus);
191}
192
193/** Get menu caption.
194 *
195 * @param menu Menu
196 * @return Caption (owned by @a menu)
197 */
198const char *ui_menu_caption(ui_menu_t *menu)
199{
200 return menu->caption;
201}
202
203/** Get menu geometry.
204 *
205 * @param menu Menu
206 * @param spos Starting position
207 * @param geom Structure to fill in with computed geometry
208 */
209void ui_menu_get_geom(ui_menu_t *menu, gfx_coord2_t *spos,
210 ui_menu_geom_t *geom)
211{
212 ui_resource_t *res;
213 gfx_coord2_t edim;
214 gfx_coord_t frame_w;
215 gfx_coord_t frame_h;
216
217 res = ui_window_get_res(menu->mbar->window);
218
219 if (res->textmode) {
220 frame_w = menu_frame_w_text;
221 frame_h = menu_frame_h_text;
222 } else {
223 frame_w = menu_frame_w;
224 frame_h = menu_frame_h;
225 }
226
227 edim.x = ui_menu_entry_calc_width(menu, menu->max_caption_w,
228 menu->max_shortcut_w);
229 edim.y = menu->total_h;
230
231 geom->outer_rect.p0 = *spos;
232 geom->outer_rect.p1.x = spos->x + edim.x + 2 * frame_w;
233 geom->outer_rect.p1.y = spos->y + edim.y + 2 * frame_h;
234
235 geom->entries_rect.p0.x = spos->x + frame_w;
236 geom->entries_rect.p0.y = spos->y + frame_h;
237 geom->entries_rect.p1.x = geom->entries_rect.p0.x + edim.x;
238 geom->entries_rect.p1.y = geom->entries_rect.p0.x + edim.y;
239}
240
241/** Get menu rectangle.
242 *
243 * @param menu Menu
244 * @param spos Starting position (top-left corner)
245 * @param rect Place to store menu rectangle
246 */
247void ui_menu_get_rect(ui_menu_t *menu, gfx_coord2_t *spos, gfx_rect_t *rect)
248{
249 ui_menu_geom_t geom;
250
251 ui_menu_get_geom(menu, spos, &geom);
252 *rect = geom.outer_rect;
253}
254
255/** Get UI resource from menu.
256 *
257 * @param menu Menu
258 * @return UI resource
259 */
260ui_resource_t *ui_menu_get_res(ui_menu_t *menu)
261{
262 return ui_popup_get_res(menu->popup);
263}
264
265/** Open menu.
266 *
267 * @param menu Menu
268 * @param prect Parent rectangle around which the menu should be placed
269 */
270errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect)
271{
272 ui_popup_t *popup = NULL;
273 ui_popup_params_t params;
274 ui_menu_geom_t geom;
275 gfx_coord2_t mpos;
276 errno_t rc;
277
278 /* Select first entry */
279 menu->selected = ui_menu_entry_first(menu);
280
281 /* Determine menu dimensions */
282
283 mpos.x = 0;
284 mpos.y = 0;
285 ui_menu_get_geom(menu, &mpos, &geom);
286
287 ui_popup_params_init(&params);
288 params.rect = geom.outer_rect;
289 params.place = *prect;
290
291 rc = ui_popup_create(menu->mbar->ui, menu->mbar->window, &params,
292 &popup);
293 if (rc != EOK)
294 return rc;
295
296 menu->popup = popup;
297 ui_popup_set_cb(popup, &ui_menu_popup_cb, menu);
298
299 return ui_menu_paint(menu, &mpos);
300}
301
302/** Close menu.
303 *
304 * @param menu Menu
305 */
306void ui_menu_close(ui_menu_t *menu)
307{
308 ui_popup_destroy(menu->popup);
309 menu->popup = NULL;
310}
311
312/** Determine if menu is open.
313 *
314 * @param menu Menu
315 * @return @c true iff menu is open
316 */
317bool ui_menu_is_open(ui_menu_t *menu)
318{
319 return menu->popup != NULL;
320}
321
322/** Paint menu.
323 *
324 * @param menu Menu
325 * @param spos Starting position (top-left corner)
326 * @return EOK on success or an error code
327 */
328errno_t ui_menu_paint_bg_gfx(ui_menu_t *menu, gfx_coord2_t *spos)
329{
330 ui_resource_t *res;
331 ui_menu_geom_t geom;
332 gfx_rect_t bg_rect;
333 errno_t rc;
334
335 res = ui_menu_get_res(menu);
336 ui_menu_get_geom(menu, spos, &geom);
337
338 /* Paint menu frame */
339
340 rc = gfx_set_color(res->gc, res->wnd_face_color);
341 if (rc != EOK)
342 goto error;
343
344 rc = ui_paint_outset_frame(res, &geom.outer_rect, &bg_rect);
345 if (rc != EOK)
346 goto error;
347
348 /* Paint menu background */
349
350 rc = gfx_set_color(res->gc, res->wnd_face_color);
351 if (rc != EOK)
352 goto error;
353
354 rc = gfx_fill_rect(res->gc, &bg_rect);
355 if (rc != EOK)
356 goto error;
357
358 return EOK;
359error:
360 return rc;
361}
362
363/** Paint menu.
364 *
365 * @param menu Menu
366 * @param spos Starting position (top-left corner)
367 * @return EOK on success or an error code
368 */
369errno_t ui_menu_paint_bg_text(ui_menu_t *menu, gfx_coord2_t *spos)
370{
371 ui_resource_t *res;
372 ui_menu_geom_t geom;
373 gfx_rect_t rect;
374 errno_t rc;
375
376 res = ui_menu_get_res(menu);
377 ui_menu_get_geom(menu, spos, &geom);
378
379 /* Paint menu background */
380
381 rc = gfx_set_color(res->gc, res->wnd_face_color);
382 if (rc != EOK)
383 goto error;
384
385 rc = gfx_fill_rect(res->gc, &geom.outer_rect);
386 if (rc != EOK)
387 goto error;
388
389 /* Paint menu box */
390
391 rect = geom.outer_rect;
392 rect.p0.x += menu_frame_h_margin_text;
393 rect.p1.x -= menu_frame_h_margin_text;
394
395 rc = ui_paint_text_box(res, &rect, ui_box_single, res->wnd_face_color);
396 if (rc != EOK)
397 goto error;
398
399 return EOK;
400error:
401 return rc;
402}
403
404/** Paint menu.
405 *
406 * @param menu Menu
407 * @param spos Starting position (top-left corner)
408 * @return EOK on success or an error code
409 */
410errno_t ui_menu_paint(ui_menu_t *menu, gfx_coord2_t *spos)
411{
412 ui_resource_t *res;
413 gfx_coord2_t pos;
414 ui_menu_entry_t *mentry;
415 ui_menu_geom_t geom;
416 errno_t rc;
417
418 res = ui_menu_get_res(menu);
419 ui_menu_get_geom(menu, spos, &geom);
420
421 /* Paint menu frame and background */
422 if (res->textmode)
423 rc = ui_menu_paint_bg_text(menu, spos);
424 else
425 rc = ui_menu_paint_bg_gfx(menu, spos);
426 if (rc != EOK)
427 goto error;
428
429 /* Paint entries */
430 pos = geom.entries_rect.p0;
431
432 mentry = ui_menu_entry_first(menu);
433 while (mentry != NULL) {
434 rc = ui_menu_entry_paint(mentry, &pos);
435 if (rc != EOK)
436 goto error;
437
438 pos.y += ui_menu_entry_height(mentry);
439 mentry = ui_menu_entry_next(mentry);
440 }
441
442 rc = gfx_update(res->gc);
443 if (rc != EOK)
444 goto error;
445
446 return EOK;
447error:
448 return rc;
449}
450
451/** Handle position event in menu.
452 *
453 * @param menu Menu
454 * @param spos Starting position (top-left corner)
455 * @param event Position event
456 * @return ui_claimed iff the event was claimed
457 */
458ui_evclaim_t ui_menu_pos_event(ui_menu_t *menu, gfx_coord2_t *spos,
459 pos_event_t *event)
460{
461 ui_menu_geom_t geom;
462 ui_menu_entry_t *mentry;
463 gfx_coord2_t pos;
464 gfx_coord2_t epos;
465 ui_evclaim_t claimed;
466
467 ui_menu_get_geom(menu, spos, &geom);
468 epos.x = event->hpos;
469 epos.y = event->vpos;
470
471 pos = geom.entries_rect.p0;
472
473 mentry = ui_menu_entry_first(menu);
474 while (mentry != NULL) {
475 claimed = ui_menu_entry_pos_event(mentry, &pos, event);
476 if (claimed == ui_claimed)
477 return ui_claimed;
478
479 pos.y += ui_menu_entry_height(mentry);
480 mentry = ui_menu_entry_next(mentry);
481 }
482
483 /* Event inside menu? */
484 if (gfx_pix_inside_rect(&epos, &geom.outer_rect)) {
485 /* Claim event */
486 return ui_claimed;
487 } else {
488 /* Press outside menu - close it */
489 if (event->type == POS_PRESS)
490 ui_menu_bar_deactivate(menu->mbar);
491 }
492
493 return ui_unclaimed;
494}
495
496/** Handle keyboard event in menu.
497 *
498 * @param menu Menu
499 * @param event Keyboard event
500 * @return ui_claimed iff the event was claimed
501 */
502ui_evclaim_t ui_menu_kbd_event(ui_menu_t *menu, kbd_event_t *event)
503{
504 if (event->type == KEY_PRESS && (event->mods &
505 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
506 ui_menu_key_press_unmod(menu, event);
507 }
508
509 return ui_claimed;
510}
511
512/** Move one entry up.
513 *
514 * Non-selectable entries are skipped. If we are already at the top,
515 * we wrap around.
516 *
517 * @param menu Menu
518 */
519void ui_menu_up(ui_menu_t *menu)
520{
521 gfx_coord2_t mpos;
522 ui_menu_entry_t *nentry;
523
524 if (menu->selected == NULL)
525 return;
526
527 nentry = ui_menu_entry_prev(menu->selected);
528 if (nentry == NULL)
529 nentry = ui_menu_entry_last(menu);
530
531 /* Need to find a selectable entry */
532 while (!ui_menu_entry_selectable(nentry)) {
533 nentry = ui_menu_entry_prev(nentry);
534 if (nentry == NULL)
535 nentry = ui_menu_entry_last(menu);
536
537 /* Went completely around and found nothing? */
538 if (nentry == menu->selected)
539 return;
540 }
541
542 menu->selected = nentry;
543
544 mpos.x = 0;
545 mpos.y = 0;
546 (void) ui_menu_paint(menu, &mpos);
547}
548
549/** Move one entry down.
550 *
551 * Non-selectable entries are skipped. If we are already at the bottom,
552 * we wrap around.
553 *
554 * @param menu Menu
555 */
556void ui_menu_down(ui_menu_t *menu)
557{
558 gfx_coord2_t mpos;
559 ui_menu_entry_t *nentry;
560
561 if (menu->selected == NULL)
562 return;
563
564 nentry = ui_menu_entry_next(menu->selected);
565 if (nentry == NULL)
566 nentry = ui_menu_entry_first(menu);
567
568 /* Need to find a selectable entry */
569 while (!ui_menu_entry_selectable(nentry)) {
570 nentry = ui_menu_entry_next(nentry);
571 if (nentry == NULL)
572 nentry = ui_menu_entry_first(menu);
573
574 /* Went completely around and found nothing? */
575 if (nentry == menu->selected)
576 return;
577 }
578
579 menu->selected = nentry;
580
581 mpos.x = 0;
582 mpos.y = 0;
583 (void) ui_menu_paint(menu, &mpos);
584}
585
586/** Handle key press without modifiers in menu popup window.
587 *
588 * @param menu Menu
589 * @param event Keyboard event
590 */
591static void ui_menu_key_press_unmod(ui_menu_t *menu, kbd_event_t *event)
592{
593 switch (event->key) {
594 case KC_ESCAPE:
595 ui_menu_bar_deactivate(menu->mbar);
596 break;
597 case KC_LEFT:
598 ui_menu_bar_left(menu->mbar);
599 break;
600 case KC_RIGHT:
601 ui_menu_bar_right(menu->mbar);
602 break;
603 case KC_UP:
604 ui_menu_up(menu);
605 break;
606 case KC_DOWN:
607 ui_menu_down(menu);
608 break;
609 case KC_ENTER:
610 if (menu->selected != NULL)
611 ui_menu_entry_activate(menu->selected);
612 break;
613 default:
614 break;
615 }
616}
617
618/** Handle close event in menu popup window.
619 *
620 * @param popup Menu popup window
621 * @param arg Argument (ui_menu_t *)
622 */
623static void ui_menu_popup_close(ui_popup_t *popup, void *arg)
624{
625 ui_menu_t *menu = (ui_menu_t *)arg;
626
627 /* Deactivate menu bar, close menu */
628 ui_menu_bar_deactivate(menu->mbar);
629}
630
631/** Handle keyboard event in menu popup window.
632 *
633 * @param popup Menu popup window
634 * @param arg Argument (ui_menu_t *)
635 * @param event Keyboard event
636 */
637static void ui_menu_popup_kbd(ui_popup_t *popup, void *arg, kbd_event_t *event)
638{
639 ui_menu_t *menu = (ui_menu_t *)arg;
640
641 ui_menu_kbd_event(menu, event);
642}
643
644/** Handle position event in menu popup window.
645 *
646 * @param popup Menu popup window
647 * @param arg Argument (ui_menu_t *)
648 * @param event Position event
649 */
650static void ui_menu_popup_pos(ui_popup_t *popup, void *arg, pos_event_t *event)
651{
652 ui_menu_t *menu = (ui_menu_t *)arg;
653 gfx_coord2_t spos;
654
655 spos.x = 0;
656 spos.y = 0;
657 ui_menu_pos_event(menu, &spos, event);
658}
659
660/** @}
661 */
Note: See TracBrowser for help on using the repository browser.