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

Last change on this file since d92b8e8f was d92b8e8f, checked in by Jiri Svoboda <jiri@…>, 16 months ago

Start menu support for passing input device ID (multiseat)

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