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

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

Add font to gfx_text_fmt_t

This is quite logical and saves us one argument that we need to pass to
all text formatting functions.

  • 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.font = res->font;
354 fmt.halign = gfx_halign_left;
355 fmt.valign = gfx_valign_top;
356
357 if ((mentry->held && mentry->inside) ||
358 mentry == mentry->menu->selected) {
359 fmt.color = res->wnd_sel_text_color;
360 bg_color = res->wnd_sel_text_bg_color;
361 } else {
362 fmt.color = res->wnd_text_color;
363 bg_color = res->wnd_face_color;
364 }
365
366 rc = gfx_set_color(res->gc, bg_color);
367 if (rc != EOK)
368 goto error;
369
370 rc = gfx_fill_rect(res->gc, &geom.outer_rect);
371 if (rc != EOK)
372 goto error;
373
374 rc = gfx_puttext(&geom.caption_pos, &fmt, mentry->caption);
375 if (rc != EOK)
376 goto error;
377
378 fmt.halign = gfx_halign_right;
379
380 rc = gfx_puttext(&geom.shortcut_pos, &fmt, mentry->shortcut);
381 if (rc != EOK)
382 goto error;
383
384 if (mentry->separator) {
385 if (res->textmode) {
386 rect = geom.outer_rect;
387 rect.p0.x -= 1;
388 rect.p1.x += 1;
389
390 rc = ui_paint_text_hbrace(res, &rect, ui_box_single,
391 res->wnd_face_color);
392 if (rc != EOK)
393 goto error;
394 } else {
395 rect.p0 = geom.caption_pos;
396 rect.p1.x = geom.shortcut_pos.x;
397 rect.p1.y = rect.p0.y + 2;
398 rc = ui_paint_bevel(res->gc, &rect, res->wnd_shadow_color,
399 res->wnd_highlight_color, 1, NULL);
400 if (rc != EOK)
401 goto error;
402 }
403 }
404
405 rc = gfx_update(res->gc);
406 if (rc != EOK)
407 goto error;
408
409 return EOK;
410error:
411 return rc;
412}
413
414/** Determine if entry is selectable.
415 *
416 * @return @c true iff entry is selectable
417 */
418bool ui_menu_entry_selectable(ui_menu_entry_t *mentry)
419{
420 return !mentry->separator;
421}
422
423/** Handle button press in menu entry.
424 *
425 * @param mentry Menu entry
426 * @param pos Menu entry position
427 */
428void ui_menu_entry_press(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
429{
430 if (mentry->held)
431 return;
432
433 if (mentry->separator)
434 return;
435
436 mentry->inside = true;
437 mentry->held = true;
438 ui_menu_entry_paint(mentry, pos);
439}
440
441/** Handle button release in menu entry.
442 *
443 * @param mentry Menu entry
444 */
445void ui_menu_entry_release(ui_menu_entry_t *mentry)
446{
447 if (!mentry->held)
448 return;
449
450 mentry->held = false;
451
452 ui_menu_entry_activate(mentry);
453}
454
455/** Activate menu entry.
456 *
457 * @param mentry Menu entry
458 */
459void ui_menu_entry_activate(ui_menu_entry_t *mentry)
460{
461 /* Deactivate menu bar, close menu */
462 ui_menu_bar_deactivate(mentry->menu->mbar);
463
464 /* Call back */
465 ui_menu_entry_cb(mentry);
466}
467
468/** Call menu entry callback.
469 *
470 * @param mentry Menu entry
471 */
472void ui_menu_entry_cb(ui_menu_entry_t *mentry)
473{
474 if (mentry->cb != NULL)
475 mentry->cb(mentry, mentry->arg);
476}
477
478/** Pointer entered menu entry.
479 *
480 * @param mentry Menu entry
481 * @param pos Menu entry position
482 */
483void ui_menu_entry_enter(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
484{
485 if (mentry->inside)
486 return;
487
488 mentry->inside = true;
489 if (mentry->held)
490 (void) ui_menu_entry_paint(mentry, pos);
491}
492
493/** Pointer left menu entry.
494 *
495 * @param mentry Menu entry
496 * @param pos Menu entry position
497 */
498void ui_menu_entry_leave(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
499{
500 if (!mentry->inside)
501 return;
502
503 mentry->inside = false;
504 if (mentry->held)
505 (void) ui_menu_entry_paint(mentry, pos);
506}
507
508/** Handle menu entry position event.
509 *
510 * @param mentry Menu entry
511 * @param pos Menu entry position (top-left corner)
512 * @param pos_event Position event
513 * @return @c ui_claimed iff the event is claimed
514 */
515ui_evclaim_t ui_menu_entry_pos_event(ui_menu_entry_t *mentry,
516 gfx_coord2_t *pos, pos_event_t *event)
517{
518 ui_menu_entry_geom_t geom;
519 gfx_coord2_t ppos;
520 bool inside;
521
522 ppos.x = event->hpos;
523 ppos.y = event->vpos;
524
525 ui_menu_entry_get_geom(mentry, pos, &geom);
526 inside = gfx_pix_inside_rect(&ppos, &geom.outer_rect);
527
528 switch (event->type) {
529 case POS_PRESS:
530 if (inside) {
531 ui_menu_entry_press(mentry, pos);
532 return ui_claimed;
533 }
534 break;
535 case POS_RELEASE:
536 if (mentry->held) {
537 ui_menu_entry_release(mentry);
538 return ui_claimed;
539 }
540 break;
541 case POS_UPDATE:
542 if (inside && !mentry->inside) {
543 ui_menu_entry_enter(mentry, pos);
544 return ui_claimed;
545 } else if (!inside && mentry->inside) {
546 ui_menu_entry_leave(mentry, pos);
547 }
548 break;
549 case POS_DCLICK:
550 break;
551 }
552
553 return ui_unclaimed;
554}
555
556/** Get menu entry geometry.
557 *
558 * @param mentry Menu entry
559 * @param spos Entry position
560 * @param geom Structure to fill in with computed geometry
561 */
562void ui_menu_entry_get_geom(ui_menu_entry_t *mentry, gfx_coord2_t *pos,
563 ui_menu_entry_geom_t *geom)
564{
565 ui_resource_t *res;
566 gfx_coord_t hpad;
567 gfx_coord_t vpad;
568 gfx_coord_t width;
569
570 res = ui_menu_get_res(mentry->menu);
571
572 if (res->textmode) {
573 hpad = menu_entry_hpad_text;
574 vpad = menu_entry_vpad_text;
575 } else {
576 hpad = menu_entry_hpad;
577 vpad = menu_entry_vpad;
578 }
579
580 /* Compute total width of menu entry */
581 width = ui_menu_entry_calc_width(mentry->menu,
582 mentry->menu->max_caption_w, mentry->menu->max_shortcut_w);
583
584 geom->outer_rect.p0 = *pos;
585 geom->outer_rect.p1.x = geom->outer_rect.p0.x + width;
586 geom->outer_rect.p1.y = geom->outer_rect.p0.y +
587 ui_menu_entry_height(mentry);
588
589 geom->caption_pos.x = pos->x + hpad;
590 geom->caption_pos.y = pos->y + vpad;
591
592 geom->shortcut_pos.x = geom->outer_rect.p1.x - hpad;
593 geom->shortcut_pos.y = pos->y + vpad;
594}
595
596/** @}
597 */
Note: See TracBrowser for help on using the repository browser.