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

serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 59768c7 was 59768c7, checked in by Jiri Svoboda <jiri@…>, 3 years ago

Menu control using F10, cursor keys, Enter, Escape

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