source: mainline/uspace/lib/ui/src/menubar.c@ 1af103e

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

Split drop-down menu into two classes: drop-down and menu

Naming is clearly the hardest problem in computer science.

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