blob: 80bd7457cb1a1c3888b078e1e47cd9126e9e97ad [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 Glassc55eeba2023-06-01 10:22:54 -060064/**
65 * update_pointers() - Update the pointer object and handle highlights
66 *
67 * @menu: Menu to update
68 * @id: ID of menu item to select/deselect
69 * @point: true if @id is being selected, false if it is being deselected
70 */
71static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
72{
73 struct scene *scn = menu->obj.scene;
Simon Glassd353b752023-06-01 10:22:55 -060074 const bool stack = scn->expo->popup;
Simon Glassc55eeba2023-06-01 10:22:54 -060075 const struct scene_menitem *item;
76 int ret;
77
78 item = scene_menuitem_find(menu, id);
79 if (!item)
80 return log_msg_ret("itm", -ENOENT);
81
82 /* adjust the pointer object to point to the selected item */
83 if (menu->pointer_id && item && point) {
84 struct scene_obj *label;
85
86 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
87
88 ret = scene_obj_set_pos(scn, menu->pointer_id,
89 menu->obj.dim.x + 200, label->dim.y);
90 if (ret < 0)
91 return log_msg_ret("ptr", ret);
92 }
93
Simon Glassd353b752023-06-01 10:22:55 -060094 if (stack) {
95 point &= scn->highlight_id == menu->obj.id;
96 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
97 point ? SCENEOF_POINT : 0);
98 }
99
Simon Glassc55eeba2023-06-01 10:22:54 -0600100 return 0;
101}
102
Simon Glass9f513932023-01-06 08:52:38 -0600103/**
104 * menu_point_to_item() - Point to a particular menu item
105 *
106 * Sets the currently pointed-to / highlighted menu item
107 */
108static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
109{
Simon Glassc55eeba2023-06-01 10:22:54 -0600110 if (menu->cur_item_id)
111 update_pointers(menu, menu->cur_item_id, false);
Simon Glass9f513932023-01-06 08:52:38 -0600112 menu->cur_item_id = item_id;
Simon Glassc55eeba2023-06-01 10:22:54 -0600113 update_pointers(menu, item_id, true);
Simon Glass9f513932023-01-06 08:52:38 -0600114}
115
Simon Glassf0994692023-10-01 19:13:29 -0600116void scene_menu_calc_bbox(struct scene_obj_menu *menu,
117 struct vidconsole_bbox *bbox,
118 struct vidconsole_bbox *label_bbox)
Simon Glass7a960052023-06-01 10:22:52 -0600119{
Simon Glass86f1ac52023-06-01 10:23:00 -0600120 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
Simon Glass7a960052023-06-01 10:22:52 -0600121 const struct scene_menitem *item;
122
123 bbox->valid = false;
Simon Glass86f1ac52023-06-01 10:23:00 -0600124 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600125
126 label_bbox->valid = false;
127
128 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glass86f1ac52023-06-01 10:23:00 -0600129 scene_bbox_union(menu->obj.scene, item->label_id,
130 theme->menu_inset, bbox);
131 scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox);
132 scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox);
133 scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600134
135 /* Get the bounding box of all labels */
Simon Glass86f1ac52023-06-01 10:23:00 -0600136 scene_bbox_union(menu->obj.scene, item->label_id,
137 theme->menu_inset, label_bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600138 }
Simon Glass86f1ac52023-06-01 10:23:00 -0600139
140 /*
141 * subtract the final menuitem's gap to keep the insert the same top
142 * and bottom
143 */
144 label_bbox->y1 -= theme->menuitem_gap_y;
Simon Glass7a960052023-06-01 10:22:52 -0600145}
146
147int scene_menu_calc_dims(struct scene_obj_menu *menu)
148{
149 struct vidconsole_bbox bbox, label_bbox;
150 const struct scene_menitem *item;
151
152 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
153
154 /* Make all labels the same size */
155 if (label_bbox.valid) {
156 list_for_each_entry(item, &menu->item_head, sibling) {
157 scene_obj_set_size(menu->obj.scene, item->label_id,
158 label_bbox.x1 - label_bbox.x0,
159 label_bbox.y1 - label_bbox.y0);
160 }
161 }
162
163 if (bbox.valid) {
164 menu->obj.dim.w = bbox.x1 - bbox.x0;
165 menu->obj.dim.h = bbox.y1 - bbox.y0;
166 }
167
168 return 0;
169}
170
Simon Glass9f513932023-01-06 08:52:38 -0600171int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
172{
Simon Glassd353b752023-06-01 10:22:55 -0600173 const bool open = menu->obj.flags & SCENEOF_OPEN;
174 struct expo *exp = scn->expo;
175 const bool stack = exp->popup;
Simon Glass86f1ac52023-06-01 10:23:00 -0600176 const struct expo_theme *theme = &exp->theme;
Simon Glass9f513932023-01-06 08:52:38 -0600177 struct scene_menitem *item;
Simon Glassc55eeba2023-06-01 10:22:54 -0600178 uint sel_id;
Simon Glassd353b752023-06-01 10:22:55 -0600179 int x, y;
Simon Glass9f513932023-01-06 08:52:38 -0600180 int ret;
181
Simon Glassd353b752023-06-01 10:22:55 -0600182 x = menu->obj.dim.x;
Simon Glass7b043952023-06-01 10:22:49 -0600183 y = menu->obj.dim.y;
Simon Glass9f513932023-01-06 08:52:38 -0600184 if (menu->title_id) {
Simon Glass7b043952023-06-01 10:22:49 -0600185 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
Simon Glass9f513932023-01-06 08:52:38 -0600186 if (ret < 0)
187 return log_msg_ret("tit", ret);
188
189 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
190 if (ret < 0)
191 return log_msg_ret("hei", ret);
192
Simon Glassd353b752023-06-01 10:22:55 -0600193 if (stack)
194 x += 200;
195 else
196 y += ret * 2;
Simon Glass9f513932023-01-06 08:52:38 -0600197 }
198
199 /*
200 * Currently everything is hard-coded to particular columns so this
201 * won't work on small displays and looks strange if the font size is
202 * small. This can be updated once text measuring is supported in
203 * vidconsole
204 */
Simon Glassc55eeba2023-06-01 10:22:54 -0600205 sel_id = menu->cur_item_id;
Simon Glass9f513932023-01-06 08:52:38 -0600206 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glassd353b752023-06-01 10:22:55 -0600207 bool selected;
Simon Glass9f513932023-01-06 08:52:38 -0600208 int height;
209
Simon Glassd353b752023-06-01 10:22:55 -0600210 ret = scene_obj_get_hw(scn, item->label_id, NULL);
Simon Glass9f513932023-01-06 08:52:38 -0600211 if (ret < 0)
212 return log_msg_ret("get", ret);
213 height = ret;
214
215 if (item->flags & SCENEMIF_GAP_BEFORE)
216 y += height;
217
218 /* select an item if not done already */
Simon Glassc55eeba2023-06-01 10:22:54 -0600219 if (!sel_id)
220 sel_id = item->id;
Simon Glass9f513932023-01-06 08:52:38 -0600221
Simon Glassd353b752023-06-01 10:22:55 -0600222 selected = sel_id == item->id;
223
Simon Glass9f513932023-01-06 08:52:38 -0600224 /*
225 * Put the label on the left, then leave a space for the
226 * pointer, then the key and the description
227 */
Simon Glass86f1ac52023-06-01 10:23:00 -0600228 ret = scene_obj_set_pos(scn, item->label_id,
229 x + theme->menu_inset, y);
Simon Glass9f513932023-01-06 08:52:38 -0600230 if (ret < 0)
Simon Glassd353b752023-06-01 10:22:55 -0600231 return log_msg_ret("nam", ret);
232 scene_obj_set_hide(scn, item->label_id,
233 stack && !open && !selected);
Simon Glass9f513932023-01-06 08:52:38 -0600234
Simon Glassd353b752023-06-01 10:22:55 -0600235 if (item->key_id) {
236 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
237 if (ret < 0)
238 return log_msg_ret("key", ret);
239 }
Simon Glass9f513932023-01-06 08:52:38 -0600240
Simon Glassd353b752023-06-01 10:22:55 -0600241 if (item->desc_id) {
242 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
243 if (ret < 0)
244 return log_msg_ret("des", ret);
245 }
Simon Glass9f513932023-01-06 08:52:38 -0600246
247 if (item->preview_id) {
248 bool hide;
249
250 /*
251 * put all previews on top of each other, on the right
252 * size of the display
253 */
254 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
255 if (ret < 0)
256 return log_msg_ret("prev", ret);
257
258 hide = menu->cur_item_id != item->id;
259 ret = scene_obj_set_hide(scn, item->preview_id, hide);
260 if (ret < 0)
261 return log_msg_ret("hid", ret);
262 }
263
Simon Glassd353b752023-06-01 10:22:55 -0600264 if (!stack || open)
Simon Glass86f1ac52023-06-01 10:23:00 -0600265 y += height + theme->menuitem_gap_y;
Simon Glass9f513932023-01-06 08:52:38 -0600266 }
267
Simon Glassc55eeba2023-06-01 10:22:54 -0600268 if (sel_id)
269 menu_point_to_item(menu, sel_id);
Simon Glass9f513932023-01-06 08:52:38 -0600270
271 return 0;
272}
273
274int scene_menu(struct scene *scn, const char *name, uint id,
275 struct scene_obj_menu **menup)
276{
277 struct scene_obj_menu *menu;
278 int ret;
279
280 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
281 sizeof(struct scene_obj_menu),
282 (struct scene_obj **)&menu);
283 if (ret < 0)
284 return log_msg_ret("obj", -ENOMEM);
285
286 if (menup)
287 *menup = menu;
288 INIT_LIST_HEAD(&menu->item_head);
289
Simon Glass9f513932023-01-06 08:52:38 -0600290 return menu->obj.id;
291}
292
293static struct scene_menitem *scene_menu_find_key(struct scene *scn,
294 struct scene_obj_menu *menu,
295 int key)
296{
297 struct scene_menitem *item;
298
299 list_for_each_entry(item, &menu->item_head, sibling) {
300 if (item->key_id) {
301 struct scene_obj_txt *txt;
302 const char *str;
303
304 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
305 if (txt) {
306 str = expo_get_str(scn->expo, txt->str_id);
307 if (str && *str == key)
308 return item;
309 }
310 }
311 }
312
313 return NULL;
314}
315
316int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
317 struct expo_action *event)
318{
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600319 const bool open = menu->obj.flags & SCENEOF_OPEN;
Simon Glass9f513932023-01-06 08:52:38 -0600320 struct scene_menitem *item, *cur, *key_item;
321
322 cur = NULL;
323 key_item = NULL;
324
325 if (!list_empty(&menu->item_head)) {
326 list_for_each_entry(item, &menu->item_head, sibling) {
327 /* select an item if not done already */
328 if (menu->cur_item_id == item->id) {
329 cur = item;
330 break;
331 }
332 }
333 }
334
335 if (!cur)
336 return -ENOTTY;
337
338 switch (key) {
339 case BKEY_UP:
340 if (item != list_first_entry(&menu->item_head,
341 struct scene_menitem, sibling)) {
342 item = list_entry(item->sibling.prev,
343 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600344 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600345 event->select.id = item->id;
346 log_debug("up to item %d\n", event->select.id);
347 }
348 break;
349 case BKEY_DOWN:
350 if (!list_is_last(&item->sibling, &menu->item_head)) {
351 item = list_entry(item->sibling.next,
352 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600353 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600354 event->select.id = item->id;
355 log_debug("down to item %d\n", event->select.id);
356 }
357 break;
358 case BKEY_SELECT:
359 event->type = EXPOACT_SELECT;
360 event->select.id = item->id;
361 log_debug("select item %d\n", event->select.id);
362 break;
363 case BKEY_QUIT:
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600364 if (scn->expo->popup && open) {
365 event->type = EXPOACT_CLOSE;
366 event->select.id = menu->obj.id;
367 } else {
368 event->type = EXPOACT_QUIT;
369 log_debug("menu quit\n");
370 }
Simon Glass9f513932023-01-06 08:52:38 -0600371 break;
372 case '0'...'9':
373 key_item = scene_menu_find_key(scn, menu, key);
374 if (key_item) {
375 event->type = EXPOACT_SELECT;
376 event->select.id = key_item->id;
377 }
378 break;
379 }
380
381 menu_point_to_item(menu, item->id);
382
383 return 0;
384}
385
386int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
387 uint key_id, uint label_id, uint desc_id, uint preview_id,
388 uint flags, struct scene_menitem **itemp)
389{
390 struct scene_obj_menu *menu;
391 struct scene_menitem *item;
Simon Glass9f513932023-01-06 08:52:38 -0600392
393 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
394 if (!menu)
395 return log_msg_ret("find", -ENOENT);
396
397 /* Check that the text ID is valid */
Simon Glassd353b752023-06-01 10:22:55 -0600398 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
Simon Glass9f513932023-01-06 08:52:38 -0600399 return log_msg_ret("txt", -EINVAL);
400
Dan Carpenter463be542023-07-31 17:08:29 +0300401 item = calloc(1, sizeof(struct scene_menitem));
Simon Glass9f513932023-01-06 08:52:38 -0600402 if (!item)
403 return log_msg_ret("item", -ENOMEM);
404 item->name = strdup(name);
405 if (!item->name) {
406 free(item);
407 return log_msg_ret("name", -ENOMEM);
408 }
409
410 item->id = resolve_id(scn->expo, id);
411 item->key_id = key_id;
412 item->label_id = label_id;
413 item->desc_id = desc_id;
414 item->preview_id = preview_id;
415 item->flags = flags;
416 list_add_tail(&item->sibling, &menu->item_head);
417
Simon Glass9f513932023-01-06 08:52:38 -0600418 if (itemp)
419 *itemp = item;
420
421 return item->id;
422}
423
424int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
425{
426 struct scene_obj_menu *menu;
427 struct scene_obj_txt *txt;
428
429 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
430 if (!menu)
431 return log_msg_ret("menu", -ENOENT);
432
433 /* Check that the ID is valid */
434 if (title_id) {
435 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
436 if (!txt)
437 return log_msg_ret("txt", -EINVAL);
438 }
439
440 menu->title_id = title_id;
441
442 return 0;
443}
444
445int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
446{
447 struct scene_obj_menu *menu;
448 struct scene_obj *obj;
449
450 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
451 if (!menu)
452 return log_msg_ret("menu", -ENOENT);
453
454 /* Check that the ID is valid */
455 if (pointer_id) {
456 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
457 if (!obj)
458 return log_msg_ret("obj", -EINVAL);
459 }
460
461 menu->pointer_id = pointer_id;
462
463 return 0;
464}
465
466int scene_menu_display(struct scene_obj_menu *menu)
467{
468 struct scene *scn = menu->obj.scene;
469 struct scene_obj_txt *pointer;
470 struct expo *exp = scn->expo;
471 struct scene_menitem *item;
472 const char *pstr;
473
474 printf("U-Boot : Boot Menu\n\n");
475 if (menu->title_id) {
476 struct scene_obj_txt *txt;
477 const char *str;
478
479 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
480 if (!txt)
481 return log_msg_ret("txt", -EINVAL);
482
483 str = expo_get_str(exp, txt->str_id);
484 printf("%s\n\n", str);
485 }
486
487 if (list_empty(&menu->item_head))
488 return 0;
489
490 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
491 pstr = expo_get_str(scn->expo, pointer->str_id);
492
493 list_for_each_entry(item, &menu->item_head, sibling) {
494 struct scene_obj_txt *key = NULL, *label = NULL;
495 struct scene_obj_txt *desc = NULL;
496 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
497
498 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
499 if (key)
500 kstr = expo_get_str(exp, key->str_id);
501
502 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
503 if (label)
504 lstr = expo_get_str(exp, label->str_id);
505
506 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
507 if (desc)
508 dstr = expo_get_str(exp, desc->str_id);
509
510 printf("%3s %3s %-10s %s\n",
511 pointer && menu->cur_item_id == item->id ? pstr : "",
512 kstr, lstr, dstr);
513 }
514
515 return -ENOTSUPP;
516}
Simon Glass01922ec2023-06-01 10:22:57 -0600517
Simon Glass12f57732023-06-01 10:22:58 -0600518int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
519{
520 struct scene_menitem *item;
521
522 scene_render_deps(scn, menu->title_id);
523 scene_render_deps(scn, menu->cur_item_id);
524 scene_render_deps(scn, menu->pointer_id);
525
526 list_for_each_entry(item, &menu->item_head, sibling) {
527 scene_render_deps(scn, item->key_id);
528 scene_render_deps(scn, item->label_id);
529 scene_render_deps(scn, item->desc_id);
530 }
531
532 return 0;
533}