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

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 46bd63c9 was 46bd63c9, checked in by Jiri Svoboda <jiri@…>, 22 months ago

Split drop-down menu into two classes: drop-down and menu

Naming is clearly the hardest problem in computer science.

  • Property mode set to 100644
File size: 14.0 KB
RevLine 
[214aefb]1/*
[46bd63c9]2 * Copyright (c) 2023 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
147 list_remove(&mentry->lentries);
148 free(mentry->caption);
149 free(mentry);
150}
151
152/** Set menu entry callback.
153 *
154 * @param mentry Menu entry
155 * @param cb Menu entry callback
156 * @param arg Callback argument
157 */
158void ui_menu_entry_set_cb(ui_menu_entry_t *mentry, ui_menu_entry_cb_t cb,
159 void *arg)
160{
161 mentry->cb = cb;
162 mentry->arg = arg;
163}
164
165/** Get first menu entry in menu.
166 *
167 * @param menu Menu
168 * @return First menu entry or @c NULL if there is none
169 */
170ui_menu_entry_t *ui_menu_entry_first(ui_menu_t *menu)
171{
172 link_t *link;
173
174 link = list_first(&menu->entries);
175 if (link == NULL)
176 return NULL;
177
178 return list_get_instance(link, ui_menu_entry_t, lentries);
179}
180
[59768c7]181/** Get last menu entry in menu.
182 *
183 * @param menu Menu
184 * @return Last menu entry or @c NULL if there is none
185 */
186ui_menu_entry_t *ui_menu_entry_last(ui_menu_t *menu)
187{
188 link_t *link;
189
190 link = list_last(&menu->entries);
191 if (link == NULL)
192 return NULL;
193
194 return list_get_instance(link, ui_menu_entry_t, lentries);
195}
196
[214aefb]197/** Get next menu entry in menu.
198 *
199 * @param cur Current menu entry
200 * @return Next menu entry or @c NULL if @a cur is the last one
201 */
202ui_menu_entry_t *ui_menu_entry_next(ui_menu_entry_t *cur)
203{
204 link_t *link;
205
206 link = list_next(&cur->lentries, &cur->menu->entries);
207 if (link == NULL)
208 return NULL;
209
210 return list_get_instance(link, ui_menu_entry_t, lentries);
211}
212
[59768c7]213/** Get previous menu entry in menu.
214 *
215 * @param cur Current menu entry
216 * @return Next menu entry or @c NULL if @a cur is the last one
217 */
218ui_menu_entry_t *ui_menu_entry_prev(ui_menu_entry_t *cur)
219{
220 link_t *link;
221
222 link = list_prev(&cur->lentries, &cur->menu->entries);
223 if (link == NULL)
224 return NULL;
225
226 return list_get_instance(link, ui_menu_entry_t, lentries);
227}
228
[214aefb]229/** Get width of menu entry.
230 *
231 * @param mentry Menu entry
[b8b64a8]232 * @param caption_w Place to store caption width
233 * @param shortcut_w Place to store shortcut width
234 */
235void ui_menu_entry_column_widths(ui_menu_entry_t *mentry,
236 gfx_coord_t *caption_w, gfx_coord_t *shortcut_w)
237{
238 ui_resource_t *res;
239
[3c8c580]240 /*
241 * This needs to work even if the menu is not open, so we cannot
242 * use the menu's resource, which is only created after the menu
[46bd63c9]243 * is open (and its window is created). Use the parent window's
[3c8c580]244 * resource instead.
245 */
[46bd63c9]246 res = ui_window_get_res(mentry->menu->parent);
[b8b64a8]247
[c88d7f99]248 *caption_w = ui_text_width(res->font, mentry->caption);
249 *shortcut_w = ui_text_width(res->font, mentry->shortcut);
[b8b64a8]250}
251
252/** Compute width of menu entry.
253 *
254 * @param menu Menu
255 * @param caption_w Widht of caption text
256 * @param shortcut_w Width of shortcut text
[214aefb]257 * @return Width in pixels
258 */
[b8b64a8]259gfx_coord_t ui_menu_entry_calc_width(ui_menu_t *menu, gfx_coord_t caption_w,
260 gfx_coord_t shortcut_w)
[214aefb]261{
262 ui_resource_t *res;
263 gfx_coord_t hpad;
[b8b64a8]264 gfx_coord_t width;
[214aefb]265
[3c8c580]266 /*
267 * This needs to work even if the menu is not open, so we cannot
268 * use the menu's resource, which is only created after the menu
[46bd63c9]269 * is open (and its window is created). Use the parent window's
[3c8c580]270 * resource instead.
271 */
[46bd63c9]272 res = ui_window_get_res(menu->parent);
[214aefb]273
[b8b64a8]274 if (res->textmode)
[214aefb]275 hpad = menu_entry_hpad_text;
[b8b64a8]276 else
[214aefb]277 hpad = menu_entry_hpad;
[b8b64a8]278
279 width = caption_w + 2 * hpad;
280
281 if (shortcut_w != 0) {
282 if (res->textmode)
283 width += menu_entry_column_pad_text;
284 else
285 width += menu_entry_column_pad;
286
287 width += shortcut_w;
[214aefb]288 }
289
[b8b64a8]290 return width;
[214aefb]291}
292
293/** Get height of menu entry.
294 *
295 * @param mentry Menu entry
296 * @return Width in pixels
297 */
298gfx_coord_t ui_menu_entry_height(ui_menu_entry_t *mentry)
299{
300 ui_resource_t *res;
301 gfx_font_metrics_t metrics;
302 gfx_coord_t height;
303 gfx_coord_t vpad;
304
[3c8c580]305 /*
306 * This needs to work even if the menu is not open, so we cannot
307 * use the menu's resource, which is only created after the menu
[46bd63c9]308 * is open (and its window is created). Use the parent window's
[3c8c580]309 * resource instead.
310 */
[46bd63c9]311 res = ui_window_get_res(mentry->menu->parent);
[214aefb]312
313 if (res->textmode) {
314 vpad = menu_entry_vpad_text;
315 } else {
316 vpad = menu_entry_vpad;
317 }
318
[6186f9f]319 if (mentry->separator) {
320 /* Separator menu entry */
321 if (res->textmode)
322 height = menu_entry_sep_height_text;
323 else
324 height = menu_entry_sep_height;
325 } else {
326 /* Normal menu entry */
327 gfx_font_get_metrics(res->font, &metrics);
328 height = metrics.ascent + metrics.descent + 1;
329 }
330
[214aefb]331 return height + 2 * vpad;
332}
333
[c88d7f99]334/** Get menu entry accelerator character.
335 *
336 * @param mentry Menu entry
337 * @return Accelerator character (lowercase) or the null character if
338 * the menu entry has no accelerator.
339 */
340char32_t ui_menu_entry_get_accel(ui_menu_entry_t *mentry)
341{
342 return ui_accel_get(mentry->caption);
343}
344
[214aefb]345/** Paint menu entry.
346 *
347 * @param mentry Menu entry
348 * @param pos Position where to paint entry
349 * @return EOK on success or an error code
350 */
351errno_t ui_menu_entry_paint(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
352{
353 ui_resource_t *res;
[c88d7f99]354 ui_text_fmt_t fmt;
[214aefb]355 gfx_color_t *bg_color;
356 ui_menu_entry_geom_t geom;
[6186f9f]357 gfx_rect_t rect;
[214aefb]358 errno_t rc;
359
[3c8c580]360 res = ui_menu_get_res(mentry->menu);
[214aefb]361
362 ui_menu_entry_get_geom(mentry, pos, &geom);
363
[c88d7f99]364 ui_text_fmt_init(&fmt);
[4583015]365 fmt.font = res->font;
[214aefb]366 fmt.halign = gfx_halign_left;
367 fmt.valign = gfx_valign_top;
368
[0262f16c]369 if ((mentry->held && mentry->inside) ||
370 mentry == mentry->menu->selected) {
[214aefb]371 fmt.color = res->wnd_sel_text_color;
[c88d7f99]372 fmt.hgl_color = res->wnd_sel_text_hgl_color;
[214aefb]373 bg_color = res->wnd_sel_text_bg_color;
374 } else {
375 fmt.color = res->wnd_text_color;
[c88d7f99]376 fmt.hgl_color = res->wnd_text_hgl_color;
[214aefb]377 bg_color = res->wnd_face_color;
378 }
379
380 rc = gfx_set_color(res->gc, bg_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
[c88d7f99]388 rc = ui_paint_text(&geom.caption_pos, &fmt, mentry->caption);
[b8b64a8]389 if (rc != EOK)
390 goto error;
391
392 fmt.halign = gfx_halign_right;
393
[c88d7f99]394 rc = ui_paint_text(&geom.shortcut_pos, &fmt, mentry->shortcut);
[214aefb]395 if (rc != EOK)
396 goto error;
397
[6186f9f]398 if (mentry->separator) {
[81ec7e1]399 if (res->textmode) {
400 rect = geom.outer_rect;
401 rect.p0.x -= 1;
402 rect.p1.x += 1;
403
404 rc = ui_paint_text_hbrace(res, &rect, ui_box_single,
405 res->wnd_face_color);
406 if (rc != EOK)
407 goto error;
408 } else {
409 rect.p0 = geom.caption_pos;
410 rect.p1.x = geom.shortcut_pos.x;
411 rect.p1.y = rect.p0.y + 2;
412 rc = ui_paint_bevel(res->gc, &rect, res->wnd_shadow_color,
413 res->wnd_highlight_color, 1, NULL);
414 if (rc != EOK)
415 goto error;
416 }
[6186f9f]417 }
418
[214aefb]419 rc = gfx_update(res->gc);
420 if (rc != EOK)
421 goto error;
422
423 return EOK;
424error:
425 return rc;
426}
427
[59768c7]428/** Determine if entry is selectable.
429 *
430 * @return @c true iff entry is selectable
431 */
432bool ui_menu_entry_selectable(ui_menu_entry_t *mentry)
433{
434 return !mentry->separator;
435}
436
[214aefb]437/** Handle button press in menu entry.
438 *
439 * @param mentry Menu entry
[0262f16c]440 * @param pos Menu entry position
[214aefb]441 */
[0262f16c]442void ui_menu_entry_press(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
[214aefb]443{
[0262f16c]444 if (mentry->held)
445 return;
[214aefb]446
[6186f9f]447 if (mentry->separator)
448 return;
449
[0262f16c]450 mentry->inside = true;
451 mentry->held = true;
452 ui_menu_entry_paint(mentry, pos);
453}
454
455/** Handle button release in menu entry.
456 *
457 * @param mentry Menu entry
458 */
459void ui_menu_entry_release(ui_menu_entry_t *mentry)
460{
461 if (!mentry->held)
462 return;
[214aefb]463
[0262f16c]464 mentry->held = false;
[214aefb]465
[71edd430]466 if (mentry->inside)
467 ui_menu_entry_activate(mentry);
[59768c7]468}
[214aefb]469
[59768c7]470/** Activate menu entry.
471 *
472 * @param mentry Menu entry
473 */
474void ui_menu_entry_activate(ui_menu_entry_t *mentry)
475{
[46bd63c9]476 /* Close menu */
477 ui_menu_close_req(mentry->menu);
[59768c7]478
479 /* Call back */
480 ui_menu_entry_cb(mentry);
[214aefb]481}
482
[95a9cbc]483/** Call menu entry callback.
484 *
485 * @param mentry Menu entry
486 */
487void ui_menu_entry_cb(ui_menu_entry_t *mentry)
488{
489 if (mentry->cb != NULL)
490 mentry->cb(mentry, mentry->arg);
491}
492
[0262f16c]493/** Pointer entered menu entry.
494 *
495 * @param mentry Menu entry
496 * @param pos Menu entry position
497 */
498void ui_menu_entry_enter(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
499{
500 if (mentry->inside)
501 return;
502
503 mentry->inside = true;
504 if (mentry->held)
505 (void) ui_menu_entry_paint(mentry, pos);
506}
507
508/** Pointer left menu entry.
509 *
510 * @param mentry Menu entry
511 * @param pos Menu entry position
512 */
513void ui_menu_entry_leave(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
514{
515 if (!mentry->inside)
516 return;
517
518 mentry->inside = false;
519 if (mentry->held)
520 (void) ui_menu_entry_paint(mentry, pos);
521}
522
523/** Handle menu entry position event.
524 *
525 * @param mentry Menu entry
526 * @param pos Menu entry position (top-left corner)
527 * @param pos_event Position event
528 * @return @c ui_claimed iff the event is claimed
529 */
530ui_evclaim_t ui_menu_entry_pos_event(ui_menu_entry_t *mentry,
531 gfx_coord2_t *pos, pos_event_t *event)
532{
533 ui_menu_entry_geom_t geom;
534 gfx_coord2_t ppos;
535 bool inside;
536
537 ppos.x = event->hpos;
538 ppos.y = event->vpos;
539
540 ui_menu_entry_get_geom(mentry, pos, &geom);
541 inside = gfx_pix_inside_rect(&ppos, &geom.outer_rect);
542
543 switch (event->type) {
544 case POS_PRESS:
545 if (inside) {
546 ui_menu_entry_press(mentry, pos);
547 return ui_claimed;
548 }
549 break;
550 case POS_RELEASE:
551 if (mentry->held) {
552 ui_menu_entry_release(mentry);
553 return ui_claimed;
554 }
555 break;
556 case POS_UPDATE:
557 if (inside && !mentry->inside) {
558 ui_menu_entry_enter(mentry, pos);
559 return ui_claimed;
560 } else if (!inside && mentry->inside) {
561 ui_menu_entry_leave(mentry, pos);
562 }
563 break;
[8edec53]564 case POS_DCLICK:
565 break;
[0262f16c]566 }
567
568 return ui_unclaimed;
569}
570
[214aefb]571/** Get menu entry geometry.
572 *
573 * @param mentry Menu entry
574 * @param spos Entry position
575 * @param geom Structure to fill in with computed geometry
576 */
577void ui_menu_entry_get_geom(ui_menu_entry_t *mentry, gfx_coord2_t *pos,
578 ui_menu_entry_geom_t *geom)
579{
580 ui_resource_t *res;
581 gfx_coord_t hpad;
582 gfx_coord_t vpad;
583 gfx_coord_t width;
584
[3c8c580]585 res = ui_menu_get_res(mentry->menu);
[214aefb]586
587 if (res->textmode) {
588 hpad = menu_entry_hpad_text;
589 vpad = menu_entry_vpad_text;
590 } else {
591 hpad = menu_entry_hpad;
592 vpad = menu_entry_vpad;
593 }
594
[b8b64a8]595 /* Compute total width of menu entry */
596 width = ui_menu_entry_calc_width(mentry->menu,
597 mentry->menu->max_caption_w, mentry->menu->max_shortcut_w);
[214aefb]598
599 geom->outer_rect.p0 = *pos;
600 geom->outer_rect.p1.x = geom->outer_rect.p0.x + width;
601 geom->outer_rect.p1.y = geom->outer_rect.p0.y +
602 ui_menu_entry_height(mentry);
[b8b64a8]603
604 geom->caption_pos.x = pos->x + hpad;
605 geom->caption_pos.y = pos->y + vpad;
606
607 geom->shortcut_pos.x = geom->outer_rect.p1.x - hpad;
608 geom->shortcut_pos.y = pos->y + vpad;
[214aefb]609}
610
611/** @}
612 */
Note: See TracBrowser for help on using the repository browser.