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

Last change on this file since ee3b28a9 was ee3b28a9, checked in by Jiri Svoboda <jiri@…>, 16 months ago

Notify taskbar when start menu changes

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