blob: 8a355f838cc8bcfa8b3f5cac78146e141c45cb02 [file] [log] [blame]
Simon Glass9f513932023-01-06 08:52:38 -06001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Implementation of a menu in a scene
4 *
5 * Copyright 2022 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
7 */
8
Simon Glassfe4c1e22023-06-01 10:22:43 -06009#define LOG_CATEGORY LOGC_EXPO
Simon Glass9f513932023-01-06 08:52:38 -060010
11#include <common.h>
12#include <dm.h>
13#include <expo.h>
14#include <malloc.h>
15#include <mapmem.h>
16#include <menu.h>
17#include <video.h>
18#include <video_console.h>
19#include <linux/input.h>
20#include "scene_internal.h"
21
22static void scene_menuitem_destroy(struct scene_menitem *item)
23{
24 free(item->name);
25 free(item);
26}
27
28void scene_menu_destroy(struct scene_obj_menu *menu)
29{
30 struct scene_menitem *item, *next;
31
32 list_for_each_entry_safe(item, next, &menu->item_head, sibling)
33 scene_menuitem_destroy(item);
34}
35
Simon Glassc55eeba2023-06-01 10:22:54 -060036static struct scene_menitem *scene_menuitem_find(struct scene_obj_menu *menu,
37 int id)
38{
39 struct scene_menitem *item;
40
41 list_for_each_entry(item, &menu->item_head, sibling) {
42 if (item->id == id)
43 return item;
44 }
45
46 return NULL;
47}
48
49/**
50 * update_pointers() - Update the pointer object and handle highlights
51 *
52 * @menu: Menu to update
53 * @id: ID of menu item to select/deselect
54 * @point: true if @id is being selected, false if it is being deselected
55 */
56static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
57{
58 struct scene *scn = menu->obj.scene;
Simon Glassd353b752023-06-01 10:22:55 -060059 const bool stack = scn->expo->popup;
Simon Glassc55eeba2023-06-01 10:22:54 -060060 const struct scene_menitem *item;
61 int ret;
62
63 item = scene_menuitem_find(menu, id);
64 if (!item)
65 return log_msg_ret("itm", -ENOENT);
66
67 /* adjust the pointer object to point to the selected item */
68 if (menu->pointer_id && item && point) {
69 struct scene_obj *label;
70
71 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
72
73 ret = scene_obj_set_pos(scn, menu->pointer_id,
74 menu->obj.dim.x + 200, label->dim.y);
75 if (ret < 0)
76 return log_msg_ret("ptr", ret);
77 }
78
Simon Glassd353b752023-06-01 10:22:55 -060079 if (stack) {
80 point &= scn->highlight_id == menu->obj.id;
81 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
82 point ? SCENEOF_POINT : 0);
83 }
84
Simon Glassc55eeba2023-06-01 10:22:54 -060085 return 0;
86}
87
Simon Glass9f513932023-01-06 08:52:38 -060088/**
89 * menu_point_to_item() - Point to a particular menu item
90 *
91 * Sets the currently pointed-to / highlighted menu item
92 */
93static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
94{
Simon Glassc55eeba2023-06-01 10:22:54 -060095 if (menu->cur_item_id)
96 update_pointers(menu, menu->cur_item_id, false);
Simon Glass9f513932023-01-06 08:52:38 -060097 menu->cur_item_id = item_id;
Simon Glassc55eeba2023-06-01 10:22:54 -060098 update_pointers(menu, item_id, true);
Simon Glass9f513932023-01-06 08:52:38 -060099}
100
Simon Glass86f1ac52023-06-01 10:23:00 -0600101static int scene_bbox_union(struct scene *scn, uint id, int inset,
Simon Glass7a960052023-06-01 10:22:52 -0600102 struct vidconsole_bbox *bbox)
103{
104 struct scene_obj *obj;
105
106 if (!id)
107 return 0;
108 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
109 if (!obj)
110 return log_msg_ret("obj", -ENOENT);
111 if (bbox->valid) {
Simon Glass86f1ac52023-06-01 10:23:00 -0600112 bbox->x0 = min(bbox->x0, obj->dim.x - inset);
Simon Glass7a960052023-06-01 10:22:52 -0600113 bbox->y0 = min(bbox->y0, obj->dim.y);
Simon Glass86f1ac52023-06-01 10:23:00 -0600114 bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset);
Simon Glass7a960052023-06-01 10:22:52 -0600115 bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
116 } else {
Simon Glass86f1ac52023-06-01 10:23:00 -0600117 bbox->x0 = obj->dim.x - inset;
Simon Glass7a960052023-06-01 10:22:52 -0600118 bbox->y0 = obj->dim.y;
Simon Glass86f1ac52023-06-01 10:23:00 -0600119 bbox->x1 = obj->dim.x + obj->dim.w + inset;
Simon Glass7a960052023-06-01 10:22:52 -0600120 bbox->y1 = obj->dim.y + obj->dim.h;
121 bbox->valid = true;
122 }
123
124 return 0;
125}
126
127/**
128 * scene_menu_calc_bbox() - Calculate bounding boxes for the menu
129 *
130 * @menu: Menu to process
131 * @bbox: Returns bounding box of menu including prompts
132 * @label_bbox: Returns bounding box of labels
133 */
134static void scene_menu_calc_bbox(struct scene_obj_menu *menu,
135 struct vidconsole_bbox *bbox,
136 struct vidconsole_bbox *label_bbox)
137{
Simon Glass86f1ac52023-06-01 10:23:00 -0600138 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
Simon Glass7a960052023-06-01 10:22:52 -0600139 const struct scene_menitem *item;
140
141 bbox->valid = false;
Simon Glass86f1ac52023-06-01 10:23:00 -0600142 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600143
144 label_bbox->valid = false;
145
146 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glass86f1ac52023-06-01 10:23:00 -0600147 scene_bbox_union(menu->obj.scene, item->label_id,
148 theme->menu_inset, bbox);
149 scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox);
150 scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox);
151 scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600152
153 /* Get the bounding box of all labels */
Simon Glass86f1ac52023-06-01 10:23:00 -0600154 scene_bbox_union(menu->obj.scene, item->label_id,
155 theme->menu_inset, label_bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600156 }
Simon Glass86f1ac52023-06-01 10:23:00 -0600157
158 /*
159 * subtract the final menuitem's gap to keep the insert the same top
160 * and bottom
161 */
162 label_bbox->y1 -= theme->menuitem_gap_y;
Simon Glass7a960052023-06-01 10:22:52 -0600163}
164
165int scene_menu_calc_dims(struct scene_obj_menu *menu)
166{
167 struct vidconsole_bbox bbox, label_bbox;
168 const struct scene_menitem *item;
169
170 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
171
172 /* Make all labels the same size */
173 if (label_bbox.valid) {
174 list_for_each_entry(item, &menu->item_head, sibling) {
175 scene_obj_set_size(menu->obj.scene, item->label_id,
176 label_bbox.x1 - label_bbox.x0,
177 label_bbox.y1 - label_bbox.y0);
178 }
179 }
180
181 if (bbox.valid) {
182 menu->obj.dim.w = bbox.x1 - bbox.x0;
183 menu->obj.dim.h = bbox.y1 - bbox.y0;
184 }
185
186 return 0;
187}
188
Simon Glass9f513932023-01-06 08:52:38 -0600189int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
190{
Simon Glassd353b752023-06-01 10:22:55 -0600191 const bool open = menu->obj.flags & SCENEOF_OPEN;
192 struct expo *exp = scn->expo;
193 const bool stack = exp->popup;
Simon Glass86f1ac52023-06-01 10:23:00 -0600194 const struct expo_theme *theme = &exp->theme;
Simon Glass9f513932023-01-06 08:52:38 -0600195 struct scene_menitem *item;
Simon Glassc55eeba2023-06-01 10:22:54 -0600196 uint sel_id;
Simon Glassd353b752023-06-01 10:22:55 -0600197 int x, y;
Simon Glass9f513932023-01-06 08:52:38 -0600198 int ret;
199
Simon Glassd353b752023-06-01 10:22:55 -0600200 x = menu->obj.dim.x;
Simon Glass7b043952023-06-01 10:22:49 -0600201 y = menu->obj.dim.y;
Simon Glass9f513932023-01-06 08:52:38 -0600202 if (menu->title_id) {
Simon Glass7b043952023-06-01 10:22:49 -0600203 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
Simon Glass9f513932023-01-06 08:52:38 -0600204 if (ret < 0)
205 return log_msg_ret("tit", ret);
206
207 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
208 if (ret < 0)
209 return log_msg_ret("hei", ret);
210
Simon Glassd353b752023-06-01 10:22:55 -0600211 if (stack)
212 x += 200;
213 else
214 y += ret * 2;
Simon Glass9f513932023-01-06 08:52:38 -0600215 }
216
217 /*
218 * Currently everything is hard-coded to particular columns so this
219 * won't work on small displays and looks strange if the font size is
220 * small. This can be updated once text measuring is supported in
221 * vidconsole
222 */
Simon Glassc55eeba2023-06-01 10:22:54 -0600223 sel_id = menu->cur_item_id;
Simon Glass9f513932023-01-06 08:52:38 -0600224 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glassd353b752023-06-01 10:22:55 -0600225 bool selected;
Simon Glass9f513932023-01-06 08:52:38 -0600226 int height;
227
Simon Glassd353b752023-06-01 10:22:55 -0600228 ret = scene_obj_get_hw(scn, item->label_id, NULL);
Simon Glass9f513932023-01-06 08:52:38 -0600229 if (ret < 0)
230 return log_msg_ret("get", ret);
231 height = ret;
232
233 if (item->flags & SCENEMIF_GAP_BEFORE)
234 y += height;
235
236 /* select an item if not done already */
Simon Glassc55eeba2023-06-01 10:22:54 -0600237 if (!sel_id)
238 sel_id = item->id;
Simon Glass9f513932023-01-06 08:52:38 -0600239
Simon Glassd353b752023-06-01 10:22:55 -0600240 selected = sel_id == item->id;
241
Simon Glass9f513932023-01-06 08:52:38 -0600242 /*
243 * Put the label on the left, then leave a space for the
244 * pointer, then the key and the description
245 */
Simon Glass86f1ac52023-06-01 10:23:00 -0600246 ret = scene_obj_set_pos(scn, item->label_id,
247 x + theme->menu_inset, y);
Simon Glass9f513932023-01-06 08:52:38 -0600248 if (ret < 0)
Simon Glassd353b752023-06-01 10:22:55 -0600249 return log_msg_ret("nam", ret);
250 scene_obj_set_hide(scn, item->label_id,
251 stack && !open && !selected);
Simon Glass9f513932023-01-06 08:52:38 -0600252
Simon Glassd353b752023-06-01 10:22:55 -0600253 if (item->key_id) {
254 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
255 if (ret < 0)
256 return log_msg_ret("key", ret);
257 }
Simon Glass9f513932023-01-06 08:52:38 -0600258
Simon Glassd353b752023-06-01 10:22:55 -0600259 if (item->desc_id) {
260 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
261 if (ret < 0)
262 return log_msg_ret("des", ret);
263 }
Simon Glass9f513932023-01-06 08:52:38 -0600264
265 if (item->preview_id) {
266 bool hide;
267
268 /*
269 * put all previews on top of each other, on the right
270 * size of the display
271 */
272 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
273 if (ret < 0)
274 return log_msg_ret("prev", ret);
275
276 hide = menu->cur_item_id != item->id;
277 ret = scene_obj_set_hide(scn, item->preview_id, hide);
278 if (ret < 0)
279 return log_msg_ret("hid", ret);
280 }
281
Simon Glassd353b752023-06-01 10:22:55 -0600282 if (!stack || open)
Simon Glass86f1ac52023-06-01 10:23:00 -0600283 y += height + theme->menuitem_gap_y;
Simon Glass9f513932023-01-06 08:52:38 -0600284 }
285
Simon Glassc55eeba2023-06-01 10:22:54 -0600286 if (sel_id)
287 menu_point_to_item(menu, sel_id);
Simon Glass9f513932023-01-06 08:52:38 -0600288
289 return 0;
290}
291
292int scene_menu(struct scene *scn, const char *name, uint id,
293 struct scene_obj_menu **menup)
294{
295 struct scene_obj_menu *menu;
296 int ret;
297
298 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
299 sizeof(struct scene_obj_menu),
300 (struct scene_obj **)&menu);
301 if (ret < 0)
302 return log_msg_ret("obj", -ENOMEM);
303
304 if (menup)
305 *menup = menu;
306 INIT_LIST_HEAD(&menu->item_head);
307
Simon Glass9f513932023-01-06 08:52:38 -0600308 return menu->obj.id;
309}
310
311static struct scene_menitem *scene_menu_find_key(struct scene *scn,
312 struct scene_obj_menu *menu,
313 int key)
314{
315 struct scene_menitem *item;
316
317 list_for_each_entry(item, &menu->item_head, sibling) {
318 if (item->key_id) {
319 struct scene_obj_txt *txt;
320 const char *str;
321
322 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
323 if (txt) {
324 str = expo_get_str(scn->expo, txt->str_id);
325 if (str && *str == key)
326 return item;
327 }
328 }
329 }
330
331 return NULL;
332}
333
334int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
335 struct expo_action *event)
336{
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600337 const bool open = menu->obj.flags & SCENEOF_OPEN;
Simon Glass9f513932023-01-06 08:52:38 -0600338 struct scene_menitem *item, *cur, *key_item;
339
340 cur = NULL;
341 key_item = NULL;
342
343 if (!list_empty(&menu->item_head)) {
344 list_for_each_entry(item, &menu->item_head, sibling) {
345 /* select an item if not done already */
346 if (menu->cur_item_id == item->id) {
347 cur = item;
348 break;
349 }
350 }
351 }
352
353 if (!cur)
354 return -ENOTTY;
355
356 switch (key) {
357 case BKEY_UP:
358 if (item != list_first_entry(&menu->item_head,
359 struct scene_menitem, sibling)) {
360 item = list_entry(item->sibling.prev,
361 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600362 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600363 event->select.id = item->id;
364 log_debug("up to item %d\n", event->select.id);
365 }
366 break;
367 case BKEY_DOWN:
368 if (!list_is_last(&item->sibling, &menu->item_head)) {
369 item = list_entry(item->sibling.next,
370 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600371 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600372 event->select.id = item->id;
373 log_debug("down to item %d\n", event->select.id);
374 }
375 break;
376 case BKEY_SELECT:
377 event->type = EXPOACT_SELECT;
378 event->select.id = item->id;
379 log_debug("select item %d\n", event->select.id);
380 break;
381 case BKEY_QUIT:
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600382 if (scn->expo->popup && open) {
383 event->type = EXPOACT_CLOSE;
384 event->select.id = menu->obj.id;
385 } else {
386 event->type = EXPOACT_QUIT;
387 log_debug("menu quit\n");
388 }
Simon Glass9f513932023-01-06 08:52:38 -0600389 break;
390 case '0'...'9':
391 key_item = scene_menu_find_key(scn, menu, key);
392 if (key_item) {
393 event->type = EXPOACT_SELECT;
394 event->select.id = key_item->id;
395 }
396 break;
397 }
398
399 menu_point_to_item(menu, item->id);
400
401 return 0;
402}
403
404int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
405 uint key_id, uint label_id, uint desc_id, uint preview_id,
406 uint flags, struct scene_menitem **itemp)
407{
408 struct scene_obj_menu *menu;
409 struct scene_menitem *item;
Simon Glass9f513932023-01-06 08:52:38 -0600410
411 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
412 if (!menu)
413 return log_msg_ret("find", -ENOENT);
414
415 /* Check that the text ID is valid */
Simon Glassd353b752023-06-01 10:22:55 -0600416 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
Simon Glass9f513932023-01-06 08:52:38 -0600417 return log_msg_ret("txt", -EINVAL);
418
419 item = calloc(1, sizeof(struct scene_obj_menu));
420 if (!item)
421 return log_msg_ret("item", -ENOMEM);
422 item->name = strdup(name);
423 if (!item->name) {
424 free(item);
425 return log_msg_ret("name", -ENOMEM);
426 }
427
428 item->id = resolve_id(scn->expo, id);
429 item->key_id = key_id;
430 item->label_id = label_id;
431 item->desc_id = desc_id;
432 item->preview_id = preview_id;
433 item->flags = flags;
434 list_add_tail(&item->sibling, &menu->item_head);
435
Simon Glass9f513932023-01-06 08:52:38 -0600436 if (itemp)
437 *itemp = item;
438
439 return item->id;
440}
441
442int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
443{
444 struct scene_obj_menu *menu;
445 struct scene_obj_txt *txt;
446
447 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
448 if (!menu)
449 return log_msg_ret("menu", -ENOENT);
450
451 /* Check that the ID is valid */
452 if (title_id) {
453 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
454 if (!txt)
455 return log_msg_ret("txt", -EINVAL);
456 }
457
458 menu->title_id = title_id;
459
460 return 0;
461}
462
463int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
464{
465 struct scene_obj_menu *menu;
466 struct scene_obj *obj;
467
468 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
469 if (!menu)
470 return log_msg_ret("menu", -ENOENT);
471
472 /* Check that the ID is valid */
473 if (pointer_id) {
474 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
475 if (!obj)
476 return log_msg_ret("obj", -EINVAL);
477 }
478
479 menu->pointer_id = pointer_id;
480
481 return 0;
482}
483
484int scene_menu_display(struct scene_obj_menu *menu)
485{
486 struct scene *scn = menu->obj.scene;
487 struct scene_obj_txt *pointer;
488 struct expo *exp = scn->expo;
489 struct scene_menitem *item;
490 const char *pstr;
491
492 printf("U-Boot : Boot Menu\n\n");
493 if (menu->title_id) {
494 struct scene_obj_txt *txt;
495 const char *str;
496
497 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
498 if (!txt)
499 return log_msg_ret("txt", -EINVAL);
500
501 str = expo_get_str(exp, txt->str_id);
502 printf("%s\n\n", str);
503 }
504
505 if (list_empty(&menu->item_head))
506 return 0;
507
508 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
509 pstr = expo_get_str(scn->expo, pointer->str_id);
510
511 list_for_each_entry(item, &menu->item_head, sibling) {
512 struct scene_obj_txt *key = NULL, *label = NULL;
513 struct scene_obj_txt *desc = NULL;
514 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
515
516 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
517 if (key)
518 kstr = expo_get_str(exp, key->str_id);
519
520 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
521 if (label)
522 lstr = expo_get_str(exp, label->str_id);
523
524 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
525 if (desc)
526 dstr = expo_get_str(exp, desc->str_id);
527
528 printf("%3s %3s %-10s %s\n",
529 pointer && menu->cur_item_id == item->id ? pstr : "",
530 kstr, lstr, dstr);
531 }
532
533 return -ENOTSUPP;
534}
Simon Glass01922ec2023-06-01 10:22:57 -0600535
536void scene_menu_render(struct scene_obj_menu *menu)
537{
538 struct expo *exp = menu->obj.scene->expo;
539 const struct expo_theme *theme = &exp->theme;
540 struct vidconsole_bbox bbox, label_bbox;
541 struct udevice *dev = exp->display;
542 struct video_priv *vid_priv;
543 struct udevice *cons = exp->cons;
544 struct vidconsole_colour old;
545 enum colour_idx fore, back;
546
547 if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
548 fore = VID_BLACK;
549 back = VID_WHITE;
550 } else {
551 fore = VID_LIGHT_GRAY;
552 back = VID_BLACK;
553 }
554
555 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
556 vidconsole_push_colour(cons, fore, back, &old);
557 vid_priv = dev_get_uclass_priv(dev);
558 video_fill_part(dev, label_bbox.x0 - theme->menu_inset,
559 label_bbox.y0 - theme->menu_inset,
560 label_bbox.x1, label_bbox.y1 + theme->menu_inset,
561 vid_priv->colour_fg);
562 vidconsole_pop_colour(cons, &old);
563}
Simon Glass12f57732023-06-01 10:22:58 -0600564
565int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
566{
567 struct scene_menitem *item;
568
569 scene_render_deps(scn, menu->title_id);
570 scene_render_deps(scn, menu->cur_item_id);
571 scene_render_deps(scn, menu->pointer_id);
572
573 list_for_each_entry(item, &menu->item_head, sibling) {
574 scene_render_deps(scn, item->key_id);
575 scene_render_deps(scn, item->label_id);
576 scene_render_deps(scn, item->desc_id);
577 }
578
579 return 0;
580}