blob: 17150af145dcd4ea2dbcbf7e0e63c4c2f7af64de [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
Simon Glass9f513932023-01-06 08:52:38 -060011#include <dm.h>
12#include <expo.h>
13#include <malloc.h>
14#include <mapmem.h>
15#include <menu.h>
16#include <video.h>
17#include <video_console.h>
18#include <linux/input.h>
19#include "scene_internal.h"
20
21static void scene_menuitem_destroy(struct scene_menitem *item)
22{
23 free(item->name);
24 free(item);
25}
26
27void scene_menu_destroy(struct scene_obj_menu *menu)
28{
29 struct scene_menitem *item, *next;
30
31 list_for_each_entry_safe(item, next, &menu->item_head, sibling)
32 scene_menuitem_destroy(item);
33}
34
Simon Glass5fd4f782023-08-14 16:40:32 -060035struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
36 int id)
Simon Glassc55eeba2023-06-01 10:22:54 -060037{
38 struct scene_menitem *item;
39
40 list_for_each_entry(item, &menu->item_head, sibling) {
41 if (item->id == id)
42 return item;
43 }
44
45 return NULL;
46}
47
Simon Glass4462fa32023-08-14 16:40:38 -060048struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
49 uint seq)
50{
51 struct scene_menitem *item;
52 uint i;
53
54 i = 0;
55 list_for_each_entry(item, &menu->item_head, sibling) {
56 if (i == seq)
57 return item;
58 i++;
59 }
60
61 return NULL;
62}
63
Simon Glass100389f2024-10-14 16:31:58 -060064struct scene_menitem *scene_menuitem_find_val(const struct scene_obj_menu *menu,
65 int val)
66{
67 struct scene_menitem *item;
68 uint i;
69
70 i = 0;
71 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glass6f3e87a2024-10-14 16:32:00 -060072 if (item->value == INT_MAX ? val == i : item->value == val)
Simon Glass100389f2024-10-14 16:31:58 -060073 return item;
74 i++;
75 }
76
77 return NULL;
78}
79
Simon Glassc55eeba2023-06-01 10:22:54 -060080/**
81 * update_pointers() - Update the pointer object and handle highlights
82 *
83 * @menu: Menu to update
84 * @id: ID of menu item to select/deselect
85 * @point: true if @id is being selected, false if it is being deselected
86 */
87static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
88{
89 struct scene *scn = menu->obj.scene;
Simon Glassd353b752023-06-01 10:22:55 -060090 const bool stack = scn->expo->popup;
Simon Glassc55eeba2023-06-01 10:22:54 -060091 const struct scene_menitem *item;
92 int ret;
93
94 item = scene_menuitem_find(menu, id);
95 if (!item)
96 return log_msg_ret("itm", -ENOENT);
97
98 /* adjust the pointer object to point to the selected item */
99 if (menu->pointer_id && item && point) {
100 struct scene_obj *label;
101
102 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
103
104 ret = scene_obj_set_pos(scn, menu->pointer_id,
105 menu->obj.dim.x + 200, label->dim.y);
106 if (ret < 0)
107 return log_msg_ret("ptr", ret);
108 }
109
Simon Glassd353b752023-06-01 10:22:55 -0600110 if (stack) {
111 point &= scn->highlight_id == menu->obj.id;
112 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
113 point ? SCENEOF_POINT : 0);
114 }
115
Simon Glassc55eeba2023-06-01 10:22:54 -0600116 return 0;
117}
118
Simon Glass9f513932023-01-06 08:52:38 -0600119/**
120 * menu_point_to_item() - Point to a particular menu item
121 *
122 * Sets the currently pointed-to / highlighted menu item
123 */
124static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
125{
Simon Glassc55eeba2023-06-01 10:22:54 -0600126 if (menu->cur_item_id)
127 update_pointers(menu, menu->cur_item_id, false);
Simon Glass9f513932023-01-06 08:52:38 -0600128 menu->cur_item_id = item_id;
Simon Glassc55eeba2023-06-01 10:22:54 -0600129 update_pointers(menu, item_id, true);
Simon Glass9f513932023-01-06 08:52:38 -0600130}
131
Simon Glassf0994692023-10-01 19:13:29 -0600132void scene_menu_calc_bbox(struct scene_obj_menu *menu,
133 struct vidconsole_bbox *bbox,
134 struct vidconsole_bbox *label_bbox)
Simon Glass7a960052023-06-01 10:22:52 -0600135{
Simon Glass86f1ac52023-06-01 10:23:00 -0600136 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
Simon Glass7a960052023-06-01 10:22:52 -0600137 const struct scene_menitem *item;
138
139 bbox->valid = false;
Simon Glass86f1ac52023-06-01 10:23:00 -0600140 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600141
142 label_bbox->valid = false;
143
144 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glass86f1ac52023-06-01 10:23:00 -0600145 scene_bbox_union(menu->obj.scene, item->label_id,
146 theme->menu_inset, bbox);
147 scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox);
148 scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox);
149 scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600150
151 /* Get the bounding box of all labels */
Simon Glass86f1ac52023-06-01 10:23:00 -0600152 scene_bbox_union(menu->obj.scene, item->label_id,
153 theme->menu_inset, label_bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600154 }
Simon Glass86f1ac52023-06-01 10:23:00 -0600155
156 /*
157 * subtract the final menuitem's gap to keep the insert the same top
158 * and bottom
159 */
160 label_bbox->y1 -= theme->menuitem_gap_y;
Simon Glass7a960052023-06-01 10:22:52 -0600161}
162
163int scene_menu_calc_dims(struct scene_obj_menu *menu)
164{
165 struct vidconsole_bbox bbox, label_bbox;
166 const struct scene_menitem *item;
167
168 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
169
170 /* Make all labels the same size */
171 if (label_bbox.valid) {
172 list_for_each_entry(item, &menu->item_head, sibling) {
173 scene_obj_set_size(menu->obj.scene, item->label_id,
174 label_bbox.x1 - label_bbox.x0,
175 label_bbox.y1 - label_bbox.y0);
176 }
177 }
178
179 if (bbox.valid) {
180 menu->obj.dim.w = bbox.x1 - bbox.x0;
181 menu->obj.dim.h = bbox.y1 - bbox.y0;
182 }
183
184 return 0;
185}
186
Simon Glass377f18e2024-10-14 16:31:55 -0600187int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr,
188 struct scene_obj_menu *menu)
Simon Glass9f513932023-01-06 08:52:38 -0600189{
Simon Glassd353b752023-06-01 10:22:55 -0600190 const bool open = menu->obj.flags & SCENEOF_OPEN;
191 struct expo *exp = scn->expo;
192 const bool stack = exp->popup;
Simon Glass86f1ac52023-06-01 10:23:00 -0600193 const struct expo_theme *theme = &exp->theme;
Simon Glass9f513932023-01-06 08:52:38 -0600194 struct scene_menitem *item;
Simon Glassc55eeba2023-06-01 10:22:54 -0600195 uint sel_id;
Simon Glassd353b752023-06-01 10:22:55 -0600196 int x, y;
Simon Glass9f513932023-01-06 08:52:38 -0600197 int ret;
198
Simon Glassd353b752023-06-01 10:22:55 -0600199 x = menu->obj.dim.x;
Simon Glass7b043952023-06-01 10:22:49 -0600200 y = menu->obj.dim.y;
Simon Glass9f513932023-01-06 08:52:38 -0600201 if (menu->title_id) {
Simon Glass377f18e2024-10-14 16:31:55 -0600202 int width;
203
Simon Glass7b043952023-06-01 10:22:49 -0600204 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
Simon Glass9f513932023-01-06 08:52:38 -0600205 if (ret < 0)
206 return log_msg_ret("tit", ret);
207
Simon Glass377f18e2024-10-14 16:31:55 -0600208 ret = scene_obj_get_hw(scn, menu->title_id, &width);
Simon Glass9f513932023-01-06 08:52:38 -0600209 if (ret < 0)
210 return log_msg_ret("hei", ret);
211
Simon Glassd353b752023-06-01 10:22:55 -0600212 if (stack)
Simon Glass377f18e2024-10-14 16:31:55 -0600213 x += arr->label_width + theme->menu_title_margin_x;
Simon Glassd353b752023-06-01 10:22:55 -0600214 else
215 y += ret * 2;
Simon Glass9f513932023-01-06 08:52:38 -0600216 }
217
218 /*
219 * Currently everything is hard-coded to particular columns so this
220 * won't work on small displays and looks strange if the font size is
221 * small. This can be updated once text measuring is supported in
222 * vidconsole
223 */
Simon Glassc55eeba2023-06-01 10:22:54 -0600224 sel_id = menu->cur_item_id;
Simon Glass9f513932023-01-06 08:52:38 -0600225 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glassd353b752023-06-01 10:22:55 -0600226 bool selected;
Simon Glass9f513932023-01-06 08:52:38 -0600227 int height;
228
Simon Glassd353b752023-06-01 10:22:55 -0600229 ret = scene_obj_get_hw(scn, item->label_id, NULL);
Simon Glass9f513932023-01-06 08:52:38 -0600230 if (ret < 0)
231 return log_msg_ret("get", ret);
232 height = ret;
233
234 if (item->flags & SCENEMIF_GAP_BEFORE)
235 y += height;
236
237 /* select an item if not done already */
Simon Glassc55eeba2023-06-01 10:22:54 -0600238 if (!sel_id)
239 sel_id = item->id;
Simon Glass9f513932023-01-06 08:52:38 -0600240
Simon Glassd353b752023-06-01 10:22:55 -0600241 selected = sel_id == item->id;
242
Simon Glass9f513932023-01-06 08:52:38 -0600243 /*
244 * Put the label on the left, then leave a space for the
245 * pointer, then the key and the description
246 */
Simon Glass86f1ac52023-06-01 10:23:00 -0600247 ret = scene_obj_set_pos(scn, item->label_id,
248 x + theme->menu_inset, y);
Simon Glass9f513932023-01-06 08:52:38 -0600249 if (ret < 0)
Simon Glassd353b752023-06-01 10:22:55 -0600250 return log_msg_ret("nam", ret);
251 scene_obj_set_hide(scn, item->label_id,
252 stack && !open && !selected);
Simon Glass9f513932023-01-06 08:52:38 -0600253
Simon Glassd353b752023-06-01 10:22:55 -0600254 if (item->key_id) {
255 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
256 if (ret < 0)
257 return log_msg_ret("key", ret);
258 }
Simon Glass9f513932023-01-06 08:52:38 -0600259
Simon Glassd353b752023-06-01 10:22:55 -0600260 if (item->desc_id) {
261 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
262 if (ret < 0)
263 return log_msg_ret("des", ret);
264 }
Simon Glass9f513932023-01-06 08:52:38 -0600265
266 if (item->preview_id) {
267 bool hide;
268
269 /*
270 * put all previews on top of each other, on the right
271 * size of the display
272 */
273 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
274 if (ret < 0)
275 return log_msg_ret("prev", ret);
276
277 hide = menu->cur_item_id != item->id;
278 ret = scene_obj_set_hide(scn, item->preview_id, hide);
279 if (ret < 0)
280 return log_msg_ret("hid", ret);
281 }
282
Simon Glassd353b752023-06-01 10:22:55 -0600283 if (!stack || open)
Simon Glass86f1ac52023-06-01 10:23:00 -0600284 y += height + theme->menuitem_gap_y;
Simon Glass9f513932023-01-06 08:52:38 -0600285 }
286
Simon Glassc55eeba2023-06-01 10:22:54 -0600287 if (sel_id)
288 menu_point_to_item(menu, sel_id);
Simon Glass9f513932023-01-06 08:52:38 -0600289
290 return 0;
291}
292
293int scene_menu(struct scene *scn, const char *name, uint id,
294 struct scene_obj_menu **menup)
295{
296 struct scene_obj_menu *menu;
297 int ret;
298
299 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
300 sizeof(struct scene_obj_menu),
301 (struct scene_obj **)&menu);
302 if (ret < 0)
303 return log_msg_ret("obj", -ENOMEM);
304
305 if (menup)
306 *menup = menu;
307 INIT_LIST_HEAD(&menu->item_head);
308
Simon Glass9f513932023-01-06 08:52:38 -0600309 return menu->obj.id;
310}
311
312static struct scene_menitem *scene_menu_find_key(struct scene *scn,
313 struct scene_obj_menu *menu,
314 int key)
315{
316 struct scene_menitem *item;
317
318 list_for_each_entry(item, &menu->item_head, sibling) {
319 if (item->key_id) {
320 struct scene_obj_txt *txt;
321 const char *str;
322
323 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
324 if (txt) {
325 str = expo_get_str(scn->expo, txt->str_id);
326 if (str && *str == key)
327 return item;
328 }
329 }
330 }
331
332 return NULL;
333}
334
335int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
336 struct expo_action *event)
337{
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600338 const bool open = menu->obj.flags & SCENEOF_OPEN;
Simon Glass9f513932023-01-06 08:52:38 -0600339 struct scene_menitem *item, *cur, *key_item;
340
341 cur = NULL;
342 key_item = NULL;
343
344 if (!list_empty(&menu->item_head)) {
345 list_for_each_entry(item, &menu->item_head, sibling) {
346 /* select an item if not done already */
347 if (menu->cur_item_id == item->id) {
348 cur = item;
349 break;
350 }
351 }
352 }
353
354 if (!cur)
355 return -ENOTTY;
356
357 switch (key) {
358 case BKEY_UP:
359 if (item != list_first_entry(&menu->item_head,
360 struct scene_menitem, sibling)) {
361 item = list_entry(item->sibling.prev,
362 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600363 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600364 event->select.id = item->id;
365 log_debug("up to item %d\n", event->select.id);
366 }
367 break;
368 case BKEY_DOWN:
369 if (!list_is_last(&item->sibling, &menu->item_head)) {
370 item = list_entry(item->sibling.next,
371 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600372 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600373 event->select.id = item->id;
374 log_debug("down to item %d\n", event->select.id);
375 }
376 break;
377 case BKEY_SELECT:
378 event->type = EXPOACT_SELECT;
379 event->select.id = item->id;
380 log_debug("select item %d\n", event->select.id);
381 break;
382 case BKEY_QUIT:
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600383 if (scn->expo->popup && open) {
384 event->type = EXPOACT_CLOSE;
385 event->select.id = menu->obj.id;
386 } else {
387 event->type = EXPOACT_QUIT;
388 log_debug("menu quit\n");
389 }
Simon Glass9f513932023-01-06 08:52:38 -0600390 break;
391 case '0'...'9':
392 key_item = scene_menu_find_key(scn, menu, key);
393 if (key_item) {
394 event->type = EXPOACT_SELECT;
395 event->select.id = key_item->id;
396 }
397 break;
398 }
399
400 menu_point_to_item(menu, item->id);
401
402 return 0;
403}
404
405int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
406 uint key_id, uint label_id, uint desc_id, uint preview_id,
407 uint flags, struct scene_menitem **itemp)
408{
409 struct scene_obj_menu *menu;
410 struct scene_menitem *item;
Simon Glass9f513932023-01-06 08:52:38 -0600411
412 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
413 if (!menu)
414 return log_msg_ret("find", -ENOENT);
415
416 /* Check that the text ID is valid */
Simon Glassd353b752023-06-01 10:22:55 -0600417 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
Simon Glass9f513932023-01-06 08:52:38 -0600418 return log_msg_ret("txt", -EINVAL);
419
Dan Carpenter463be542023-07-31 17:08:29 +0300420 item = calloc(1, sizeof(struct scene_menitem));
Simon Glass9f513932023-01-06 08:52:38 -0600421 if (!item)
422 return log_msg_ret("item", -ENOMEM);
423 item->name = strdup(name);
424 if (!item->name) {
425 free(item);
426 return log_msg_ret("name", -ENOMEM);
427 }
428
429 item->id = resolve_id(scn->expo, id);
430 item->key_id = key_id;
431 item->label_id = label_id;
432 item->desc_id = desc_id;
433 item->preview_id = preview_id;
434 item->flags = flags;
Simon Glass100389f2024-10-14 16:31:58 -0600435 item->value = INT_MAX;
Simon Glass9f513932023-01-06 08:52:38 -0600436 list_add_tail(&item->sibling, &menu->item_head);
437
Simon Glass9f513932023-01-06 08:52:38 -0600438 if (itemp)
439 *itemp = item;
440
441 return item->id;
442}
443
444int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
445{
446 struct scene_obj_menu *menu;
447 struct scene_obj_txt *txt;
448
449 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
450 if (!menu)
451 return log_msg_ret("menu", -ENOENT);
452
453 /* Check that the ID is valid */
454 if (title_id) {
455 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
456 if (!txt)
457 return log_msg_ret("txt", -EINVAL);
458 }
459
460 menu->title_id = title_id;
461
462 return 0;
463}
464
465int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
466{
467 struct scene_obj_menu *menu;
468 struct scene_obj *obj;
469
470 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
471 if (!menu)
472 return log_msg_ret("menu", -ENOENT);
473
474 /* Check that the ID is valid */
475 if (pointer_id) {
476 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
477 if (!obj)
478 return log_msg_ret("obj", -EINVAL);
479 }
480
481 menu->pointer_id = pointer_id;
482
483 return 0;
484}
485
486int scene_menu_display(struct scene_obj_menu *menu)
487{
488 struct scene *scn = menu->obj.scene;
489 struct scene_obj_txt *pointer;
490 struct expo *exp = scn->expo;
491 struct scene_menitem *item;
492 const char *pstr;
493
494 printf("U-Boot : Boot Menu\n\n");
495 if (menu->title_id) {
496 struct scene_obj_txt *txt;
497 const char *str;
498
499 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
500 if (!txt)
501 return log_msg_ret("txt", -EINVAL);
502
503 str = expo_get_str(exp, txt->str_id);
504 printf("%s\n\n", str);
505 }
506
507 if (list_empty(&menu->item_head))
508 return 0;
509
510 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
511 pstr = expo_get_str(scn->expo, pointer->str_id);
512
513 list_for_each_entry(item, &menu->item_head, sibling) {
514 struct scene_obj_txt *key = NULL, *label = NULL;
515 struct scene_obj_txt *desc = NULL;
516 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
517
518 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
519 if (key)
520 kstr = expo_get_str(exp, key->str_id);
521
522 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
523 if (label)
524 lstr = expo_get_str(exp, label->str_id);
525
526 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
527 if (desc)
528 dstr = expo_get_str(exp, desc->str_id);
529
530 printf("%3s %3s %-10s %s\n",
531 pointer && menu->cur_item_id == item->id ? pstr : "",
532 kstr, lstr, dstr);
533 }
534
535 return -ENOTSUPP;
536}
Simon Glass01922ec2023-06-01 10:22:57 -0600537
Simon Glass12f57732023-06-01 10:22:58 -0600538int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
539{
540 struct scene_menitem *item;
541
542 scene_render_deps(scn, menu->title_id);
543 scene_render_deps(scn, menu->cur_item_id);
544 scene_render_deps(scn, menu->pointer_id);
545
546 list_for_each_entry(item, &menu->item_head, sibling) {
547 scene_render_deps(scn, item->key_id);
548 scene_render_deps(scn, item->label_id);
549 scene_render_deps(scn, item->desc_id);
550 }
551
552 return 0;
553}