blob: 63994165efba41192d1138dd17b9fcb11cedb272 [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 Glass5fd4f782023-08-14 16:40:32 -060036struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
37 int id)
Simon Glassc55eeba2023-06-01 10:22:54 -060038{
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
Simon Glass4462fa32023-08-14 16:40:38 -060049struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
50 uint seq)
51{
52 struct scene_menitem *item;
53 uint i;
54
55 i = 0;
56 list_for_each_entry(item, &menu->item_head, sibling) {
57 if (i == seq)
58 return item;
59 i++;
60 }
61
62 return NULL;
63}
64
Simon Glassc55eeba2023-06-01 10:22:54 -060065/**
66 * update_pointers() - Update the pointer object and handle highlights
67 *
68 * @menu: Menu to update
69 * @id: ID of menu item to select/deselect
70 * @point: true if @id is being selected, false if it is being deselected
71 */
72static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
73{
74 struct scene *scn = menu->obj.scene;
Simon Glassd353b752023-06-01 10:22:55 -060075 const bool stack = scn->expo->popup;
Simon Glassc55eeba2023-06-01 10:22:54 -060076 const struct scene_menitem *item;
77 int ret;
78
79 item = scene_menuitem_find(menu, id);
80 if (!item)
81 return log_msg_ret("itm", -ENOENT);
82
83 /* adjust the pointer object to point to the selected item */
84 if (menu->pointer_id && item && point) {
85 struct scene_obj *label;
86
87 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
88
89 ret = scene_obj_set_pos(scn, menu->pointer_id,
90 menu->obj.dim.x + 200, label->dim.y);
91 if (ret < 0)
92 return log_msg_ret("ptr", ret);
93 }
94
Simon Glassd353b752023-06-01 10:22:55 -060095 if (stack) {
96 point &= scn->highlight_id == menu->obj.id;
97 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
98 point ? SCENEOF_POINT : 0);
99 }
100
Simon Glassc55eeba2023-06-01 10:22:54 -0600101 return 0;
102}
103
Simon Glass9f513932023-01-06 08:52:38 -0600104/**
105 * menu_point_to_item() - Point to a particular menu item
106 *
107 * Sets the currently pointed-to / highlighted menu item
108 */
109static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
110{
Simon Glassc55eeba2023-06-01 10:22:54 -0600111 if (menu->cur_item_id)
112 update_pointers(menu, menu->cur_item_id, false);
Simon Glass9f513932023-01-06 08:52:38 -0600113 menu->cur_item_id = item_id;
Simon Glassc55eeba2023-06-01 10:22:54 -0600114 update_pointers(menu, item_id, true);
Simon Glass9f513932023-01-06 08:52:38 -0600115}
116
Simon Glassf0994692023-10-01 19:13:29 -0600117void scene_menu_calc_bbox(struct scene_obj_menu *menu,
118 struct vidconsole_bbox *bbox,
119 struct vidconsole_bbox *label_bbox)
Simon Glass7a960052023-06-01 10:22:52 -0600120{
Simon Glass86f1ac52023-06-01 10:23:00 -0600121 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
Simon Glass7a960052023-06-01 10:22:52 -0600122 const struct scene_menitem *item;
123
124 bbox->valid = false;
Simon Glass86f1ac52023-06-01 10:23:00 -0600125 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600126
127 label_bbox->valid = false;
128
129 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glass86f1ac52023-06-01 10:23:00 -0600130 scene_bbox_union(menu->obj.scene, item->label_id,
131 theme->menu_inset, bbox);
132 scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox);
133 scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox);
134 scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600135
136 /* Get the bounding box of all labels */
Simon Glass86f1ac52023-06-01 10:23:00 -0600137 scene_bbox_union(menu->obj.scene, item->label_id,
138 theme->menu_inset, label_bbox);
Simon Glass7a960052023-06-01 10:22:52 -0600139 }
Simon Glass86f1ac52023-06-01 10:23:00 -0600140
141 /*
142 * subtract the final menuitem's gap to keep the insert the same top
143 * and bottom
144 */
145 label_bbox->y1 -= theme->menuitem_gap_y;
Simon Glass7a960052023-06-01 10:22:52 -0600146}
147
148int scene_menu_calc_dims(struct scene_obj_menu *menu)
149{
150 struct vidconsole_bbox bbox, label_bbox;
151 const struct scene_menitem *item;
152
153 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
154
155 /* Make all labels the same size */
156 if (label_bbox.valid) {
157 list_for_each_entry(item, &menu->item_head, sibling) {
158 scene_obj_set_size(menu->obj.scene, item->label_id,
159 label_bbox.x1 - label_bbox.x0,
160 label_bbox.y1 - label_bbox.y0);
161 }
162 }
163
164 if (bbox.valid) {
165 menu->obj.dim.w = bbox.x1 - bbox.x0;
166 menu->obj.dim.h = bbox.y1 - bbox.y0;
167 }
168
169 return 0;
170}
171
Simon Glass9f513932023-01-06 08:52:38 -0600172int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
173{
Simon Glassd353b752023-06-01 10:22:55 -0600174 const bool open = menu->obj.flags & SCENEOF_OPEN;
175 struct expo *exp = scn->expo;
176 const bool stack = exp->popup;
Simon Glass86f1ac52023-06-01 10:23:00 -0600177 const struct expo_theme *theme = &exp->theme;
Simon Glass9f513932023-01-06 08:52:38 -0600178 struct scene_menitem *item;
Simon Glassc55eeba2023-06-01 10:22:54 -0600179 uint sel_id;
Simon Glassd353b752023-06-01 10:22:55 -0600180 int x, y;
Simon Glass9f513932023-01-06 08:52:38 -0600181 int ret;
182
Simon Glassd353b752023-06-01 10:22:55 -0600183 x = menu->obj.dim.x;
Simon Glass7b043952023-06-01 10:22:49 -0600184 y = menu->obj.dim.y;
Simon Glass9f513932023-01-06 08:52:38 -0600185 if (menu->title_id) {
Simon Glass7b043952023-06-01 10:22:49 -0600186 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
Simon Glass9f513932023-01-06 08:52:38 -0600187 if (ret < 0)
188 return log_msg_ret("tit", ret);
189
190 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
191 if (ret < 0)
192 return log_msg_ret("hei", ret);
193
Simon Glassd353b752023-06-01 10:22:55 -0600194 if (stack)
195 x += 200;
196 else
197 y += ret * 2;
Simon Glass9f513932023-01-06 08:52:38 -0600198 }
199
200 /*
201 * Currently everything is hard-coded to particular columns so this
202 * won't work on small displays and looks strange if the font size is
203 * small. This can be updated once text measuring is supported in
204 * vidconsole
205 */
Simon Glassc55eeba2023-06-01 10:22:54 -0600206 sel_id = menu->cur_item_id;
Simon Glass9f513932023-01-06 08:52:38 -0600207 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glassd353b752023-06-01 10:22:55 -0600208 bool selected;
Simon Glass9f513932023-01-06 08:52:38 -0600209 int height;
210
Simon Glassd353b752023-06-01 10:22:55 -0600211 ret = scene_obj_get_hw(scn, item->label_id, NULL);
Simon Glass9f513932023-01-06 08:52:38 -0600212 if (ret < 0)
213 return log_msg_ret("get", ret);
214 height = ret;
215
216 if (item->flags & SCENEMIF_GAP_BEFORE)
217 y += height;
218
219 /* select an item if not done already */
Simon Glassc55eeba2023-06-01 10:22:54 -0600220 if (!sel_id)
221 sel_id = item->id;
Simon Glass9f513932023-01-06 08:52:38 -0600222
Simon Glassd353b752023-06-01 10:22:55 -0600223 selected = sel_id == item->id;
224
Simon Glass9f513932023-01-06 08:52:38 -0600225 /*
226 * Put the label on the left, then leave a space for the
227 * pointer, then the key and the description
228 */
Simon Glass86f1ac52023-06-01 10:23:00 -0600229 ret = scene_obj_set_pos(scn, item->label_id,
230 x + theme->menu_inset, y);
Simon Glass9f513932023-01-06 08:52:38 -0600231 if (ret < 0)
Simon Glassd353b752023-06-01 10:22:55 -0600232 return log_msg_ret("nam", ret);
233 scene_obj_set_hide(scn, item->label_id,
234 stack && !open && !selected);
Simon Glass9f513932023-01-06 08:52:38 -0600235
Simon Glassd353b752023-06-01 10:22:55 -0600236 if (item->key_id) {
237 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
238 if (ret < 0)
239 return log_msg_ret("key", ret);
240 }
Simon Glass9f513932023-01-06 08:52:38 -0600241
Simon Glassd353b752023-06-01 10:22:55 -0600242 if (item->desc_id) {
243 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
244 if (ret < 0)
245 return log_msg_ret("des", ret);
246 }
Simon Glass9f513932023-01-06 08:52:38 -0600247
248 if (item->preview_id) {
249 bool hide;
250
251 /*
252 * put all previews on top of each other, on the right
253 * size of the display
254 */
255 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
256 if (ret < 0)
257 return log_msg_ret("prev", ret);
258
259 hide = menu->cur_item_id != item->id;
260 ret = scene_obj_set_hide(scn, item->preview_id, hide);
261 if (ret < 0)
262 return log_msg_ret("hid", ret);
263 }
264
Simon Glassd353b752023-06-01 10:22:55 -0600265 if (!stack || open)
Simon Glass86f1ac52023-06-01 10:23:00 -0600266 y += height + theme->menuitem_gap_y;
Simon Glass9f513932023-01-06 08:52:38 -0600267 }
268
Simon Glassc55eeba2023-06-01 10:22:54 -0600269 if (sel_id)
270 menu_point_to_item(menu, sel_id);
Simon Glass9f513932023-01-06 08:52:38 -0600271
272 return 0;
273}
274
275int scene_menu(struct scene *scn, const char *name, uint id,
276 struct scene_obj_menu **menup)
277{
278 struct scene_obj_menu *menu;
279 int ret;
280
281 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
282 sizeof(struct scene_obj_menu),
283 (struct scene_obj **)&menu);
284 if (ret < 0)
285 return log_msg_ret("obj", -ENOMEM);
286
287 if (menup)
288 *menup = menu;
289 INIT_LIST_HEAD(&menu->item_head);
290
Simon Glass9f513932023-01-06 08:52:38 -0600291 return menu->obj.id;
292}
293
294static struct scene_menitem *scene_menu_find_key(struct scene *scn,
295 struct scene_obj_menu *menu,
296 int key)
297{
298 struct scene_menitem *item;
299
300 list_for_each_entry(item, &menu->item_head, sibling) {
301 if (item->key_id) {
302 struct scene_obj_txt *txt;
303 const char *str;
304
305 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
306 if (txt) {
307 str = expo_get_str(scn->expo, txt->str_id);
308 if (str && *str == key)
309 return item;
310 }
311 }
312 }
313
314 return NULL;
315}
316
317int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
318 struct expo_action *event)
319{
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600320 const bool open = menu->obj.flags & SCENEOF_OPEN;
Simon Glass9f513932023-01-06 08:52:38 -0600321 struct scene_menitem *item, *cur, *key_item;
322
323 cur = NULL;
324 key_item = NULL;
325
326 if (!list_empty(&menu->item_head)) {
327 list_for_each_entry(item, &menu->item_head, sibling) {
328 /* select an item if not done already */
329 if (menu->cur_item_id == item->id) {
330 cur = item;
331 break;
332 }
333 }
334 }
335
336 if (!cur)
337 return -ENOTTY;
338
339 switch (key) {
340 case BKEY_UP:
341 if (item != list_first_entry(&menu->item_head,
342 struct scene_menitem, sibling)) {
343 item = list_entry(item->sibling.prev,
344 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600345 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600346 event->select.id = item->id;
347 log_debug("up to item %d\n", event->select.id);
348 }
349 break;
350 case BKEY_DOWN:
351 if (!list_is_last(&item->sibling, &menu->item_head)) {
352 item = list_entry(item->sibling.next,
353 struct scene_menitem, sibling);
Simon Glass719a3c62023-06-01 10:22:56 -0600354 event->type = EXPOACT_POINT_ITEM;
Simon Glass9f513932023-01-06 08:52:38 -0600355 event->select.id = item->id;
356 log_debug("down to item %d\n", event->select.id);
357 }
358 break;
359 case BKEY_SELECT:
360 event->type = EXPOACT_SELECT;
361 event->select.id = item->id;
362 log_debug("select item %d\n", event->select.id);
363 break;
364 case BKEY_QUIT:
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600365 if (scn->expo->popup && open) {
366 event->type = EXPOACT_CLOSE;
367 event->select.id = menu->obj.id;
368 } else {
369 event->type = EXPOACT_QUIT;
370 log_debug("menu quit\n");
371 }
Simon Glass9f513932023-01-06 08:52:38 -0600372 break;
373 case '0'...'9':
374 key_item = scene_menu_find_key(scn, menu, key);
375 if (key_item) {
376 event->type = EXPOACT_SELECT;
377 event->select.id = key_item->id;
378 }
379 break;
380 }
381
382 menu_point_to_item(menu, item->id);
383
384 return 0;
385}
386
387int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
388 uint key_id, uint label_id, uint desc_id, uint preview_id,
389 uint flags, struct scene_menitem **itemp)
390{
391 struct scene_obj_menu *menu;
392 struct scene_menitem *item;
Simon Glass9f513932023-01-06 08:52:38 -0600393
394 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
395 if (!menu)
396 return log_msg_ret("find", -ENOENT);
397
398 /* Check that the text ID is valid */
Simon Glassd353b752023-06-01 10:22:55 -0600399 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
Simon Glass9f513932023-01-06 08:52:38 -0600400 return log_msg_ret("txt", -EINVAL);
401
Dan Carpenter463be542023-07-31 17:08:29 +0300402 item = calloc(1, sizeof(struct scene_menitem));
Simon Glass9f513932023-01-06 08:52:38 -0600403 if (!item)
404 return log_msg_ret("item", -ENOMEM);
405 item->name = strdup(name);
406 if (!item->name) {
407 free(item);
408 return log_msg_ret("name", -ENOMEM);
409 }
410
411 item->id = resolve_id(scn->expo, id);
412 item->key_id = key_id;
413 item->label_id = label_id;
414 item->desc_id = desc_id;
415 item->preview_id = preview_id;
416 item->flags = flags;
417 list_add_tail(&item->sibling, &menu->item_head);
418
Simon Glass9f513932023-01-06 08:52:38 -0600419 if (itemp)
420 *itemp = item;
421
422 return item->id;
423}
424
425int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
426{
427 struct scene_obj_menu *menu;
428 struct scene_obj_txt *txt;
429
430 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
431 if (!menu)
432 return log_msg_ret("menu", -ENOENT);
433
434 /* Check that the ID is valid */
435 if (title_id) {
436 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
437 if (!txt)
438 return log_msg_ret("txt", -EINVAL);
439 }
440
441 menu->title_id = title_id;
442
443 return 0;
444}
445
446int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
447{
448 struct scene_obj_menu *menu;
449 struct scene_obj *obj;
450
451 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
452 if (!menu)
453 return log_msg_ret("menu", -ENOENT);
454
455 /* Check that the ID is valid */
456 if (pointer_id) {
457 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
458 if (!obj)
459 return log_msg_ret("obj", -EINVAL);
460 }
461
462 menu->pointer_id = pointer_id;
463
464 return 0;
465}
466
467int scene_menu_display(struct scene_obj_menu *menu)
468{
469 struct scene *scn = menu->obj.scene;
470 struct scene_obj_txt *pointer;
471 struct expo *exp = scn->expo;
472 struct scene_menitem *item;
473 const char *pstr;
474
475 printf("U-Boot : Boot Menu\n\n");
476 if (menu->title_id) {
477 struct scene_obj_txt *txt;
478 const char *str;
479
480 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
481 if (!txt)
482 return log_msg_ret("txt", -EINVAL);
483
484 str = expo_get_str(exp, txt->str_id);
485 printf("%s\n\n", str);
486 }
487
488 if (list_empty(&menu->item_head))
489 return 0;
490
491 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
492 pstr = expo_get_str(scn->expo, pointer->str_id);
493
494 list_for_each_entry(item, &menu->item_head, sibling) {
495 struct scene_obj_txt *key = NULL, *label = NULL;
496 struct scene_obj_txt *desc = NULL;
497 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
498
499 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
500 if (key)
501 kstr = expo_get_str(exp, key->str_id);
502
503 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
504 if (label)
505 lstr = expo_get_str(exp, label->str_id);
506
507 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
508 if (desc)
509 dstr = expo_get_str(exp, desc->str_id);
510
511 printf("%3s %3s %-10s %s\n",
512 pointer && menu->cur_item_id == item->id ? pstr : "",
513 kstr, lstr, dstr);
514 }
515
516 return -ENOTSUPP;
517}
Simon Glass01922ec2023-06-01 10:22:57 -0600518
Simon Glass12f57732023-06-01 10:22:58 -0600519int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
520{
521 struct scene_menitem *item;
522
523 scene_render_deps(scn, menu->title_id);
524 scene_render_deps(scn, menu->cur_item_id);
525 scene_render_deps(scn, menu->pointer_id);
526
527 list_for_each_entry(item, &menu->item_head, sibling) {
528 scene_render_deps(scn, item->key_id);
529 scene_render_deps(scn, item->label_id);
530 scene_render_deps(scn, item->desc_id);
531 }
532
533 return 0;
534}