source: mainline/uspace/lib/ui/src/menu.c@ 24be331e

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

Create menu windows in the correct seat

Add a mechanism to set the seat of a new display window, UI window,
UI popup - input device ID. This is set to the ID of the device which
activated the menu (mouse, keyboard). The display server determines
the correct seat from there.

This makes sure clicking outside closes the correct pop-up window.

  • Property mode set to 100644
File size: 15.0 KB
Line 
1/*
2 * Copyright (c) 2023 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 <uchar.h>
46#include <ui/accel.h>
47#include <ui/control.h>
48#include <ui/paint.h>
49#include <ui/popup.h>
50#include <ui/menu.h>
51#include <ui/menubar.h>
52#include <ui/menuentry.h>
53#include <ui/resource.h>
54#include <ui/window.h>
55#include "../private/menubar.h"
56#include "../private/menu.h"
57#include "../private/resource.h"
58
59enum {
60 menu_frame_w = 4,
61 menu_frame_h = 4,
62 menu_frame_w_text = 2,
63 menu_frame_h_text = 1,
64 menu_frame_h_margin_text = 1
65};
66
67static void ui_menu_popup_close(ui_popup_t *, void *);
68static void ui_menu_popup_kbd(ui_popup_t *, void *, kbd_event_t *);
69static void ui_menu_popup_pos(ui_popup_t *, void *, pos_event_t *);
70static void ui_menu_key_press_unmod(ui_menu_t *, kbd_event_t *);
71
72static ui_popup_cb_t ui_menu_popup_cb = {
73 .close = ui_menu_popup_close,
74 .kbd = ui_menu_popup_kbd,
75 .pos = ui_menu_popup_pos
76};
77
78/** Create new menu.
79 *
80 * @param mbar Menu bar
81 * @param caption Caption
82 * @param rmenu Place to store pointer to new menu
83 * @return EOK on success, ENOMEM if out of memory
84 */
85errno_t ui_menu_create(ui_menu_bar_t *mbar, const char *caption,
86 ui_menu_t **rmenu)
87{
88 ui_menu_t *menu;
89
90 menu = calloc(1, sizeof(ui_menu_t));
91 if (menu == NULL)
92 return ENOMEM;
93
94 menu->caption = str_dup(caption);
95 if (menu->caption == NULL) {
96 free(menu);
97 return ENOMEM;
98 }
99
100 menu->mbar = mbar;
101 list_append(&menu->lmenus, &mbar->menus);
102 list_initialize(&menu->entries);
103
104 *rmenu = menu;
105 return EOK;
106}
107
108/** Destroy menu.
109 *
110 * @param menu Menu or @c NULL
111 */
112void ui_menu_destroy(ui_menu_t *menu)
113{
114 ui_menu_entry_t *mentry;
115
116 if (menu == NULL)
117 return;
118
119 /* Destroy entries */
120 mentry = ui_menu_entry_first(menu);
121 while (mentry != NULL) {
122 ui_menu_entry_destroy(mentry);
123 mentry = ui_menu_entry_first(menu);
124 }
125
126 list_remove(&menu->lmenus);
127 free(menu->caption);
128 free(menu);
129}
130
131/** Get first menu in menu bar.
132 *
133 * @param mbar Menu bar
134 * @return First menu or @c NULL if there is none
135 */
136ui_menu_t *ui_menu_first(ui_menu_bar_t *mbar)
137{
138 link_t *link;
139
140 link = list_first(&mbar->menus);
141 if (link == NULL)
142 return NULL;
143
144 return list_get_instance(link, ui_menu_t, lmenus);
145}
146
147/** Get next menu in menu bar.
148 *
149 * @param cur Current menu
150 * @return Next menu or @c NULL if @a cur is the last one
151 */
152ui_menu_t *ui_menu_next(ui_menu_t *cur)
153{
154 link_t *link;
155
156 link = list_next(&cur->lmenus, &cur->mbar->menus);
157 if (link == NULL)
158 return NULL;
159
160 return list_get_instance(link, ui_menu_t, lmenus);
161}
162
163/** Get last menu in menu bar.
164 *
165 * @param mbar Menu bar
166 * @return Last menu or @c NULL if there is none
167 */
168ui_menu_t *ui_menu_last(ui_menu_bar_t *mbar)
169{
170 link_t *link;
171
172 link = list_last(&mbar->menus);
173 if (link == NULL)
174 return NULL;
175
176 return list_get_instance(link, ui_menu_t, lmenus);
177}
178
179/** Get previous menu in menu bar.
180 *
181 * @param cur Current menu
182 * @return Previous menu or @c NULL if @a cur is the fist one
183 */
184ui_menu_t *ui_menu_prev(ui_menu_t *cur)
185{
186 link_t *link;
187
188 link = list_prev(&cur->lmenus, &cur->mbar->menus);
189 if (link == NULL)
190 return NULL;
191
192 return list_get_instance(link, ui_menu_t, lmenus);
193}
194
195/** Get menu caption.
196 *
197 * @param menu Menu
198 * @return Caption (owned by @a menu)
199 */
200const char *ui_menu_caption(ui_menu_t *menu)
201{
202 return menu->caption;
203}
204
205/** Get menu geometry.
206 *
207 * @param menu Menu
208 * @param spos Starting position
209 * @param geom Structure to fill in with computed geometry
210 */
211void ui_menu_get_geom(ui_menu_t *menu, gfx_coord2_t *spos,
212 ui_menu_geom_t *geom)
213{
214 ui_resource_t *res;
215 gfx_coord2_t edim;
216 gfx_coord_t frame_w;
217 gfx_coord_t frame_h;
218
219 res = ui_window_get_res(menu->mbar->window);
220
221 if (res->textmode) {
222 frame_w = menu_frame_w_text;
223 frame_h = menu_frame_h_text;
224 } else {
225 frame_w = menu_frame_w;
226 frame_h = menu_frame_h;
227 }
228
229 edim.x = ui_menu_entry_calc_width(menu, menu->max_caption_w,
230 menu->max_shortcut_w);
231 edim.y = menu->total_h;
232
233 geom->outer_rect.p0 = *spos;
234 geom->outer_rect.p1.x = spos->x + edim.x + 2 * frame_w;
235 geom->outer_rect.p1.y = spos->y + edim.y + 2 * frame_h;
236
237 geom->entries_rect.p0.x = spos->x + frame_w;
238 geom->entries_rect.p0.y = spos->y + frame_h;
239 geom->entries_rect.p1.x = geom->entries_rect.p0.x + edim.x;
240 geom->entries_rect.p1.y = geom->entries_rect.p0.x + edim.y;
241}
242
243/** Get menu rectangle.
244 *
245 * @param menu Menu
246 * @param spos Starting position (top-left corner)
247 * @param rect Place to store menu rectangle
248 */
249void ui_menu_get_rect(ui_menu_t *menu, gfx_coord2_t *spos, gfx_rect_t *rect)
250{
251 ui_menu_geom_t geom;
252
253 ui_menu_get_geom(menu, spos, &geom);
254 *rect = geom.outer_rect;
255}
256
257/** Get menu accelerator character.
258 *
259 * @param menu Menu
260 * @return Accelerator character (lowercase) or the null character if
261 * the menu has no accelerator.
262 */
263char32_t ui_menu_get_accel(ui_menu_t *menu)
264{
265 return ui_accel_get(menu->caption);
266}
267
268/** Get UI resource from menu.
269 *
270 * @param menu Menu
271 * @return UI resource
272 */
273ui_resource_t *ui_menu_get_res(ui_menu_t *menu)
274{
275 return ui_popup_get_res(menu->popup);
276}
277
278/** Open menu.
279 *
280 * @param menu Menu
281 * @param prect Parent rectangle around which the menu should be placed
282 * @param idev_id Input device associated with the menu's seat
283 */
284errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect, sysarg_t idev_id)
285{
286 ui_popup_t *popup = NULL;
287 ui_popup_params_t params;
288 ui_menu_geom_t geom;
289 gfx_coord2_t mpos;
290 errno_t rc;
291
292 /* Select first entry */
293 menu->selected = ui_menu_entry_first(menu);
294
295 /* Determine menu dimensions */
296
297 mpos.x = 0;
298 mpos.y = 0;
299 ui_menu_get_geom(menu, &mpos, &geom);
300
301 ui_popup_params_init(&params);
302 params.rect = geom.outer_rect;
303 params.place = *prect;
304 params.idev_id = idev_id;
305
306 rc = ui_popup_create(menu->mbar->ui, menu->mbar->window, &params,
307 &popup);
308 if (rc != EOK)
309 return rc;
310
311 menu->popup = popup;
312 ui_popup_set_cb(popup, &ui_menu_popup_cb, menu);
313
314 return ui_menu_paint(menu, &mpos);
315}
316
317/** Close menu.
318 *
319 * @param menu Menu
320 */
321void ui_menu_close(ui_menu_t *menu)
322{
323 ui_popup_destroy(menu->popup);
324 menu->popup = NULL;
325}
326
327/** Determine if menu is open.
328 *
329 * @param menu Menu
330 * @return @c true iff menu is open
331 */
332bool ui_menu_is_open(ui_menu_t *menu)
333{
334 return menu->popup != NULL;
335}
336
337/** Paint menu.
338 *
339 * @param menu Menu
340 * @param spos Starting position (top-left corner)
341 * @return EOK on success or an error code
342 */
343errno_t ui_menu_paint_bg_gfx(ui_menu_t *menu, gfx_coord2_t *spos)
344{
345 ui_resource_t *res;
346 ui_menu_geom_t geom;
347 gfx_rect_t bg_rect;
348 errno_t rc;
349
350 res = ui_menu_get_res(menu);
351 ui_menu_get_geom(menu, spos, &geom);
352
353 /* Paint menu frame */
354
355 rc = gfx_set_color(res->gc, res->wnd_face_color);
356 if (rc != EOK)
357 goto error;
358
359 rc = ui_paint_outset_frame(res, &geom.outer_rect, &bg_rect);
360 if (rc != EOK)
361 goto error;
362
363 /* Paint menu background */
364
365 rc = gfx_set_color(res->gc, res->wnd_face_color);
366 if (rc != EOK)
367 goto error;
368
369 rc = gfx_fill_rect(res->gc, &bg_rect);
370 if (rc != EOK)
371 goto error;
372
373 return EOK;
374error:
375 return rc;
376}
377
378/** Paint menu.
379 *
380 * @param menu Menu
381 * @param spos Starting position (top-left corner)
382 * @return EOK on success or an error code
383 */
384errno_t ui_menu_paint_bg_text(ui_menu_t *menu, gfx_coord2_t *spos)
385{
386 ui_resource_t *res;
387 ui_menu_geom_t geom;
388 gfx_rect_t rect;
389 errno_t rc;
390
391 res = ui_menu_get_res(menu);
392 ui_menu_get_geom(menu, spos, &geom);
393
394 /* Paint menu background */
395
396 rc = gfx_set_color(res->gc, res->wnd_face_color);
397 if (rc != EOK)
398 goto error;
399
400 rc = gfx_fill_rect(res->gc, &geom.outer_rect);
401 if (rc != EOK)
402 goto error;
403
404 /* Paint menu box */
405
406 rect = geom.outer_rect;
407 rect.p0.x += menu_frame_h_margin_text;
408 rect.p1.x -= menu_frame_h_margin_text;
409
410 rc = ui_paint_text_box(res, &rect, ui_box_single, res->wnd_face_color);
411 if (rc != EOK)
412 goto error;
413
414 return EOK;
415error:
416 return rc;
417}
418
419/** Paint menu.
420 *
421 * @param menu Menu
422 * @param spos Starting position (top-left corner)
423 * @return EOK on success or an error code
424 */
425errno_t ui_menu_paint(ui_menu_t *menu, gfx_coord2_t *spos)
426{
427 ui_resource_t *res;
428 gfx_coord2_t pos;
429 ui_menu_entry_t *mentry;
430 ui_menu_geom_t geom;
431 errno_t rc;
432
433 res = ui_menu_get_res(menu);
434 ui_menu_get_geom(menu, spos, &geom);
435
436 /* Paint menu frame and background */
437 if (res->textmode)
438 rc = ui_menu_paint_bg_text(menu, spos);
439 else
440 rc = ui_menu_paint_bg_gfx(menu, spos);
441 if (rc != EOK)
442 goto error;
443
444 /* Paint entries */
445 pos = geom.entries_rect.p0;
446
447 mentry = ui_menu_entry_first(menu);
448 while (mentry != NULL) {
449 rc = ui_menu_entry_paint(mentry, &pos);
450 if (rc != EOK)
451 goto error;
452
453 pos.y += ui_menu_entry_height(mentry);
454 mentry = ui_menu_entry_next(mentry);
455 }
456
457 rc = gfx_update(res->gc);
458 if (rc != EOK)
459 goto error;
460
461 return EOK;
462error:
463 return rc;
464}
465
466/** Handle position event in menu.
467 *
468 * @param menu Menu
469 * @param spos Starting position (top-left corner)
470 * @param event Position event
471 * @return ui_claimed iff the event was claimed
472 */
473ui_evclaim_t ui_menu_pos_event(ui_menu_t *menu, gfx_coord2_t *spos,
474 pos_event_t *event)
475{
476 ui_menu_geom_t geom;
477 ui_menu_entry_t *mentry;
478 gfx_coord2_t pos;
479 gfx_coord2_t epos;
480 ui_evclaim_t claimed;
481
482 ui_menu_get_geom(menu, spos, &geom);
483 epos.x = event->hpos;
484 epos.y = event->vpos;
485
486 pos = geom.entries_rect.p0;
487
488 mentry = ui_menu_entry_first(menu);
489 while (mentry != NULL) {
490 claimed = ui_menu_entry_pos_event(mentry, &pos, event);
491 if (claimed == ui_claimed)
492 return ui_claimed;
493
494 pos.y += ui_menu_entry_height(mentry);
495 mentry = ui_menu_entry_next(mentry);
496 }
497
498 /* Event inside menu? */
499 if (gfx_pix_inside_rect(&epos, &geom.outer_rect)) {
500 /* Claim event */
501 return ui_claimed;
502 } else {
503 /* Press outside menu - close it */
504 if (event->type == POS_PRESS)
505 ui_menu_bar_deactivate(menu->mbar);
506 }
507
508 return ui_unclaimed;
509}
510
511/** Handle keyboard event in menu.
512 *
513 * @param menu Menu
514 * @param event Keyboard event
515 * @return ui_claimed iff the event was claimed
516 */
517ui_evclaim_t ui_menu_kbd_event(ui_menu_t *menu, kbd_event_t *event)
518{
519 if (event->type == KEY_PRESS && (event->mods &
520 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
521 ui_menu_key_press_unmod(menu, event);
522 }
523
524 if (event->type == KEY_PRESS && (event->mods & KM_ALT) != 0 &&
525 (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0')
526 ui_menu_bar_press_accel(menu->mbar, event->c, event->kbd_id);
527
528 return ui_claimed;
529}
530
531/** Move one entry up.
532 *
533 * Non-selectable entries are skipped. If we are already at the top,
534 * we wrap around.
535 *
536 * @param menu Menu
537 */
538void ui_menu_up(ui_menu_t *menu)
539{
540 gfx_coord2_t mpos;
541 ui_menu_entry_t *nentry;
542
543 if (menu->selected == NULL)
544 return;
545
546 nentry = ui_menu_entry_prev(menu->selected);
547 if (nentry == NULL)
548 nentry = ui_menu_entry_last(menu);
549
550 /* Need to find a selectable entry */
551 while (!ui_menu_entry_selectable(nentry)) {
552 nentry = ui_menu_entry_prev(nentry);
553 if (nentry == NULL)
554 nentry = ui_menu_entry_last(menu);
555
556 /* Went completely around and found nothing? */
557 if (nentry == menu->selected)
558 return;
559 }
560
561 menu->selected = nentry;
562
563 mpos.x = 0;
564 mpos.y = 0;
565 (void) ui_menu_paint(menu, &mpos);
566}
567
568/** Move one entry down.
569 *
570 * Non-selectable entries are skipped. If we are already at the bottom,
571 * we wrap around.
572 *
573 * @param menu Menu
574 */
575void ui_menu_down(ui_menu_t *menu)
576{
577 gfx_coord2_t mpos;
578 ui_menu_entry_t *nentry;
579
580 if (menu->selected == NULL)
581 return;
582
583 nentry = ui_menu_entry_next(menu->selected);
584 if (nentry == NULL)
585 nentry = ui_menu_entry_first(menu);
586
587 /* Need to find a selectable entry */
588 while (!ui_menu_entry_selectable(nentry)) {
589 nentry = ui_menu_entry_next(nentry);
590 if (nentry == NULL)
591 nentry = ui_menu_entry_first(menu);
592
593 /* Went completely around and found nothing? */
594 if (nentry == menu->selected)
595 return;
596 }
597
598 menu->selected = nentry;
599
600 mpos.x = 0;
601 mpos.y = 0;
602 (void) ui_menu_paint(menu, &mpos);
603}
604
605/** Handle key press without modifiers in menu popup window.
606 *
607 * @param menu Menu
608 * @param event Keyboard event
609 */
610static void ui_menu_key_press_unmod(ui_menu_t *menu, kbd_event_t *event)
611{
612 ui_menu_entry_t *mentry;
613 char32_t c;
614
615 switch (event->key) {
616 case KC_ESCAPE:
617 ui_menu_bar_deactivate(menu->mbar);
618 break;
619 case KC_LEFT:
620 ui_menu_bar_left(menu->mbar, event->kbd_id);
621 break;
622 case KC_RIGHT:
623 ui_menu_bar_right(menu->mbar, event->kbd_id);
624 break;
625 case KC_UP:
626 ui_menu_up(menu);
627 break;
628 case KC_DOWN:
629 ui_menu_down(menu);
630 break;
631 case KC_ENTER:
632 if (menu->selected != NULL)
633 ui_menu_entry_activate(menu->selected);
634 break;
635 default:
636 if (event->c != '\0') {
637 mentry = ui_menu_entry_first(menu);
638 while (mentry != NULL) {
639 c = ui_menu_entry_get_accel(mentry);
640 if (c == event->c && menu->selected != NULL) {
641 ui_menu_entry_activate(mentry);
642 break;
643 }
644 mentry = ui_menu_entry_next(mentry);
645 }
646 }
647 break;
648 }
649}
650
651/** Handle close event in menu popup window.
652 *
653 * @param popup Menu popup window
654 * @param arg Argument (ui_menu_t *)
655 */
656static void ui_menu_popup_close(ui_popup_t *popup, void *arg)
657{
658 ui_menu_t *menu = (ui_menu_t *)arg;
659
660 /* Deactivate menu bar, close menu */
661 ui_menu_bar_deactivate(menu->mbar);
662}
663
664/** Handle keyboard event in menu popup window.
665 *
666 * @param popup Menu popup window
667 * @param arg Argument (ui_menu_t *)
668 * @param event Keyboard event
669 */
670static void ui_menu_popup_kbd(ui_popup_t *popup, void *arg, kbd_event_t *event)
671{
672 ui_menu_t *menu = (ui_menu_t *)arg;
673
674 ui_menu_kbd_event(menu, event);
675}
676
677/** Handle position event in menu popup window.
678 *
679 * @param popup Menu popup window
680 * @param arg Argument (ui_menu_t *)
681 * @param event Position event
682 */
683static void ui_menu_popup_pos(ui_popup_t *popup, void *arg, pos_event_t *event)
684{
685 ui_menu_t *menu = (ui_menu_t *)arg;
686 gfx_coord2_t spos;
687
688 spos.x = 0;
689 spos.y = 0;
690 ui_menu_pos_event(menu, &spos, event);
691}
692
693/** @}
694 */
Note: See TracBrowser for help on using the repository browser.