source: mainline/uspace/lib/ui/src/menubar.c@ 95658c9

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 95658c9 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: 13.4 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 <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/menu.h>
48#include <ui/menubar.h>
49#include <ui/window.h>
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 *);
62static ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *, kbd_event_t *);
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,
69 .kbd_event = ui_menu_bar_ctl_kbd_event,
70 .pos_event = ui_menu_bar_ctl_pos_event
71};
72
73/** Create new menu bar.
74 *
75 * @param ui UI
76 * @param window Window that will contain the menu bar
77 * @param rmbar Place to store pointer to new menu bar
78 * @return EOK on success, ENOMEM if out of memory
79 */
80errno_t ui_menu_bar_create(ui_t *ui, ui_window_t *window, ui_menu_bar_t **rmbar)
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
95 mbar->ui = ui;
96 mbar->window = window;
97 list_initialize(&mbar->menus);
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{
108 ui_menu_t *menu;
109
110 if (mbar == NULL)
111 return;
112
113 /* Destroy menus */
114 menu = ui_menu_first(mbar);
115 while (menu != NULL) {
116 ui_menu_destroy(menu);
117 menu = ui_menu_first(mbar);
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{
151 ui_resource_t *res;
152 ui_text_fmt_t fmt;
153 gfx_coord2_t pos;
154 gfx_coord2_t tpos;
155 gfx_rect_t rect;
156 gfx_color_t *bg_color;
157 ui_menu_t *menu;
158 const char *caption;
159 gfx_coord_t width;
160 gfx_coord_t hpad;
161 gfx_coord_t vpad;
162 errno_t rc;
163
164 res = ui_window_get_res(mbar->window);
165
166 /* Paint menu bar background */
167
168 rc = gfx_set_color(res->gc, res->wnd_face_color);
169 if (rc != EOK)
170 goto error;
171
172 rc = gfx_fill_rect(res->gc, &mbar->rect);
173 if (rc != EOK)
174 goto error;
175
176 if (res->textmode) {
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
186 ui_text_fmt_init(&fmt);
187 fmt.font = res->font;
188 fmt.halign = gfx_halign_left;
189 fmt.valign = gfx_valign_top;
190
191 menu = ui_menu_first(mbar);
192 while (menu != NULL) {
193 caption = ui_menu_caption(menu);
194 width = ui_text_width(res->font, caption) + 2 * hpad;
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
202 if (menu == mbar->selected) {
203 fmt.color = res->wnd_sel_text_color;
204 fmt.hgl_color = res->wnd_sel_text_hgl_color;
205 bg_color = res->wnd_sel_text_bg_color;
206 } else {
207 fmt.color = res->wnd_text_color;
208 fmt.hgl_color = res->wnd_text_hgl_color;
209 bg_color = res->wnd_face_color;
210 }
211
212 rc = gfx_set_color(res->gc, bg_color);
213 if (rc != EOK)
214 goto error;
215
216 rc = gfx_fill_rect(res->gc, &rect);
217 if (rc != EOK)
218 goto error;
219
220 rc = ui_paint_text(&tpos, &fmt, caption);
221 if (rc != EOK)
222 goto error;
223
224 pos.x += width;
225 menu = ui_menu_next(menu);
226 }
227
228 rc = gfx_update(res->gc);
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
243 * @param menu Menu to select (or deselect if selected) or @c NULL
244 * @param openup Open menu even if not currently open
245 * @param idev_id Input device ID associated with the selecting seat
246 */
247void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_t *menu, bool openup,
248 sysarg_t idev_id)
249{
250 ui_menu_t *old_menu;
251 gfx_rect_t rect;
252 bool was_open;
253
254 old_menu = mbar->selected;
255
256 mbar->selected = menu;
257
258 /* Close previously open menu */
259 if (old_menu != NULL && ui_menu_is_open(old_menu)) {
260 was_open = true;
261 (void) ui_menu_close(old_menu);
262 } else {
263 was_open = false;
264 }
265
266 (void) ui_menu_bar_paint(mbar);
267
268 if (mbar->selected != NULL) {
269 ui_menu_bar_entry_rect(mbar, mbar->selected, &rect);
270 if (openup || was_open) {
271 /*
272 * Open the newly selected menu if either
273 * the old menu was open or @a openup was
274 * specified.
275 */
276 (void) ui_menu_open(mbar->selected, &rect, idev_id);
277 }
278 }
279}
280
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
287 * @param idev_id Input device ID
288 */
289void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
290{
291 ui_menu_t *nmenu;
292
293 if (mbar->selected == NULL)
294 return;
295
296 nmenu = ui_menu_prev(mbar->selected);
297 if (nmenu == NULL)
298 nmenu = ui_menu_last(mbar);
299
300 if (nmenu != mbar->selected)
301 ui_menu_bar_select(mbar, nmenu, false, idev_id);
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
310 * @param idev_id Input device ID
311 */
312void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
313{
314 ui_menu_t *nmenu;
315
316 if (mbar->selected == NULL)
317 return;
318
319 nmenu = ui_menu_next(mbar->selected);
320 if (nmenu == NULL)
321 nmenu = ui_menu_first(mbar);
322
323 if (nmenu != mbar->selected)
324 ui_menu_bar_select(mbar, nmenu, false, idev_id);
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)
351 ui_menu_bar_left(mbar, event->kbd_id);
352
353 if (event->key == KC_RIGHT)
354 ui_menu_bar_right(mbar, event->kbd_id);
355
356 if (event->key == KC_ENTER || event->key == KC_DOWN) {
357 if (mbar->selected != NULL && !ui_menu_is_open(mbar->selected)) {
358 ui_menu_bar_entry_rect(mbar, mbar->selected,
359 &rect);
360 ui_menu_open(mbar->selected, &rect, event->kbd_id);
361 }
362
363 return ui_claimed;
364 }
365
366 if (event->c != '\0' && !ui_menu_is_open(mbar->selected)) {
367 /* Check if it is an accelerator. */
368 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
369 }
370
371 return ui_claimed;
372}
373
374/** Handle menu bar keyboard event.
375 *
376 * @param mbar Menu bar
377 * @param kbd_event Keyboard event
378 * @return @c ui_claimed iff the event is claimed
379 */
380ui_evclaim_t ui_menu_bar_kbd_event(ui_menu_bar_t *mbar, kbd_event_t *event)
381{
382 if ((event->mods & KM_ALT) != 0 &&
383 (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0') {
384 /* Check if it is an accelerator. */
385 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
386 }
387
388 if (event->type == KEY_PRESS && (event->mods &
389 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
390 return ui_menu_bar_key_press_unmod(mbar, event);
391 }
392
393 if (mbar->active)
394 return ui_claimed;
395
396 return ui_unclaimed;
397}
398
399/** Accelerator key press.
400 *
401 * If @a c matches an accelerator key, open the respective menu.
402 *
403 * @param mbar Menu bar
404 * @param c Character
405 * @param kbd_id Keyboard ID
406 */
407void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
408{
409 ui_menu_t *menu;
410 char32_t maccel;
411
412 menu = ui_menu_first(mbar);
413 while (menu != NULL) {
414 maccel = ui_menu_get_accel(menu);
415 if (c == maccel) {
416 ui_menu_bar_select(mbar, menu, true, kbd_id);
417 return;
418 }
419
420 menu = ui_menu_next(menu);
421 }
422}
423
424/** Handle menu bar position event.
425 *
426 * @param mbar Menu bar
427 * @param pos_event Position event
428 * @return @c ui_claimed iff the event is claimed
429 */
430ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *mbar, pos_event_t *event)
431{
432 ui_resource_t *res;
433 gfx_coord2_t pos;
434 gfx_rect_t rect;
435 ui_menu_t *menu;
436 const char *caption;
437 gfx_coord_t width;
438 gfx_coord_t hpad;
439 gfx_coord2_t ppos;
440 sysarg_t pos_id;
441
442 res = ui_window_get_res(mbar->window);
443
444 ppos.x = event->hpos;
445 ppos.y = event->vpos;
446
447 if (res->textmode) {
448 hpad = menubar_hpad_text;
449 } else {
450 hpad = menubar_hpad;
451 }
452
453 pos = mbar->rect.p0;
454 pos_id = event->pos_id;
455
456 menu = ui_menu_first(mbar);
457 while (menu != NULL) {
458 caption = ui_menu_caption(menu);
459 width = ui_text_width(res->font, caption) + 2 * hpad;
460
461 rect.p0 = pos;
462 rect.p1.x = rect.p0.x + width;
463 rect.p1.y = mbar->rect.p1.y;
464
465 /* Check if press is inside menu bar entry */
466 if (event->type == POS_PRESS &&
467 gfx_pix_inside_rect(&ppos, &rect)) {
468 mbar->active = true;
469
470 /* Open the menu, close if already open. */
471 if (menu == mbar->selected)
472 ui_menu_bar_select(mbar, NULL, false, pos_id);
473 else
474 ui_menu_bar_select(mbar, menu, true, pos_id);
475
476 return ui_claimed;
477 }
478
479 pos.x += width;
480 menu = ui_menu_next(menu);
481 }
482
483 return ui_unclaimed;
484}
485
486/** Handle menu bar position event.
487 *
488 * @param mbar Menu bar
489 * @param menu Menu whose entry's rectangle is to be returned
490 * @param rrect Place to store entry rectangle
491 */
492void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_t *menu,
493 gfx_rect_t *rrect)
494{
495 ui_resource_t *res;
496 gfx_coord2_t pos;
497 gfx_rect_t rect;
498 ui_menu_t *cur;
499 const char *caption;
500 gfx_coord_t width;
501 gfx_coord_t hpad;
502
503 res = ui_window_get_res(mbar->window);
504
505 if (res->textmode) {
506 hpad = menubar_hpad_text;
507 } else {
508 hpad = menubar_hpad;
509 }
510
511 pos = mbar->rect.p0;
512
513 cur = ui_menu_first(mbar);
514 while (cur != NULL) {
515 caption = ui_menu_caption(cur);
516 width = ui_text_width(res->font, caption) + 2 * hpad;
517
518 rect.p0 = pos;
519 rect.p1.x = rect.p0.x + width;
520 rect.p1.y = mbar->rect.p1.y;
521
522 if (cur == menu) {
523 *rrect = rect;
524 return;
525 }
526
527 pos.x += width;
528 cur = ui_menu_next(cur);
529 }
530
531 /* We should never get here */
532 assert(false);
533}
534
535/** Activate menu bar.
536 *
537 * @param mbar Menu bar
538 */
539void ui_menu_bar_activate(ui_menu_bar_t *mbar)
540{
541 if (mbar->active)
542 return;
543
544 mbar->active = true;
545 if (mbar->selected == NULL)
546 mbar->selected = ui_menu_first(mbar);
547
548 (void) ui_menu_bar_paint(mbar);
549}
550
551void ui_menu_bar_deactivate(ui_menu_bar_t *mbar)
552{
553 ui_menu_bar_select(mbar, NULL, false, 0);
554 mbar->active = false;
555}
556
557/** Destroy menu bar control.
558 *
559 * @param arg Argument (ui_menu_bar_t *)
560 */
561void ui_menu_bar_ctl_destroy(void *arg)
562{
563 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
564
565 ui_menu_bar_destroy(mbar);
566}
567
568/** Paint menu bar control.
569 *
570 * @param arg Argument (ui_menu_bar_t *)
571 * @return EOK on success or an error code
572 */
573errno_t ui_menu_bar_ctl_paint(void *arg)
574{
575 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
576
577 return ui_menu_bar_paint(mbar);
578}
579
580/** Handle menu bar control keyboard event.
581 *
582 * @param arg Argument (ui_menu_bar_t *)
583 * @param pos_event Position event
584 * @return @c ui_claimed iff the event is claimed
585 */
586ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *arg, kbd_event_t *event)
587{
588 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
589
590 return ui_menu_bar_kbd_event(mbar, event);
591}
592
593/** Handle menu bar control position event.
594 *
595 * @param arg Argument (ui_menu_bar_t *)
596 * @param pos_event Position event
597 * @return @c ui_claimed iff the event is claimed
598 */
599ui_evclaim_t ui_menu_bar_ctl_pos_event(void *arg, pos_event_t *event)
600{
601 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
602
603 return ui_menu_bar_pos_event(mbar, event);
604}
605
606/** @}
607 */
Note: See TracBrowser for help on using the repository browser.