blob: 6aab27611d71264ff39851cc2ed76b3a5cb9842d [file] [log] [blame]
Simon Glass226777f2023-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 Glassc98cb512023-06-01 10:22:43 -06009#define LOG_CATEGORY LOGC_EXPO
Simon Glass226777f2023-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 Glass8872bc72023-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 Glassd3db0212023-06-01 10:22:55 -060059 const bool stack = scn->expo->popup;
Simon Glass8872bc72023-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 Glassd3db0212023-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 Glass8872bc72023-06-01 10:22:54 -060085 return 0;
86}
87
Simon Glass226777f2023-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 Glass8872bc72023-06-01 10:22:54 -060095 if (menu->cur_item_id)
96 update_pointers(menu, menu->cur_item_id, false);
Simon Glass226777f2023-01-06 08:52:38 -060097 menu->cur_item_id = item_id;
Simon Glass8872bc72023-06-01 10:22:54 -060098 update_pointers(menu, item_id, true);
Simon Glass226777f2023-01-06 08:52:38 -060099}
100
Simon Glass699b0ac2023-06-01 10:22:52 -0600101static int scene_bbox_union(struct scene *scn, uint id,
102 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) {
112 bbox->x0 = min(bbox->x0, obj->dim.x);
113 bbox->y0 = min(bbox->y0, obj->dim.y);
114 bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w);
115 bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
116 } else {
117 bbox->x0 = obj->dim.x;
118 bbox->y0 = obj->dim.y;
119 bbox->x1 = obj->dim.x + obj->dim.w;
120 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{
138 const struct scene_menitem *item;
139
140 bbox->valid = false;
141 scene_bbox_union(menu->obj.scene, menu->title_id, bbox);
142
143 label_bbox->valid = false;
144
145 list_for_each_entry(item, &menu->item_head, sibling) {
146 scene_bbox_union(menu->obj.scene, item->label_id, bbox);
147 scene_bbox_union(menu->obj.scene, item->key_id, bbox);
148 scene_bbox_union(menu->obj.scene, item->desc_id, bbox);
149 scene_bbox_union(menu->obj.scene, item->preview_id, bbox);
150
151 /* Get the bounding box of all labels */
152 scene_bbox_union(menu->obj.scene, item->label_id, label_bbox);
153 }
154}
155
156int scene_menu_calc_dims(struct scene_obj_menu *menu)
157{
158 struct vidconsole_bbox bbox, label_bbox;
159 const struct scene_menitem *item;
160
161 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
162
163 /* Make all labels the same size */
164 if (label_bbox.valid) {
165 list_for_each_entry(item, &menu->item_head, sibling) {
166 scene_obj_set_size(menu->obj.scene, item->label_id,
167 label_bbox.x1 - label_bbox.x0,
168 label_bbox.y1 - label_bbox.y0);
169 }
170 }
171
172 if (bbox.valid) {
173 menu->obj.dim.w = bbox.x1 - bbox.x0;
174 menu->obj.dim.h = bbox.y1 - bbox.y0;
175 }
176
177 return 0;
178}
179
Simon Glass226777f2023-01-06 08:52:38 -0600180int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
181{
Simon Glassd3db0212023-06-01 10:22:55 -0600182 const bool open = menu->obj.flags & SCENEOF_OPEN;
183 struct expo *exp = scn->expo;
184 const bool stack = exp->popup;
Simon Glass226777f2023-01-06 08:52:38 -0600185 struct scene_menitem *item;
Simon Glass8872bc72023-06-01 10:22:54 -0600186 uint sel_id;
Simon Glassd3db0212023-06-01 10:22:55 -0600187 int x, y;
Simon Glass226777f2023-01-06 08:52:38 -0600188 int ret;
189
Simon Glassd3db0212023-06-01 10:22:55 -0600190 x = menu->obj.dim.x;
Simon Glassae45d6c2023-06-01 10:22:49 -0600191 y = menu->obj.dim.y;
Simon Glass226777f2023-01-06 08:52:38 -0600192 if (menu->title_id) {
Simon Glassae45d6c2023-06-01 10:22:49 -0600193 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
Simon Glass226777f2023-01-06 08:52:38 -0600194 if (ret < 0)
195 return log_msg_ret("tit", ret);
196
197 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
198 if (ret < 0)
199 return log_msg_ret("hei", ret);
200
Simon Glassd3db0212023-06-01 10:22:55 -0600201 if (stack)
202 x += 200;
203 else
204 y += ret * 2;
Simon Glass226777f2023-01-06 08:52:38 -0600205 }
206
207 /*
208 * Currently everything is hard-coded to particular columns so this
209 * won't work on small displays and looks strange if the font size is
210 * small. This can be updated once text measuring is supported in
211 * vidconsole
212 */
Simon Glass8872bc72023-06-01 10:22:54 -0600213 sel_id = menu->cur_item_id;
Simon Glass226777f2023-01-06 08:52:38 -0600214 list_for_each_entry(item, &menu->item_head, sibling) {
Simon Glassd3db0212023-06-01 10:22:55 -0600215 bool selected;
Simon Glass226777f2023-01-06 08:52:38 -0600216 int height;
217
Simon Glassd3db0212023-06-01 10:22:55 -0600218 ret = scene_obj_get_hw(scn, item->label_id, NULL);
Simon Glass226777f2023-01-06 08:52:38 -0600219 if (ret < 0)
220 return log_msg_ret("get", ret);
221 height = ret;
222
223 if (item->flags & SCENEMIF_GAP_BEFORE)
224 y += height;
225
226 /* select an item if not done already */
Simon Glass8872bc72023-06-01 10:22:54 -0600227 if (!sel_id)
228 sel_id = item->id;
Simon Glass226777f2023-01-06 08:52:38 -0600229
Simon Glassd3db0212023-06-01 10:22:55 -0600230 selected = sel_id == item->id;
231
Simon Glass226777f2023-01-06 08:52:38 -0600232 /*
233 * Put the label on the left, then leave a space for the
234 * pointer, then the key and the description
235 */
Simon Glassd3db0212023-06-01 10:22:55 -0600236 ret = scene_obj_set_pos(scn, item->label_id, x, y);
237 if (ret < 0)
238 return log_msg_ret("nam", ret);
239 scene_obj_set_hide(scn, item->label_id,
240 stack && !open && !selected);
241
242 if (item->key_id) {
243 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
Simon Glass226777f2023-01-06 08:52:38 -0600244 if (ret < 0)
Simon Glassd3db0212023-06-01 10:22:55 -0600245 return log_msg_ret("key", ret);
Simon Glass226777f2023-01-06 08:52:38 -0600246 }
247
Simon Glassd3db0212023-06-01 10:22:55 -0600248 if (item->desc_id) {
249 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
250 if (ret < 0)
251 return log_msg_ret("des", ret);
252 }
Simon Glass226777f2023-01-06 08:52:38 -0600253
254 if (item->preview_id) {
255 bool hide;
256
257 /*
258 * put all previews on top of each other, on the right
259 * size of the display
260 */
261 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
262 if (ret < 0)
263 return log_msg_ret("prev", ret);
264
265 hide = menu->cur_item_id != item->id;
266 ret = scene_obj_set_hide(scn, item->preview_id, hide);
267 if (ret < 0)
268 return log_msg_ret("hid", ret);
269 }
270
Simon Glassd3db0212023-06-01 10:22:55 -0600271 if (!stack || open)
272 y += height;
Simon Glass226777f2023-01-06 08:52:38 -0600273 }
274
Simon Glass8872bc72023-06-01 10:22:54 -0600275 if (sel_id)
276 menu_point_to_item(menu, sel_id);
Simon Glass226777f2023-01-06 08:52:38 -0600277
278 return 0;
279}
280
281int scene_menu(struct scene *scn, const char *name, uint id,
282 struct scene_obj_menu **menup)
283{
284 struct scene_obj_menu *menu;
285 int ret;
286
287 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
288 sizeof(struct scene_obj_menu),
289 (struct scene_obj **)&menu);
290 if (ret < 0)
291 return log_msg_ret("obj", -ENOMEM);
292
293 if (menup)
294 *menup = menu;
295 INIT_LIST_HEAD(&menu->item_head);
296
Simon Glass226777f2023-01-06 08:52:38 -0600297 return menu->obj.id;
298}
299
300static struct scene_menitem *scene_menu_find_key(struct scene *scn,
301 struct scene_obj_menu *menu,
302 int key)
303{
304 struct scene_menitem *item;
305
306 list_for_each_entry(item, &menu->item_head, sibling) {
307 if (item->key_id) {
308 struct scene_obj_txt *txt;
309 const char *str;
310
311 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
312 if (txt) {
313 str = expo_get_str(scn->expo, txt->str_id);
314 if (str && *str == key)
315 return item;
316 }
317 }
318 }
319
320 return NULL;
321}
322
323int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
324 struct expo_action *event)
325{
326 struct scene_menitem *item, *cur, *key_item;
327
328 cur = NULL;
329 key_item = NULL;
330
331 if (!list_empty(&menu->item_head)) {
332 list_for_each_entry(item, &menu->item_head, sibling) {
333 /* select an item if not done already */
334 if (menu->cur_item_id == item->id) {
335 cur = item;
336 break;
337 }
338 }
339 }
340
341 if (!cur)
342 return -ENOTTY;
343
344 switch (key) {
345 case BKEY_UP:
346 if (item != list_first_entry(&menu->item_head,
347 struct scene_menitem, sibling)) {
348 item = list_entry(item->sibling.prev,
349 struct scene_menitem, sibling);
Simon Glass3f33b9c2023-06-01 10:22:56 -0600350 event->type = EXPOACT_POINT_ITEM;
Simon Glass226777f2023-01-06 08:52:38 -0600351 event->select.id = item->id;
352 log_debug("up to item %d\n", event->select.id);
353 }
354 break;
355 case BKEY_DOWN:
356 if (!list_is_last(&item->sibling, &menu->item_head)) {
357 item = list_entry(item->sibling.next,
358 struct scene_menitem, sibling);
Simon Glass3f33b9c2023-06-01 10:22:56 -0600359 event->type = EXPOACT_POINT_ITEM;
Simon Glass226777f2023-01-06 08:52:38 -0600360 event->select.id = item->id;
361 log_debug("down to item %d\n", event->select.id);
362 }
363 break;
364 case BKEY_SELECT:
365 event->type = EXPOACT_SELECT;
366 event->select.id = item->id;
367 log_debug("select item %d\n", event->select.id);
368 break;
369 case BKEY_QUIT:
370 event->type = EXPOACT_QUIT;
371 log_debug("quit\n");
372 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 Glass226777f2023-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 Glassd3db0212023-06-01 10:22:55 -0600399 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
Simon Glass226777f2023-01-06 08:52:38 -0600400 return log_msg_ret("txt", -EINVAL);
401
402 item = calloc(1, sizeof(struct scene_obj_menu));
403 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 Glass226777f2023-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 Glass756c9552023-06-01 10:22:57 -0600518
519void scene_menu_render(struct scene_obj_menu *menu)
520{
521 struct expo *exp = menu->obj.scene->expo;
522 const struct expo_theme *theme = &exp->theme;
523 struct vidconsole_bbox bbox, label_bbox;
524 struct udevice *dev = exp->display;
525 struct video_priv *vid_priv;
526 struct udevice *cons = exp->cons;
527 struct vidconsole_colour old;
528 enum colour_idx fore, back;
529
530 if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
531 fore = VID_BLACK;
532 back = VID_WHITE;
533 } else {
534 fore = VID_LIGHT_GRAY;
535 back = VID_BLACK;
536 }
537
538 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
539 vidconsole_push_colour(cons, fore, back, &old);
540 vid_priv = dev_get_uclass_priv(dev);
541 video_fill_part(dev, label_bbox.x0 - theme->menu_inset,
542 label_bbox.y0 - theme->menu_inset,
543 label_bbox.x1, label_bbox.y1 + theme->menu_inset,
544 vid_priv->colour_fg);
545 vidconsole_pop_colour(cons, &old);
546}
Simon Glass4c87e072023-06-01 10:22:58 -0600547
548int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
549{
550 struct scene_menitem *item;
551
552 scene_render_deps(scn, menu->title_id);
553 scene_render_deps(scn, menu->cur_item_id);
554 scene_render_deps(scn, menu->pointer_id);
555
556 list_for_each_entry(item, &menu->item_head, sibling) {
557 scene_render_deps(scn, item->key_id);
558 scene_render_deps(scn, item->label_id);
559 scene_render_deps(scn, item->desc_id);
560 }
561
562 return 0;
563}