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

Last change on this file 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
RevLine 
[214aefb]1/*
[ee3b28a9]2 * Copyright (c) 2024 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 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>
[c88d7f99]45#include <ui/accel.h>
[214aefb]46#include <ui/control.h>
47#include <ui/paint.h>
[59768c7]48#include <ui/menubar.h>
[214aefb]49#include <ui/menuentry.h>
[c68c18b9]50#include <ui/window.h>
[214aefb]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,
[b8b64a8]59 menu_entry_column_pad = 8,
[6186f9f]60 menu_entry_sep_height = 2,
[214aefb]61 menu_entry_hpad_text = 1,
[b8b64a8]62 menu_entry_vpad_text = 0,
[6186f9f]63 menu_entry_column_pad_text = 2,
64 menu_entry_sep_height_text = 1
[214aefb]65};
66
67/** Create new menu entry.
68 *
69 * @param menu Menu
70 * @param caption Caption
[b8b64a8]71 * @param shortcut Shotcut key(s) or empty string
[214aefb]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,
[b8b64a8]76 const char *shortcut, ui_menu_entry_t **rmentry)
[214aefb]77{
78 ui_menu_entry_t *mentry;
[b8b64a8]79 gfx_coord_t caption_w;
80 gfx_coord_t shortcut_w;
[214aefb]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
[b8b64a8]92 mentry->shortcut = str_dup(shortcut);
93 if (mentry->caption == NULL) {
94 free(mentry->caption);
95 free(mentry);
96 return ENOMEM;
97 }
98
[214aefb]99 mentry->menu = menu;
100 list_append(&mentry->lentries, &menu->entries);
101
[f251883]102 /* Update accumulated menu entry dimensions */
[b8b64a8]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;
[f251883]108 menu->total_h += ui_menu_entry_height(mentry);
109
[214aefb]110 *rmentry = mentry;
111 return EOK;
112}
113
[6186f9f]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
[214aefb]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
[ee3b28a9]147 mentry->menu->total_h -= ui_menu_entry_height(mentry);
148 /* NOTE: max_caption_w/max_shortcut_w not updated (speed) */
149
[214aefb]150 list_remove(&mentry->lentries);
[ee3b28a9]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
[214aefb]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
[112f70a]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
[214aefb]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
[59768c7]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
[214aefb]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
[59768c7]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
[214aefb]264/** Get width of menu entry.
265 *
266 * @param mentry Menu entry
[b8b64a8]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
[3c8c580]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
[46bd63c9]278 * is open (and its window is created). Use the parent window's
[3c8c580]279 * resource instead.
280 */
[46bd63c9]281 res = ui_window_get_res(mentry->menu->parent);
[b8b64a8]282
[c88d7f99]283 *caption_w = ui_text_width(res->font, mentry->caption);
284 *shortcut_w = ui_text_width(res->font, mentry->shortcut);
[b8b64a8]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
[214aefb]292 * @return Width in pixels
293 */
[b8b64a8]294gfx_coord_t ui_menu_entry_calc_width(ui_menu_t *menu, gfx_coord_t caption_w,
295 gfx_coord_t shortcut_w)
[214aefb]296{
297 ui_resource_t *res;
298 gfx_coord_t hpad;
[b8b64a8]299 gfx_coord_t width;
[214aefb]300
[3c8c580]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
[46bd63c9]304 * is open (and its window is created). Use the parent window's
[3c8c580]305 * resource instead.
306 */
[46bd63c9]307 res = ui_window_get_res(menu->parent);
[214aefb]308
[b8b64a8]309 if (res->textmode)
[214aefb]310 hpad = menu_entry_hpad_text;
[b8b64a8]311 else
[214aefb]312 hpad = menu_entry_hpad;
[b8b64a8]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;
[214aefb]323 }
324
[b8b64a8]325 return width;
[214aefb]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
[3c8c580]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
[46bd63c9]343 * is open (and its window is created). Use the parent window's
[3c8c580]344 * resource instead.
345 */
[46bd63c9]346 res = ui_window_get_res(mentry->menu->parent);
[214aefb]347
348 if (res->textmode) {
349 vpad = menu_entry_vpad_text;
350 } else {
351 vpad = menu_entry_vpad;
352 }
353
[6186f9f]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
[214aefb]366 return height + 2 * vpad;
367}
368
[c88d7f99]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
[214aefb]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;
[c88d7f99]389 ui_text_fmt_t fmt;
[214aefb]390 gfx_color_t *bg_color;
391 ui_menu_entry_geom_t geom;
[6186f9f]392 gfx_rect_t rect;
[214aefb]393 errno_t rc;
394
[3c8c580]395 res = ui_menu_get_res(mentry->menu);
[214aefb]396
397 ui_menu_entry_get_geom(mentry, pos, &geom);
398
[c88d7f99]399 ui_text_fmt_init(&fmt);
[4583015]400 fmt.font = res->font;
[214aefb]401 fmt.halign = gfx_halign_left;
402 fmt.valign = gfx_valign_top;
403
[0262f16c]404 if ((mentry->held && mentry->inside) ||
405 mentry == mentry->menu->selected) {
[214aefb]406 fmt.color = res->wnd_sel_text_color;
[c88d7f99]407 fmt.hgl_color = res->wnd_sel_text_hgl_color;
[214aefb]408 bg_color = res->wnd_sel_text_bg_color;
[112f70a]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;
[214aefb]413 } else {
414 fmt.color = res->wnd_text_color;
[c88d7f99]415 fmt.hgl_color = res->wnd_text_hgl_color;
[214aefb]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
[c88d7f99]427 rc = ui_paint_text(&geom.caption_pos, &fmt, mentry->caption);
[b8b64a8]428 if (rc != EOK)
429 goto error;
430
431 fmt.halign = gfx_halign_right;
432
[c88d7f99]433 rc = ui_paint_text(&geom.shortcut_pos, &fmt, mentry->shortcut);
[214aefb]434 if (rc != EOK)
435 goto error;
436
[6186f9f]437 if (mentry->separator) {
[81ec7e1]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 }
[6186f9f]456 }
457
[214aefb]458 rc = gfx_update(res->gc);
459 if (rc != EOK)
460 goto error;
461
462 return EOK;
463error:
464 return rc;
465}
466
[59768c7]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
[214aefb]476/** Handle button press in menu entry.
477 *
478 * @param mentry Menu entry
[0262f16c]479 * @param pos Menu entry position
[214aefb]480 */
[0262f16c]481void ui_menu_entry_press(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
[214aefb]482{
[0262f16c]483 if (mentry->held)
484 return;
[214aefb]485
[6a0b2cc]486 if (mentry->separator || mentry->disabled)
[6186f9f]487 return;
488
[0262f16c]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;
[214aefb]502
[0262f16c]503 mentry->held = false;
[214aefb]504
[71edd430]505 if (mentry->inside)
506 ui_menu_entry_activate(mentry);
[59768c7]507}
[214aefb]508
[59768c7]509/** Activate menu entry.
510 *
511 * @param mentry Menu entry
512 */
513void ui_menu_entry_activate(ui_menu_entry_t *mentry)
514{
[46bd63c9]515 /* Close menu */
516 ui_menu_close_req(mentry->menu);
[59768c7]517
518 /* Call back */
519 ui_menu_entry_cb(mentry);
[214aefb]520}
521
[95a9cbc]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
[0262f16c]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;
[8edec53]603 case POS_DCLICK:
604 break;
[0262f16c]605 }
606
607 return ui_unclaimed;
608}
609
[214aefb]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
[3c8c580]624 res = ui_menu_get_res(mentry->menu);
[214aefb]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
[b8b64a8]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);
[214aefb]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);
[b8b64a8]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;
[214aefb]648}
649
650/** @}
651 */
Note: See TracBrowser for help on using the repository browser.