1 // SPDX-License-Identifier: GPL-2.0+
3 * Implementation of a menu in a scene
5 * Copyright 2022 Google LLC
9 #define LOG_CATEGORY LOGC_EXPO
18 #include <video_console.h>
19 #include <linux/input.h>
20 #include "scene_internal.h"
22 static void scene_menuitem_destroy(struct scene_menitem *item)
28 void scene_menu_destroy(struct scene_obj_menu *menu)
30 struct scene_menitem *item, *next;
32 list_for_each_entry_safe(item, next, &menu->item_head, sibling)
33 scene_menuitem_destroy(item);
36 static struct scene_menitem *scene_menuitem_find(struct scene_obj_menu *menu,
39 struct scene_menitem *item;
41 list_for_each_entry(item, &menu->item_head, sibling) {
50 * update_pointers() - Update the pointer object and handle highlights
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
56 static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
58 struct scene *scn = menu->obj.scene;
59 const bool stack = scn->expo->popup;
60 const struct scene_menitem *item;
63 item = scene_menuitem_find(menu, id);
65 return log_msg_ret("itm", -ENOENT);
67 /* adjust the pointer object to point to the selected item */
68 if (menu->pointer_id && item && point) {
69 struct scene_obj *label;
71 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
73 ret = scene_obj_set_pos(scn, menu->pointer_id,
74 menu->obj.dim.x + 200, label->dim.y);
76 return log_msg_ret("ptr", ret);
80 point &= scn->highlight_id == menu->obj.id;
81 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
82 point ? SCENEOF_POINT : 0);
89 * menu_point_to_item() - Point to a particular menu item
91 * Sets the currently pointed-to / highlighted menu item
93 static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
95 if (menu->cur_item_id)
96 update_pointers(menu, menu->cur_item_id, false);
97 menu->cur_item_id = item_id;
98 update_pointers(menu, item_id, true);
101 static int scene_bbox_union(struct scene *scn, uint id, int inset,
102 struct vidconsole_bbox *bbox)
104 struct scene_obj *obj;
108 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
110 return log_msg_ret("obj", -ENOENT);
112 bbox->x0 = min(bbox->x0, obj->dim.x - inset);
113 bbox->y0 = min(bbox->y0, obj->dim.y);
114 bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset);
115 bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
117 bbox->x0 = obj->dim.x - inset;
118 bbox->y0 = obj->dim.y;
119 bbox->x1 = obj->dim.x + obj->dim.w + inset;
120 bbox->y1 = obj->dim.y + obj->dim.h;
128 * scene_menu_calc_bbox() - Calculate bounding boxes for the menu
130 * @menu: Menu to process
131 * @bbox: Returns bounding box of menu including prompts
132 * @label_bbox: Returns bounding box of labels
134 static void scene_menu_calc_bbox(struct scene_obj_menu *menu,
135 struct vidconsole_bbox *bbox,
136 struct vidconsole_bbox *label_bbox)
138 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
139 const struct scene_menitem *item;
142 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
144 label_bbox->valid = false;
146 list_for_each_entry(item, &menu->item_head, sibling) {
147 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);
153 /* Get the bounding box of all labels */
154 scene_bbox_union(menu->obj.scene, item->label_id,
155 theme->menu_inset, label_bbox);
159 * subtract the final menuitem's gap to keep the insert the same top
162 label_bbox->y1 -= theme->menuitem_gap_y;
165 int scene_menu_calc_dims(struct scene_obj_menu *menu)
167 struct vidconsole_bbox bbox, label_bbox;
168 const struct scene_menitem *item;
170 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
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);
182 menu->obj.dim.w = bbox.x1 - bbox.x0;
183 menu->obj.dim.h = bbox.y1 - bbox.y0;
189 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
191 const bool open = menu->obj.flags & SCENEOF_OPEN;
192 struct expo *exp = scn->expo;
193 const bool stack = exp->popup;
194 const struct expo_theme *theme = &exp->theme;
195 struct scene_menitem *item;
202 if (menu->title_id) {
203 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
205 return log_msg_ret("tit", ret);
207 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
209 return log_msg_ret("hei", ret);
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
223 sel_id = menu->cur_item_id;
224 list_for_each_entry(item, &menu->item_head, sibling) {
228 ret = scene_obj_get_hw(scn, item->label_id, NULL);
230 return log_msg_ret("get", ret);
233 if (item->flags & SCENEMIF_GAP_BEFORE)
236 /* select an item if not done already */
240 selected = sel_id == item->id;
243 * Put the label on the left, then leave a space for the
244 * pointer, then the key and the description
246 ret = scene_obj_set_pos(scn, item->label_id,
247 x + theme->menu_inset, y);
249 return log_msg_ret("nam", ret);
250 scene_obj_set_hide(scn, item->label_id,
251 stack && !open && !selected);
254 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
256 return log_msg_ret("key", ret);
260 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
262 return log_msg_ret("des", ret);
265 if (item->preview_id) {
269 * put all previews on top of each other, on the right
270 * size of the display
272 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
274 return log_msg_ret("prev", ret);
276 hide = menu->cur_item_id != item->id;
277 ret = scene_obj_set_hide(scn, item->preview_id, hide);
279 return log_msg_ret("hid", ret);
283 y += height + theme->menuitem_gap_y;
287 menu_point_to_item(menu, sel_id);
292 int scene_menu(struct scene *scn, const char *name, uint id,
293 struct scene_obj_menu **menup)
295 struct scene_obj_menu *menu;
298 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
299 sizeof(struct scene_obj_menu),
300 (struct scene_obj **)&menu);
302 return log_msg_ret("obj", -ENOMEM);
306 INIT_LIST_HEAD(&menu->item_head);
311 static struct scene_menitem *scene_menu_find_key(struct scene *scn,
312 struct scene_obj_menu *menu,
315 struct scene_menitem *item;
317 list_for_each_entry(item, &menu->item_head, sibling) {
319 struct scene_obj_txt *txt;
322 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
324 str = expo_get_str(scn->expo, txt->str_id);
325 if (str && *str == key)
334 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
335 struct expo_action *event)
337 const bool open = menu->obj.flags & SCENEOF_OPEN;
338 struct scene_menitem *item, *cur, *key_item;
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) {
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);
362 event->type = EXPOACT_POINT_ITEM;
363 event->select.id = item->id;
364 log_debug("up to item %d\n", event->select.id);
368 if (!list_is_last(&item->sibling, &menu->item_head)) {
369 item = list_entry(item->sibling.next,
370 struct scene_menitem, sibling);
371 event->type = EXPOACT_POINT_ITEM;
372 event->select.id = item->id;
373 log_debug("down to item %d\n", event->select.id);
377 event->type = EXPOACT_SELECT;
378 event->select.id = item->id;
379 log_debug("select item %d\n", event->select.id);
382 if (scn->expo->popup && open) {
383 event->type = EXPOACT_CLOSE;
384 event->select.id = menu->obj.id;
386 event->type = EXPOACT_QUIT;
387 log_debug("menu quit\n");
391 key_item = scene_menu_find_key(scn, menu, key);
393 event->type = EXPOACT_SELECT;
394 event->select.id = key_item->id;
399 menu_point_to_item(menu, item->id);
404 int 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)
408 struct scene_obj_menu *menu;
409 struct scene_menitem *item;
411 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
413 return log_msg_ret("find", -ENOENT);
415 /* Check that the text ID is valid */
416 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
417 return log_msg_ret("txt", -EINVAL);
419 item = calloc(1, sizeof(struct scene_obj_menu));
421 return log_msg_ret("item", -ENOMEM);
422 item->name = strdup(name);
425 return log_msg_ret("name", -ENOMEM);
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;
434 list_add_tail(&item->sibling, &menu->item_head);
442 int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
444 struct scene_obj_menu *menu;
445 struct scene_obj_txt *txt;
447 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
449 return log_msg_ret("menu", -ENOENT);
451 /* Check that the ID is valid */
453 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
455 return log_msg_ret("txt", -EINVAL);
458 menu->title_id = title_id;
463 int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
465 struct scene_obj_menu *menu;
466 struct scene_obj *obj;
468 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
470 return log_msg_ret("menu", -ENOENT);
472 /* Check that the ID is valid */
474 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
476 return log_msg_ret("obj", -EINVAL);
479 menu->pointer_id = pointer_id;
484 int scene_menu_display(struct scene_obj_menu *menu)
486 struct scene *scn = menu->obj.scene;
487 struct scene_obj_txt *pointer;
488 struct expo *exp = scn->expo;
489 struct scene_menitem *item;
492 printf("U-Boot : Boot Menu\n\n");
493 if (menu->title_id) {
494 struct scene_obj_txt *txt;
497 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
499 return log_msg_ret("txt", -EINVAL);
501 str = expo_get_str(exp, txt->str_id);
502 printf("%s\n\n", str);
505 if (list_empty(&menu->item_head))
508 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
509 pstr = expo_get_str(scn->expo, pointer->str_id);
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;
516 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
518 kstr = expo_get_str(exp, key->str_id);
520 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
522 lstr = expo_get_str(exp, label->str_id);
524 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
526 dstr = expo_get_str(exp, desc->str_id);
528 printf("%3s %3s %-10s %s\n",
529 pointer && menu->cur_item_id == item->id ? pstr : "",
536 void scene_menu_render(struct scene_obj_menu *menu)
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;
547 if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
551 fore = VID_LIGHT_GRAY;
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);
565 int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
567 struct scene_menitem *item;
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);
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);