source: mainline/uspace/lib/ui/src/menuentry.c@ 5de852c

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

Menu control using F10, cursor keys, Enter, Escape

  • Property mode set to 100644
File size: 13.6 KB
Line 
1/*
2 * Copyright (c) 2022 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 entry
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>
48#include <ui/menuentry.h>
49#include <ui/window.h>
50#include "../private/menubar.h"
51#include "../private/menuentry.h"
52#include "../private/menu.h"
53#include "../private/resource.h"
54
55enum {
56 menu_entry_hpad = 4,
57 menu_entry_vpad = 4,
58 menu_entry_column_pad = 8,
59 menu_entry_sep_height = 2,
60 menu_entry_hpad_text = 1,
61 menu_entry_vpad_text = 0,
62 menu_entry_column_pad_text = 2,
63 menu_entry_sep_height_text = 1
64};
65
66/** Create new menu entry.
67 *
68 * @param menu Menu
69 * @param caption Caption
70 * @param shortcut Shotcut key(s) or empty string
71 * @param rmentry Place to store pointer to new menu entry
72 * @return EOK on success, ENOMEM if out of memory
73 */
74errno_t ui_menu_entry_create(ui_menu_t *menu, const char *caption,
75 const char *shortcut, ui_menu_entry_t **rmentry)
76{
77 ui_menu_entry_t *mentry;
78 gfx_coord_t caption_w;
79 gfx_coord_t shortcut_w;
80
81 mentry = calloc(1, sizeof(ui_menu_entry_t));
82 if (mentry == NULL)
83 return ENOMEM;
84
85 mentry->caption = str_dup(caption);
86 if (mentry->caption == NULL) {
87 free(mentry);
88 return ENOMEM;
89 }
90
91 mentry->shortcut = str_dup(shortcut);
92 if (mentry->caption == NULL) {
93 free(mentry->caption);
94 free(mentry);
95 return ENOMEM;
96 }
97
98 mentry->menu = menu;
99 list_append(&mentry->lentries, &menu->entries);
100
101 /* Update accumulated menu entry dimensions */
102 ui_menu_entry_column_widths(mentry, &caption_w, &shortcut_w);
103 if (caption_w > menu->max_caption_w)
104 menu->max_caption_w = caption_w;
105 if (shortcut_w > menu->max_shortcut_w)
106 menu->max_shortcut_w = shortcut_w;
107 menu->total_h += ui_menu_entry_height(mentry);
108
109 *rmentry = mentry;
110 return EOK;
111}
112
113/** Create new separator menu entry.
114 *
115 * @param menu Menu
116 * @param rmentry Place to store pointer to new menu entry
117 * @return EOK on success, ENOMEM if out of memory
118 */
119errno_t ui_menu_entry_sep_create(ui_menu_t *menu, ui_menu_entry_t **rmentry)
120{
121 ui_menu_entry_t *mentry;
122 errno_t rc;
123
124 rc = ui_menu_entry_create(menu, "", "", &mentry);
125 if (rc != EOK)
126 return rc;
127
128 /* Need to adjust menu height when changing to separator */
129 menu->total_h -= ui_menu_entry_height(mentry);
130 mentry->separator = true;
131 menu->total_h += ui_menu_entry_height(mentry);
132
133 *rmentry = mentry;
134 return EOK;
135}
136
137/** Destroy menu entry.
138 *
139 * @param mentry Menu entry or @c NULL
140 */
141void ui_menu_entry_destroy(ui_menu_entry_t *mentry)
142{
143 if (mentry == NULL)
144 return;
145
146 list_remove(&mentry->lentries);
147 free(mentry->caption);
148 free(mentry);
149}
150
151/** Set menu entry callback.
152 *
153 * @param mentry Menu entry
154 * @param cb Menu entry callback
155 * @param arg Callback argument
156 */
157void ui_menu_entry_set_cb(ui_menu_entry_t *mentry, ui_menu_entry_cb_t cb,
158 void *arg)
159{
160 mentry->cb = cb;
161 mentry->arg = arg;
162}
163
164/** Get first menu entry in menu.
165 *
166 * @param menu Menu
167 * @return First menu entry or @c NULL if there is none
168 */
169ui_menu_entry_t *ui_menu_entry_first(ui_menu_t *menu)
170{
171 link_t *link;
172
173 link = list_first(&menu->entries);
174 if (link == NULL)
175 return NULL;
176
177 return list_get_instance(link, ui_menu_entry_t, lentries);
178}
179
180/** Get last menu entry in menu.
181 *
182 * @param menu Menu
183 * @return Last menu entry or @c NULL if there is none
184 */
185ui_menu_entry_t *ui_menu_entry_last(ui_menu_t *menu)
186{
187 link_t *link;
188
189 link = list_last(&menu->entries);
190 if (link == NULL)
191 return NULL;
192
193 return list_get_instance(link, ui_menu_entry_t, lentries);
194}
195
196/** Get next menu entry in menu.
197 *
198 * @param cur Current menu entry
199 * @return Next menu entry or @c NULL if @a cur is the last one
200 */
201ui_menu_entry_t *ui_menu_entry_next(ui_menu_entry_t *cur)
202{
203 link_t *link;
204
205 link = list_next(&cur->lentries, &cur->menu->entries);
206 if (link == NULL)
207 return NULL;
208
209 return list_get_instance(link, ui_menu_entry_t, lentries);
210}
211
212/** Get previous menu entry in menu.
213 *
214 * @param cur Current menu entry
215 * @return Next menu entry or @c NULL if @a cur is the last one
216 */
217ui_menu_entry_t *ui_menu_entry_prev(ui_menu_entry_t *cur)
218{
219 link_t *link;
220
221 link = list_prev(&cur->lentries, &cur->menu->entries);
222 if (link == NULL)
223 return NULL;
224
225 return list_get_instance(link, ui_menu_entry_t, lentries);
226}
227
228/** Get width of menu entry.
229 *
230 * @param mentry Menu entry
231 * @param caption_w Place to store caption width
232 * @param shortcut_w Place to store shortcut width
233 */
234void ui_menu_entry_column_widths(ui_menu_entry_t *mentry,
235 gfx_coord_t *caption_w, gfx_coord_t *shortcut_w)
236{
237 ui_resource_t *res;
238
239 /*
240 * This needs to work even if the menu is not open, so we cannot
241 * use the menu's resource, which is only created after the menu
242 * is open (and its window is created). Use the menu bar's
243 * resource instead.
244 */
245 res = ui_window_get_res(mentry->menu->mbar->window);
246
247 *caption_w = gfx_text_width(res->font, mentry->caption);
248 *shortcut_w = gfx_text_width(res->font, mentry->shortcut);
249}
250
251/** Compute width of menu entry.
252 *
253 * @param menu Menu
254 * @param caption_w Widht of caption text
255 * @param shortcut_w Width of shortcut text
256 * @return Width in pixels
257 */
258gfx_coord_t ui_menu_entry_calc_width(ui_menu_t *menu, gfx_coord_t caption_w,
259 gfx_coord_t shortcut_w)
260{
261 ui_resource_t *res;
262 gfx_coord_t hpad;
263 gfx_coord_t width;
264
265 /*
266 * This needs to work even if the menu is not open, so we cannot
267 * use the menu's resource, which is only created after the menu
268 * is open (and its window is created). Use the menu bar's
269 * resource instead.
270 */
271 res = ui_window_get_res(menu->mbar->window);
272
273 if (res->textmode)
274 hpad = menu_entry_hpad_text;
275 else
276 hpad = menu_entry_hpad;
277
278 width = caption_w + 2 * hpad;
279
280 if (shortcut_w != 0) {
281 if (res->textmode)
282 width += menu_entry_column_pad_text;
283 else
284 width += menu_entry_column_pad;
285
286 width += shortcut_w;
287 }
288
289 return width;
290}
291
292/** Get height of menu entry.
293 *
294 * @param mentry Menu entry
295 * @return Width in pixels
296 */
297gfx_coord_t ui_menu_entry_height(ui_menu_entry_t *mentry)
298{
299 ui_resource_t *res;
300 gfx_font_metrics_t metrics;
301 gfx_coord_t height;
302 gfx_coord_t vpad;
303
304 /*
305 * This needs to work even if the menu is not open, so we cannot
306 * use the menu's resource, which is only created after the menu
307 * is open (and its window is created). Use the menu bar's
308 * resource instead.
309 */
310 res = ui_window_get_res(mentry->menu->mbar->window);
311
312 if (res->textmode) {
313 vpad = menu_entry_vpad_text;
314 } else {
315 vpad = menu_entry_vpad;
316 }
317
318 if (mentry->separator) {
319 /* Separator menu entry */
320 if (res->textmode)
321 height = menu_entry_sep_height_text;
322 else
323 height = menu_entry_sep_height;
324 } else {
325 /* Normal menu entry */
326 gfx_font_get_metrics(res->font, &metrics);
327 height = metrics.ascent + metrics.descent + 1;
328 }
329
330 return height + 2 * vpad;
331}
332
333/** Paint menu entry.
334 *
335 * @param mentry Menu entry
336 * @param pos Position where to paint entry
337 * @return EOK on success or an error code
338 */
339errno_t ui_menu_entry_paint(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
340{
341 ui_resource_t *res;
342 gfx_text_fmt_t fmt;
343 gfx_color_t *bg_color;
344 ui_menu_entry_geom_t geom;
345 gfx_rect_t rect;
346 errno_t rc;
347
348 res = ui_menu_get_res(mentry->menu);
349
350 ui_menu_entry_get_geom(mentry, pos, &geom);
351
352 gfx_text_fmt_init(&fmt);
353 fmt.halign = gfx_halign_left;
354 fmt.valign = gfx_valign_top;
355
356 if ((mentry->held && mentry->inside) ||
357 mentry == mentry->menu->selected) {
358 fmt.color = res->wnd_sel_text_color;
359 bg_color = res->wnd_sel_text_bg_color;
360 } else {
361 fmt.color = res->wnd_text_color;
362 bg_color = res->wnd_face_color;
363 }
364
365 rc = gfx_set_color(res->gc, bg_color);
366 if (rc != EOK)
367 goto error;
368
369 rc = gfx_fill_rect(res->gc, &geom.outer_rect);
370 if (rc != EOK)
371 goto error;
372
373 rc = gfx_puttext(res->font, &geom.caption_pos, &fmt, mentry->caption);
374 if (rc != EOK)
375 goto error;
376
377 fmt.halign = gfx_halign_right;
378
379 rc = gfx_puttext(res->font, &geom.shortcut_pos, &fmt, mentry->shortcut);
380 if (rc != EOK)
381 goto error;
382
383 if (mentry->separator) {
384 if (res->textmode) {
385 rect = geom.outer_rect;
386 rect.p0.x -= 1;
387 rect.p1.x += 1;
388
389 rc = ui_paint_text_hbrace(res, &rect, ui_box_single,
390 res->wnd_face_color);
391 if (rc != EOK)
392 goto error;
393 } else {
394 rect.p0 = geom.caption_pos;
395 rect.p1.x = geom.shortcut_pos.x;
396 rect.p1.y = rect.p0.y + 2;
397 rc = ui_paint_bevel(res->gc, &rect, res->wnd_shadow_color,
398 res->wnd_highlight_color, 1, NULL);
399 if (rc != EOK)
400 goto error;
401 }
402 }
403
404 rc = gfx_update(res->gc);
405 if (rc != EOK)
406 goto error;
407
408 return EOK;
409error:
410 return rc;
411}
412
413/** Determine if entry is selectable.
414 *
415 * @return @c true iff entry is selectable
416 */
417bool ui_menu_entry_selectable(ui_menu_entry_t *mentry)
418{
419 return !mentry->separator;
420}
421
422/** Handle button press in menu entry.
423 *
424 * @param mentry Menu entry
425 * @param pos Menu entry position
426 */
427void ui_menu_entry_press(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
428{
429 if (mentry->held)
430 return;
431
432 if (mentry->separator)
433 return;
434
435 mentry->inside = true;
436 mentry->held = true;
437 ui_menu_entry_paint(mentry, pos);
438}
439
440/** Handle button release in menu entry.
441 *
442 * @param mentry Menu entry
443 */
444void ui_menu_entry_release(ui_menu_entry_t *mentry)
445{
446 if (!mentry->held)
447 return;
448
449 mentry->held = false;
450
451 ui_menu_entry_activate(mentry);
452}
453
454/** Activate menu entry.
455 *
456 * @param mentry Menu entry
457 */
458void ui_menu_entry_activate(ui_menu_entry_t *mentry)
459{
460 /* Deactivate menu bar, close menu */
461 ui_menu_bar_deactivate(mentry->menu->mbar);
462
463 /* Call back */
464 ui_menu_entry_cb(mentry);
465}
466
467/** Call menu entry callback.
468 *
469 * @param mentry Menu entry
470 */
471void ui_menu_entry_cb(ui_menu_entry_t *mentry)
472{
473 if (mentry->cb != NULL)
474 mentry->cb(mentry, mentry->arg);
475}
476
477/** Pointer entered menu entry.
478 *
479 * @param mentry Menu entry
480 * @param pos Menu entry position
481 */
482void ui_menu_entry_enter(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
483{
484 if (mentry->inside)
485 return;
486
487 mentry->inside = true;
488 if (mentry->held)
489 (void) ui_menu_entry_paint(mentry, pos);
490}
491
492/** Pointer left menu entry.
493 *
494 * @param mentry Menu entry
495 * @param pos Menu entry position
496 */
497void ui_menu_entry_leave(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
498{
499 if (!mentry->inside)
500 return;
501
502 mentry->inside = false;
503 if (mentry->held)
504 (void) ui_menu_entry_paint(mentry, pos);
505}
506
507/** Handle menu entry position event.
508 *
509 * @param mentry Menu entry
510 * @param pos Menu entry position (top-left corner)
511 * @param pos_event Position event
512 * @return @c ui_claimed iff the event is claimed
513 */
514ui_evclaim_t ui_menu_entry_pos_event(ui_menu_entry_t *mentry,
515 gfx_coord2_t *pos, pos_event_t *event)
516{
517 ui_menu_entry_geom_t geom;
518 gfx_coord2_t ppos;
519 bool inside;
520
521 ppos.x = event->hpos;
522 ppos.y = event->vpos;
523
524 ui_menu_entry_get_geom(mentry, pos, &geom);
525 inside = gfx_pix_inside_rect(&ppos, &geom.outer_rect);
526
527 switch (event->type) {
528 case POS_PRESS:
529 if (inside) {
530 ui_menu_entry_press(mentry, pos);
531 return ui_claimed;
532 }
533 break;
534 case POS_RELEASE:
535 if (mentry->held) {
536 ui_menu_entry_release(mentry);
537 return ui_claimed;
538 }
539 break;
540 case POS_UPDATE:
541 if (inside && !mentry->inside) {
542 ui_menu_entry_enter(mentry, pos);
543 return ui_claimed;
544 } else if (!inside && mentry->inside) {
545 ui_menu_entry_leave(mentry, pos);
546 }
547 break;
548 case POS_DCLICK:
549 break;
550 }
551
552 return ui_unclaimed;
553}
554
555/** Get menu entry geometry.
556 *
557 * @param mentry Menu entry
558 * @param spos Entry position
559 * @param geom Structure to fill in with computed geometry
560 */
561void ui_menu_entry_get_geom(ui_menu_entry_t *mentry, gfx_coord2_t *pos,
562 ui_menu_entry_geom_t *geom)
563{
564 ui_resource_t *res;
565 gfx_coord_t hpad;
566 gfx_coord_t vpad;
567 gfx_coord_t width;
568
569 res = ui_menu_get_res(mentry->menu);
570
571 if (res->textmode) {
572 hpad = menu_entry_hpad_text;
573 vpad = menu_entry_vpad_text;
574 } else {
575 hpad = menu_entry_hpad;
576 vpad = menu_entry_vpad;
577 }
578
579 /* Compute total width of menu entry */
580 width = ui_menu_entry_calc_width(mentry->menu,
581 mentry->menu->max_caption_w, mentry->menu->max_shortcut_w);
582
583 geom->outer_rect.p0 = *pos;
584 geom->outer_rect.p1.x = geom->outer_rect.p0.x + width;
585 geom->outer_rect.p1.y = geom->outer_rect.p0.y +
586 ui_menu_entry_height(mentry);
587
588 geom->caption_pos.x = pos->x + hpad;
589 geom->caption_pos.y = pos->y + vpad;
590
591 geom->shortcut_pos.x = geom->outer_rect.p1.x - hpad;
592 geom->shortcut_pos.y = pos->y + vpad;
593}
594
595/** @}
596 */
Note: See TracBrowser for help on using the repository browser.