source: mainline/uspace/lib/ui/src/menubar.c@ 5afc1aa

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5afc1aa was 8d1bcd7, checked in by Jiri Svoboda <jiri@…>, 21 months ago

Handover between system menu and menu bar

We can move between them using Left and Right keys in either closed
or open state. One can now open system menu with F10, Left, Down in
a window with menu bar, or just F10 in a window without menu bar.

  • Property mode set to 100644
File size: 14.8 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 bar
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 <ui/control.h>
47#include <ui/paint.h>
48#include <ui/menubar.h>
49#include <ui/menudd.h>
50#include <ui/wdecor.h>
51#include <ui/window.h>
52#include "../private/menubar.h"
53#include "../private/resource.h"
54#include "../private/wdecor.h"
55#include "../private/window.h"
56
57enum {
58 menubar_hpad = 4,
59 menubar_vpad = 4,
60 menubar_hpad_text = 1,
61 menubar_vpad_text = 0
62};
63
64static void ui_menu_bar_ctl_destroy(void *);
65static errno_t ui_menu_bar_ctl_paint(void *);
66static ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *, kbd_event_t *);
67static ui_evclaim_t ui_menu_bar_ctl_pos_event(void *, pos_event_t *);
68
69/** Menu bar control ops */
70ui_control_ops_t ui_menu_bar_ops = {
71 .destroy = ui_menu_bar_ctl_destroy,
72 .paint = ui_menu_bar_ctl_paint,
73 .kbd_event = ui_menu_bar_ctl_kbd_event,
74 .pos_event = ui_menu_bar_ctl_pos_event
75};
76
77/** Create new menu bar.
78 *
79 * @param ui UI
80 * @param window Window that will contain the menu bar
81 * @param rmbar Place to store pointer to new menu bar
82 * @return EOK on success, ENOMEM if out of memory
83 */
84errno_t ui_menu_bar_create(ui_t *ui, ui_window_t *window, ui_menu_bar_t **rmbar)
85{
86 ui_menu_bar_t *mbar;
87 errno_t rc;
88
89 mbar = calloc(1, sizeof(ui_menu_bar_t));
90 if (mbar == NULL)
91 return ENOMEM;
92
93 rc = ui_control_new(&ui_menu_bar_ops, (void *) mbar, &mbar->control);
94 if (rc != EOK) {
95 free(mbar);
96 return rc;
97 }
98
99 mbar->ui = ui;
100 mbar->window = window;
101 list_initialize(&mbar->menudds);
102
103 if (window->mbar == NULL)
104 window->mbar = mbar;
105
106 *rmbar = mbar;
107 return EOK;
108}
109
110/** Destroy menu bar
111 *
112 * @param mbar Menu bar or @c NULL
113 */
114void ui_menu_bar_destroy(ui_menu_bar_t *mbar)
115{
116 ui_menu_dd_t *mdd;
117
118 if (mbar == NULL)
119 return;
120
121 if (mbar->window->mbar == mbar)
122 mbar->window->mbar = NULL;
123
124 /* Destroy menu drop-downs */
125 mdd = ui_menu_dd_first(mbar);
126 while (mdd != NULL) {
127 ui_menu_dd_destroy(mdd);
128 mdd = ui_menu_dd_first(mbar);
129 }
130
131 ui_control_delete(mbar->control);
132 free(mbar);
133}
134
135/** Get base control from menu bar.
136 *
137 * @param mbar Menu bar
138 * @return Control
139 */
140ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *mbar)
141{
142 return mbar->control;
143}
144
145/** Set menu bar rectangle.
146 *
147 * @param mbar Menu bar
148 * @param rect New menu bar rectangle
149 */
150void ui_menu_bar_set_rect(ui_menu_bar_t *mbar, gfx_rect_t *rect)
151{
152 mbar->rect = *rect;
153}
154
155/** Paint menu bar.
156 *
157 * @param mbar Menu bar
158 * @return EOK on success or an error code
159 */
160errno_t ui_menu_bar_paint(ui_menu_bar_t *mbar)
161{
162 ui_resource_t *res;
163 ui_text_fmt_t fmt;
164 gfx_coord2_t pos;
165 gfx_coord2_t tpos;
166 gfx_rect_t rect;
167 gfx_color_t *bg_color;
168 ui_menu_dd_t *mdd;
169 const char *caption;
170 gfx_coord_t width;
171 gfx_coord_t hpad;
172 gfx_coord_t vpad;
173 errno_t rc;
174
175 res = ui_window_get_res(mbar->window);
176
177 /* Paint menu bar background */
178
179 rc = gfx_set_color(res->gc, res->wnd_face_color);
180 if (rc != EOK)
181 goto error;
182
183 rc = gfx_fill_rect(res->gc, &mbar->rect);
184 if (rc != EOK)
185 goto error;
186
187 if (res->textmode) {
188 hpad = menubar_hpad_text;
189 vpad = menubar_vpad_text;
190 } else {
191 hpad = menubar_hpad;
192 vpad = menubar_vpad;
193 }
194
195 pos = mbar->rect.p0;
196
197 ui_text_fmt_init(&fmt);
198 fmt.font = res->font;
199 fmt.halign = gfx_halign_left;
200 fmt.valign = gfx_valign_top;
201
202 mdd = ui_menu_dd_first(mbar);
203 while (mdd != NULL) {
204 caption = ui_menu_dd_caption(mdd);
205 width = ui_text_width(res->font, caption) + 2 * hpad;
206 tpos.x = pos.x + hpad;
207 tpos.y = pos.y + vpad;
208
209 rect.p0 = pos;
210 rect.p1.x = rect.p0.x + width;
211 rect.p1.y = mbar->rect.p1.y;
212
213 if (mdd == mbar->selected) {
214 fmt.color = res->wnd_sel_text_color;
215 fmt.hgl_color = res->wnd_sel_text_hgl_color;
216 bg_color = res->wnd_sel_text_bg_color;
217 } else {
218 fmt.color = res->wnd_text_color;
219 fmt.hgl_color = res->wnd_text_hgl_color;
220 bg_color = res->wnd_face_color;
221 }
222
223 rc = gfx_set_color(res->gc, bg_color);
224 if (rc != EOK)
225 goto error;
226
227 rc = gfx_fill_rect(res->gc, &rect);
228 if (rc != EOK)
229 goto error;
230
231 rc = ui_paint_text(&tpos, &fmt, caption);
232 if (rc != EOK)
233 goto error;
234
235 pos.x += width;
236 mdd = ui_menu_dd_next(mdd);
237 }
238
239 rc = gfx_update(res->gc);
240 if (rc != EOK)
241 goto error;
242
243 return EOK;
244error:
245 return rc;
246}
247
248/** Select or deselect menu from menu bar.
249 *
250 * Select @a menu. If @a menu is @c NULL or it is already selected,
251 * then select none.
252 *
253 * @param mbar Menu bar
254 * @param mdd Menu drop-down to select (or deselect if selected) or @c NULL
255 * @param openup Open menu even if not currently open
256 * @param idev_id Input device ID associated with the selecting seat
257 */
258void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd, bool openup,
259 sysarg_t idev_id)
260{
261 ui_menu_dd_t *old_mdd;
262 gfx_rect_t rect;
263 bool was_open;
264
265 old_mdd = mbar->selected;
266
267 mbar->selected = mdd;
268
269 /* Close previously open menu drop-down */
270 if (old_mdd != NULL && ui_menu_dd_is_open(old_mdd)) {
271 was_open = true;
272 (void) ui_menu_dd_close(old_mdd);
273 } else {
274 was_open = false;
275 }
276
277 (void) ui_menu_bar_paint(mbar);
278
279 if (mbar->selected != NULL) {
280 ui_menu_bar_entry_rect(mbar, mbar->selected, &rect);
281 if (openup || was_open) {
282 /*
283 * Open the newly selected menu drop-down if either
284 * the old menu drop-down was open or @a openup was
285 * specified.
286 */
287 (void) ui_menu_dd_open(mbar->selected, &rect, idev_id);
288 }
289
290 mbar->active = true;
291 } else {
292 mbar->active = false;
293 }
294}
295
296void ui_menu_bar_select_first(ui_menu_bar_t *mbar, bool openup,
297 sysarg_t idev_id)
298{
299 ui_menu_dd_t *mdd;
300
301 mdd = ui_menu_dd_first(mbar);
302 ui_menu_bar_select(mbar, mdd, openup, idev_id);
303}
304
305void ui_menu_bar_select_last(ui_menu_bar_t *mbar, bool openup,
306 sysarg_t idev_id)
307{
308 ui_menu_dd_t *mdd;
309
310 mdd = ui_menu_dd_last(mbar);
311 ui_menu_bar_select(mbar, mdd, openup, idev_id);
312}
313
314void ui_menu_bar_select_sysmenu(ui_menu_bar_t *mbar, sysarg_t idev_id)
315{
316 bool was_open;
317
318 ui_wdecor_sysmenu_hdl_set_active(mbar->window->wdecor, true);
319 was_open = mbar->selected != NULL &&
320 ui_menu_dd_is_open(mbar->selected);
321
322 if (was_open)
323 ui_window_send_sysmenu(mbar->window, idev_id);
324}
325
326/** Move one entry left.
327 *
328 * If the selected menu is open, the newly selected menu will be open
329 * as well. If we are already at the first entry, we wrap around.
330 *
331 * @param mbar Menu bar
332 * @param idev_id Input device ID
333 */
334void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
335{
336 ui_menu_dd_t *nmdd;
337
338 if (mbar->selected == NULL)
339 return;
340
341 nmdd = ui_menu_dd_prev(mbar->selected);
342 if (nmdd == NULL) {
343 if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
344 ui_menu_bar_select_sysmenu(mbar, idev_id);
345 } else {
346 nmdd = ui_menu_dd_last(mbar);
347 }
348 }
349
350 if (nmdd != mbar->selected)
351 ui_menu_bar_select(mbar, nmdd, false, idev_id);
352}
353
354/** Move one entry right.
355 *
356 * If the selected menu is open, the newly selected menu will be open
357 * as well. If we are already at the last entry, we wrap around.
358 *
359 * @param mbar Menu bar
360 * @param idev_id Input device ID
361 */
362void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
363{
364 ui_menu_dd_t *nmdd;
365
366 if (mbar->selected == NULL)
367 return;
368
369 nmdd = ui_menu_dd_next(mbar->selected);
370 if (nmdd == NULL) {
371 if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
372 ui_menu_bar_select_sysmenu(mbar, idev_id);
373 } else {
374 nmdd = ui_menu_dd_first(mbar);
375 }
376 }
377
378 if (nmdd != mbar->selected)
379 ui_menu_bar_select(mbar, nmdd, false, idev_id);
380}
381
382/** Handle menu bar key press without modifiers.
383 *
384 * @param mbar Menu bar
385 * @param kbd_event Keyboard event
386 * @return @c ui_claimed iff the event is claimed
387 */
388ui_evclaim_t ui_menu_bar_key_press_unmod(ui_menu_bar_t *mbar, kbd_event_t *event)
389{
390 gfx_rect_t rect;
391
392 if (event->key == KC_F10) {
393 ui_menu_bar_activate(mbar);
394 return ui_claimed;
395 }
396
397 if (!mbar->active)
398 return ui_unclaimed;
399
400 if (event->key == KC_ESCAPE) {
401 ui_menu_bar_deactivate(mbar);
402 return ui_claimed;
403 }
404
405 if (event->key == KC_LEFT)
406 ui_menu_bar_left(mbar, event->kbd_id);
407
408 if (event->key == KC_RIGHT)
409 ui_menu_bar_right(mbar, event->kbd_id);
410
411 if (event->key == KC_ENTER || event->key == KC_DOWN) {
412 if (mbar->selected != NULL &&
413 !ui_menu_dd_is_open(mbar->selected)) {
414 ui_menu_bar_entry_rect(mbar, mbar->selected,
415 &rect);
416 ui_menu_dd_open(mbar->selected, &rect, event->kbd_id);
417 }
418
419 return ui_claimed;
420 }
421
422 if (event->c != '\0' && !ui_menu_dd_is_open(mbar->selected)) {
423 /* Check if it is an accelerator. */
424 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
425 }
426
427 return ui_claimed;
428}
429
430/** Handle menu bar keyboard event.
431 *
432 * @param mbar Menu bar
433 * @param kbd_event Keyboard event
434 * @return @c ui_claimed iff the event is claimed
435 */
436ui_evclaim_t ui_menu_bar_kbd_event(ui_menu_bar_t *mbar, kbd_event_t *event)
437{
438 if ((event->mods & KM_ALT) != 0 &&
439 (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0') {
440 /* Check if it is an accelerator. */
441 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
442 }
443
444 if (event->type == KEY_PRESS && (event->mods &
445 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
446 return ui_menu_bar_key_press_unmod(mbar, event);
447 }
448
449 if (mbar->active)
450 return ui_claimed;
451
452 return ui_unclaimed;
453}
454
455/** Accelerator key press.
456 *
457 * If @a c matches an accelerator key, open the respective menu.
458 *
459 * @param mbar Menu bar
460 * @param c Character
461 * @param kbd_id Keyboard ID
462 */
463void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
464{
465 ui_menu_dd_t *mdd;
466 char32_t maccel;
467
468 mdd = ui_menu_dd_first(mbar);
469 while (mdd != NULL) {
470 maccel = ui_menu_dd_get_accel(mdd);
471 if ((char32_t)tolower(c) == maccel) {
472 ui_menu_bar_select(mbar, mdd, true, kbd_id);
473 return;
474 }
475
476 mdd = ui_menu_dd_next(mdd);
477 }
478}
479
480/** Handle menu bar position event.
481 *
482 * @param mbar Menu bar
483 * @param pos_event Position event
484 * @return @c ui_claimed iff the event is claimed
485 */
486ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *mbar, pos_event_t *event)
487{
488 ui_resource_t *res;
489 gfx_coord2_t pos;
490 gfx_rect_t rect;
491 ui_menu_dd_t *mdd;
492 const char *caption;
493 gfx_coord_t width;
494 gfx_coord_t hpad;
495 gfx_coord2_t ppos;
496 sysarg_t pos_id;
497
498 res = ui_window_get_res(mbar->window);
499
500 ppos.x = event->hpos;
501 ppos.y = event->vpos;
502
503 if (res->textmode) {
504 hpad = menubar_hpad_text;
505 } else {
506 hpad = menubar_hpad;
507 }
508
509 pos = mbar->rect.p0;
510 pos_id = event->pos_id;
511
512 mdd = ui_menu_dd_first(mbar);
513 while (mdd != NULL) {
514 caption = ui_menu_dd_caption(mdd);
515 width = ui_text_width(res->font, caption) + 2 * hpad;
516
517 rect.p0 = pos;
518 rect.p1.x = rect.p0.x + width;
519 rect.p1.y = mbar->rect.p1.y;
520
521 /* Check if press is inside menu bar entry */
522 if (event->type == POS_PRESS &&
523 gfx_pix_inside_rect(&ppos, &rect)) {
524 mbar->active = true;
525
526 /* Open the menu, close if already open. */
527 if (mdd == mbar->selected)
528 ui_menu_bar_select(mbar, NULL, false, pos_id);
529 else
530 ui_menu_bar_select(mbar, mdd, true, pos_id);
531
532 return ui_claimed;
533 }
534
535 pos.x += width;
536 mdd = ui_menu_dd_next(mdd);
537 }
538
539 return ui_unclaimed;
540}
541
542/** Handle menu bar position event.
543 *
544 * @param mbar Menu bar
545 * @param mdd Menu drop-down whose entry's rectangle is to be returned
546 * @param rrect Place to store entry rectangle
547 */
548void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd,
549 gfx_rect_t *rrect)
550{
551 ui_resource_t *res;
552 gfx_coord2_t pos;
553 gfx_rect_t rect;
554 ui_menu_dd_t *cur;
555 const char *caption;
556 gfx_coord_t width;
557 gfx_coord_t hpad;
558
559 res = ui_window_get_res(mbar->window);
560
561 if (res->textmode) {
562 hpad = menubar_hpad_text;
563 } else {
564 hpad = menubar_hpad;
565 }
566
567 pos = mbar->rect.p0;
568
569 cur = ui_menu_dd_first(mbar);
570 while (cur != NULL) {
571 caption = ui_menu_dd_caption(cur);
572 width = ui_text_width(res->font, caption) + 2 * hpad;
573
574 rect.p0 = pos;
575 rect.p1.x = rect.p0.x + width;
576 rect.p1.y = mbar->rect.p1.y;
577
578 if (cur == mdd) {
579 *rrect = rect;
580 return;
581 }
582
583 pos.x += width;
584 cur = ui_menu_dd_next(cur);
585 }
586
587 /* We should never get here */
588 assert(false);
589}
590
591/** Activate menu bar.
592 *
593 * @param mbar Menu bar
594 */
595void ui_menu_bar_activate(ui_menu_bar_t *mbar)
596{
597 if (mbar->active)
598 return;
599
600 mbar->active = true;
601 if (mbar->selected == NULL)
602 mbar->selected = ui_menu_dd_first(mbar);
603
604 (void) ui_menu_bar_paint(mbar);
605}
606
607/** Deactivate menu bar.
608 *
609 * @param mbar Menu bar
610 */
611void ui_menu_bar_deactivate(ui_menu_bar_t *mbar)
612{
613 ui_menu_bar_select(mbar, NULL, false, 0);
614}
615
616/** Destroy menu bar control.
617 *
618 * @param arg Argument (ui_menu_bar_t *)
619 */
620void ui_menu_bar_ctl_destroy(void *arg)
621{
622 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
623
624 ui_menu_bar_destroy(mbar);
625}
626
627/** Paint menu bar control.
628 *
629 * @param arg Argument (ui_menu_bar_t *)
630 * @return EOK on success or an error code
631 */
632errno_t ui_menu_bar_ctl_paint(void *arg)
633{
634 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
635
636 return ui_menu_bar_paint(mbar);
637}
638
639/** Handle menu bar control keyboard event.
640 *
641 * @param arg Argument (ui_menu_bar_t *)
642 * @param pos_event Position event
643 * @return @c ui_claimed iff the event is claimed
644 */
645ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *arg, kbd_event_t *event)
646{
647 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
648
649 return ui_menu_bar_kbd_event(mbar, event);
650}
651
652/** Handle menu bar control position event.
653 *
654 * @param arg Argument (ui_menu_bar_t *)
655 * @param pos_event Position event
656 * @return @c ui_claimed iff the event is claimed
657 */
658ui_evclaim_t ui_menu_bar_ctl_pos_event(void *arg, pos_event_t *event)
659{
660 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
661
662 return ui_menu_bar_pos_event(mbar, event);
663}
664
665/** @}
666 */
Note: See TracBrowser for help on using the repository browser.