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 struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
39 struct scene_menitem *item;
41 list_for_each_entry(item, &menu->item_head, sibling) {
49 struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
52 struct scene_menitem *item;
56 list_for_each_entry(item, &menu->item_head, sibling) {
66 * update_pointers() - Update the pointer object and handle highlights
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
72 static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
74 struct scene *scn = menu->obj.scene;
75 const bool stack = scn->expo->popup;
76 const struct scene_menitem *item;
79 item = scene_menuitem_find(menu, id);
81 return log_msg_ret("itm", -ENOENT);
83 /* adjust the pointer object to point to the selected item */
84 if (menu->pointer_id && item && point) {
85 struct scene_obj *label;
87 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
89 ret = scene_obj_set_pos(scn, menu->pointer_id,
90 menu->obj.dim.x + 200, label->dim.y);
92 return log_msg_ret("ptr", ret);
96 point &= scn->highlight_id == menu->obj.id;
97 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
98 point ? SCENEOF_POINT : 0);
105 * menu_point_to_item() - Point to a particular menu item
107 * Sets the currently pointed-to / highlighted menu item
109 static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
111 if (menu->cur_item_id)
112 update_pointers(menu, menu->cur_item_id, false);
113 menu->cur_item_id = item_id;
114 update_pointers(menu, item_id, true);
117 void scene_menu_calc_bbox(struct scene_obj_menu *menu,
118 struct vidconsole_bbox *bbox,
119 struct vidconsole_bbox *label_bbox)
121 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
122 const struct scene_menitem *item;
125 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
127 label_bbox->valid = false;
129 list_for_each_entry(item, &menu->item_head, sibling) {
130 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);
136 /* Get the bounding box of all labels */
137 scene_bbox_union(menu->obj.scene, item->label_id,
138 theme->menu_inset, label_bbox);
142 * subtract the final menuitem's gap to keep the insert the same top
145 label_bbox->y1 -= theme->menuitem_gap_y;
148 int scene_menu_calc_dims(struct scene_obj_menu *menu)
150 struct vidconsole_bbox bbox, label_bbox;
151 const struct scene_menitem *item;
153 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
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);
165 menu->obj.dim.w = bbox.x1 - bbox.x0;
166 menu->obj.dim.h = bbox.y1 - bbox.y0;
172 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
174 const bool open = menu->obj.flags & SCENEOF_OPEN;
175 struct expo *exp = scn->expo;
176 const bool stack = exp->popup;
177 const struct expo_theme *theme = &exp->theme;
178 struct scene_menitem *item;
185 if (menu->title_id) {
186 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
188 return log_msg_ret("tit", ret);
190 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
192 return log_msg_ret("hei", ret);
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
206 sel_id = menu->cur_item_id;
207 list_for_each_entry(item, &menu->item_head, sibling) {
211 ret = scene_obj_get_hw(scn, item->label_id, NULL);
213 return log_msg_ret("get", ret);
216 if (item->flags & SCENEMIF_GAP_BEFORE)
219 /* select an item if not done already */
223 selected = sel_id == item->id;
226 * Put the label on the left, then leave a space for the
227 * pointer, then the key and the description
229 ret = scene_obj_set_pos(scn, item->label_id,
230 x + theme->menu_inset, y);
232 return log_msg_ret("nam", ret);
233 scene_obj_set_hide(scn, item->label_id,
234 stack && !open && !selected);
237 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
239 return log_msg_ret("key", ret);
243 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
245 return log_msg_ret("des", ret);
248 if (item->preview_id) {
252 * put all previews on top of each other, on the right
253 * size of the display
255 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
257 return log_msg_ret("prev", ret);
259 hide = menu->cur_item_id != item->id;
260 ret = scene_obj_set_hide(scn, item->preview_id, hide);
262 return log_msg_ret("hid", ret);
266 y += height + theme->menuitem_gap_y;
270 menu_point_to_item(menu, sel_id);
275 int scene_menu(struct scene *scn, const char *name, uint id,
276 struct scene_obj_menu **menup)
278 struct scene_obj_menu *menu;
281 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
282 sizeof(struct scene_obj_menu),
283 (struct scene_obj **)&menu);
285 return log_msg_ret("obj", -ENOMEM);
289 INIT_LIST_HEAD(&menu->item_head);
294 static struct scene_menitem *scene_menu_find_key(struct scene *scn,
295 struct scene_obj_menu *menu,
298 struct scene_menitem *item;
300 list_for_each_entry(item, &menu->item_head, sibling) {
302 struct scene_obj_txt *txt;
305 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
307 str = expo_get_str(scn->expo, txt->str_id);
308 if (str && *str == key)
317 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
318 struct expo_action *event)
320 const bool open = menu->obj.flags & SCENEOF_OPEN;
321 struct scene_menitem *item, *cur, *key_item;
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) {
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);
345 event->type = EXPOACT_POINT_ITEM;
346 event->select.id = item->id;
347 log_debug("up to item %d\n", event->select.id);
351 if (!list_is_last(&item->sibling, &menu->item_head)) {
352 item = list_entry(item->sibling.next,
353 struct scene_menitem, sibling);
354 event->type = EXPOACT_POINT_ITEM;
355 event->select.id = item->id;
356 log_debug("down to item %d\n", event->select.id);
360 event->type = EXPOACT_SELECT;
361 event->select.id = item->id;
362 log_debug("select item %d\n", event->select.id);
365 if (scn->expo->popup && open) {
366 event->type = EXPOACT_CLOSE;
367 event->select.id = menu->obj.id;
369 event->type = EXPOACT_QUIT;
370 log_debug("menu quit\n");
374 key_item = scene_menu_find_key(scn, menu, key);
376 event->type = EXPOACT_SELECT;
377 event->select.id = key_item->id;
382 menu_point_to_item(menu, item->id);
387 int 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)
391 struct scene_obj_menu *menu;
392 struct scene_menitem *item;
394 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
396 return log_msg_ret("find", -ENOENT);
398 /* Check that the text ID is valid */
399 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
400 return log_msg_ret("txt", -EINVAL);
402 item = calloc(1, sizeof(struct scene_menitem));
404 return log_msg_ret("item", -ENOMEM);
405 item->name = strdup(name);
408 return log_msg_ret("name", -ENOMEM);
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;
417 list_add_tail(&item->sibling, &menu->item_head);
425 int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
427 struct scene_obj_menu *menu;
428 struct scene_obj_txt *txt;
430 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
432 return log_msg_ret("menu", -ENOENT);
434 /* Check that the ID is valid */
436 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
438 return log_msg_ret("txt", -EINVAL);
441 menu->title_id = title_id;
446 int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
448 struct scene_obj_menu *menu;
449 struct scene_obj *obj;
451 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
453 return log_msg_ret("menu", -ENOENT);
455 /* Check that the ID is valid */
457 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
459 return log_msg_ret("obj", -EINVAL);
462 menu->pointer_id = pointer_id;
467 int scene_menu_display(struct scene_obj_menu *menu)
469 struct scene *scn = menu->obj.scene;
470 struct scene_obj_txt *pointer;
471 struct expo *exp = scn->expo;
472 struct scene_menitem *item;
475 printf("U-Boot : Boot Menu\n\n");
476 if (menu->title_id) {
477 struct scene_obj_txt *txt;
480 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
482 return log_msg_ret("txt", -EINVAL);
484 str = expo_get_str(exp, txt->str_id);
485 printf("%s\n\n", str);
488 if (list_empty(&menu->item_head))
491 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
492 pstr = expo_get_str(scn->expo, pointer->str_id);
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;
499 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
501 kstr = expo_get_str(exp, key->str_id);
503 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
505 lstr = expo_get_str(exp, label->str_id);
507 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
509 dstr = expo_get_str(exp, desc->str_id);
511 printf("%3s %3s %-10s %s\n",
512 pointer && menu->cur_item_id == item->id ? pstr : "",
519 int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
521 struct scene_menitem *item;
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);
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);