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
17 #include <video_console.h>
18 #include <linux/input.h>
19 #include "scene_internal.h"
21 static void scene_menuitem_destroy(struct scene_menitem *item)
27 void scene_menu_destroy(struct scene_obj_menu *menu)
29 struct scene_menitem *item, *next;
31 list_for_each_entry_safe(item, next, &menu->item_head, sibling)
32 scene_menuitem_destroy(item);
35 struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
38 struct scene_menitem *item;
40 list_for_each_entry(item, &menu->item_head, sibling) {
48 struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
51 struct scene_menitem *item;
55 list_for_each_entry(item, &menu->item_head, sibling) {
65 * update_pointers() - Update the pointer object and handle highlights
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
71 static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
73 struct scene *scn = menu->obj.scene;
74 const bool stack = scn->expo->popup;
75 const struct scene_menitem *item;
78 item = scene_menuitem_find(menu, id);
80 return log_msg_ret("itm", -ENOENT);
82 /* adjust the pointer object to point to the selected item */
83 if (menu->pointer_id && item && point) {
84 struct scene_obj *label;
86 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
88 ret = scene_obj_set_pos(scn, menu->pointer_id,
89 menu->obj.dim.x + 200, label->dim.y);
91 return log_msg_ret("ptr", ret);
95 point &= scn->highlight_id == menu->obj.id;
96 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
97 point ? SCENEOF_POINT : 0);
104 * menu_point_to_item() - Point to a particular menu item
106 * Sets the currently pointed-to / highlighted menu item
108 static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
110 if (menu->cur_item_id)
111 update_pointers(menu, menu->cur_item_id, false);
112 menu->cur_item_id = item_id;
113 update_pointers(menu, item_id, true);
116 void scene_menu_calc_bbox(struct scene_obj_menu *menu,
117 struct vidconsole_bbox *bbox,
118 struct vidconsole_bbox *label_bbox)
120 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
121 const struct scene_menitem *item;
124 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
126 label_bbox->valid = false;
128 list_for_each_entry(item, &menu->item_head, sibling) {
129 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);
135 /* Get the bounding box of all labels */
136 scene_bbox_union(menu->obj.scene, item->label_id,
137 theme->menu_inset, label_bbox);
141 * subtract the final menuitem's gap to keep the insert the same top
144 label_bbox->y1 -= theme->menuitem_gap_y;
147 int scene_menu_calc_dims(struct scene_obj_menu *menu)
149 struct vidconsole_bbox bbox, label_bbox;
150 const struct scene_menitem *item;
152 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
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);
164 menu->obj.dim.w = bbox.x1 - bbox.x0;
165 menu->obj.dim.h = bbox.y1 - bbox.y0;
171 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
173 const bool open = menu->obj.flags & SCENEOF_OPEN;
174 struct expo *exp = scn->expo;
175 const bool stack = exp->popup;
176 const struct expo_theme *theme = &exp->theme;
177 struct scene_menitem *item;
184 if (menu->title_id) {
185 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
187 return log_msg_ret("tit", ret);
189 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
191 return log_msg_ret("hei", ret);
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
205 sel_id = menu->cur_item_id;
206 list_for_each_entry(item, &menu->item_head, sibling) {
210 ret = scene_obj_get_hw(scn, item->label_id, NULL);
212 return log_msg_ret("get", ret);
215 if (item->flags & SCENEMIF_GAP_BEFORE)
218 /* select an item if not done already */
222 selected = sel_id == item->id;
225 * Put the label on the left, then leave a space for the
226 * pointer, then the key and the description
228 ret = scene_obj_set_pos(scn, item->label_id,
229 x + theme->menu_inset, y);
231 return log_msg_ret("nam", ret);
232 scene_obj_set_hide(scn, item->label_id,
233 stack && !open && !selected);
236 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
238 return log_msg_ret("key", ret);
242 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
244 return log_msg_ret("des", ret);
247 if (item->preview_id) {
251 * put all previews on top of each other, on the right
252 * size of the display
254 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
256 return log_msg_ret("prev", ret);
258 hide = menu->cur_item_id != item->id;
259 ret = scene_obj_set_hide(scn, item->preview_id, hide);
261 return log_msg_ret("hid", ret);
265 y += height + theme->menuitem_gap_y;
269 menu_point_to_item(menu, sel_id);
274 int scene_menu(struct scene *scn, const char *name, uint id,
275 struct scene_obj_menu **menup)
277 struct scene_obj_menu *menu;
280 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
281 sizeof(struct scene_obj_menu),
282 (struct scene_obj **)&menu);
284 return log_msg_ret("obj", -ENOMEM);
288 INIT_LIST_HEAD(&menu->item_head);
293 static struct scene_menitem *scene_menu_find_key(struct scene *scn,
294 struct scene_obj_menu *menu,
297 struct scene_menitem *item;
299 list_for_each_entry(item, &menu->item_head, sibling) {
301 struct scene_obj_txt *txt;
304 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
306 str = expo_get_str(scn->expo, txt->str_id);
307 if (str && *str == key)
316 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
317 struct expo_action *event)
319 const bool open = menu->obj.flags & SCENEOF_OPEN;
320 struct scene_menitem *item, *cur, *key_item;
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) {
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);
344 event->type = EXPOACT_POINT_ITEM;
345 event->select.id = item->id;
346 log_debug("up to item %d\n", event->select.id);
350 if (!list_is_last(&item->sibling, &menu->item_head)) {
351 item = list_entry(item->sibling.next,
352 struct scene_menitem, sibling);
353 event->type = EXPOACT_POINT_ITEM;
354 event->select.id = item->id;
355 log_debug("down to item %d\n", event->select.id);
359 event->type = EXPOACT_SELECT;
360 event->select.id = item->id;
361 log_debug("select item %d\n", event->select.id);
364 if (scn->expo->popup && open) {
365 event->type = EXPOACT_CLOSE;
366 event->select.id = menu->obj.id;
368 event->type = EXPOACT_QUIT;
369 log_debug("menu quit\n");
373 key_item = scene_menu_find_key(scn, menu, key);
375 event->type = EXPOACT_SELECT;
376 event->select.id = key_item->id;
381 menu_point_to_item(menu, item->id);
386 int 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)
390 struct scene_obj_menu *menu;
391 struct scene_menitem *item;
393 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
395 return log_msg_ret("find", -ENOENT);
397 /* Check that the text ID is valid */
398 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
399 return log_msg_ret("txt", -EINVAL);
401 item = calloc(1, sizeof(struct scene_menitem));
403 return log_msg_ret("item", -ENOMEM);
404 item->name = strdup(name);
407 return log_msg_ret("name", -ENOMEM);
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;
416 list_add_tail(&item->sibling, &menu->item_head);
424 int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
426 struct scene_obj_menu *menu;
427 struct scene_obj_txt *txt;
429 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
431 return log_msg_ret("menu", -ENOENT);
433 /* Check that the ID is valid */
435 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
437 return log_msg_ret("txt", -EINVAL);
440 menu->title_id = title_id;
445 int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
447 struct scene_obj_menu *menu;
448 struct scene_obj *obj;
450 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
452 return log_msg_ret("menu", -ENOENT);
454 /* Check that the ID is valid */
456 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
458 return log_msg_ret("obj", -EINVAL);
461 menu->pointer_id = pointer_id;
466 int scene_menu_display(struct scene_obj_menu *menu)
468 struct scene *scn = menu->obj.scene;
469 struct scene_obj_txt *pointer;
470 struct expo *exp = scn->expo;
471 struct scene_menitem *item;
474 printf("U-Boot : Boot Menu\n\n");
475 if (menu->title_id) {
476 struct scene_obj_txt *txt;
479 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
481 return log_msg_ret("txt", -EINVAL);
483 str = expo_get_str(exp, txt->str_id);
484 printf("%s\n\n", str);
487 if (list_empty(&menu->item_head))
490 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
491 pstr = expo_get_str(scn->expo, pointer->str_id);
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;
498 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
500 kstr = expo_get_str(exp, key->str_id);
502 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
504 lstr = expo_get_str(exp, label->str_id);
506 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
508 dstr = expo_get_str(exp, desc->str_id);
510 printf("%3s %3s %-10s %s\n",
511 pointer && menu->cur_item_id == item->id ? pstr : "",
518 int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
520 struct scene_menitem *item;
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);
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);