1 // 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
9 #define LOG_CATEGORY LOGC_EXPO
10
11 #include <dm.h>
12 #include <expo.h>
13 #include <malloc.h>
14 #include <mapmem.h>
15 #include <menu.h>
16 #include <video.h>
17 #include <video_console.h>
18 #include <linux/input.h>
19 #include "scene_internal.h"
20
scene_menuitem_destroy(struct scene_menitem * item)21 static void scene_menuitem_destroy(struct scene_menitem *item)
22 {
23 free(item->name);
24 free(item);
25 }
26
scene_menu_destroy(struct scene_obj_menu * menu)27 void scene_menu_destroy(struct scene_obj_menu *menu)
28 {
29 struct scene_menitem *item, *next;
30
31 list_for_each_entry_safe(item, next, &menu->item_head, sibling)
32 scene_menuitem_destroy(item);
33 }
34
scene_menuitem_find(const struct scene_obj_menu * menu,int id)35 struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
36 int id)
37 {
38 struct scene_menitem *item;
39
40 list_for_each_entry(item, &menu->item_head, sibling) {
41 if (item->id == id)
42 return item;
43 }
44
45 return NULL;
46 }
47
scene_menuitem_find_seq(const struct scene_obj_menu * menu,uint seq)48 struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
49 uint seq)
50 {
51 struct scene_menitem *item;
52 uint i;
53
54 i = 0;
55 list_for_each_entry(item, &menu->item_head, sibling) {
56 if (i == seq)
57 return item;
58 i++;
59 }
60
61 return NULL;
62 }
63
scene_menuitem_find_val(const struct scene_obj_menu * menu,int val)64 struct scene_menitem *scene_menuitem_find_val(const struct scene_obj_menu *menu,
65 int val)
66 {
67 struct scene_menitem *item;
68 uint i;
69
70 i = 0;
71 list_for_each_entry(item, &menu->item_head, sibling) {
72 if (item->value == INT_MAX ? val == i : item->value == val)
73 return item;
74 i++;
75 }
76
77 return NULL;
78 }
79
80 /**
81 * update_pointers() - Update the pointer object and handle highlights
82 *
83 * @menu: Menu to update
84 * @id: ID of menu item to select/deselect
85 * @point: true if @id is being selected, false if it is being deselected
86 */
update_pointers(struct scene_obj_menu * menu,uint id,bool point)87 static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
88 {
89 struct scene *scn = menu->obj.scene;
90 const bool stack = scn->expo->show_highlight;
91 const struct scene_menitem *item;
92 int ret;
93
94 item = scene_menuitem_find(menu, id);
95 if (!item)
96 return log_msg_ret("itm", -ENOENT);
97
98 /* adjust the pointer object to point to the selected item */
99 if (menu->pointer_id && item && point) {
100 struct scene_obj *label;
101
102 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
103
104 ret = scene_obj_set_pos(scn, menu->pointer_id,
105 menu->obj.bbox.x0 + 200, label->bbox.y0);
106 if (ret < 0)
107 return log_msg_ret("ptr", ret);
108 }
109
110 if (stack) {
111 uint id;
112 int val;
113
114 point &= scn->highlight_id == menu->obj.id;
115 val = point ? SCENEOF_POINT : 0;
116 id = item->desc_id;
117 if (!id)
118 id = item->label_id;
119 if (!id)
120 id = item->key_id;
121 scene_obj_flag_clrset(scn, id, SCENEOF_POINT, val);
122 }
123
124 return 0;
125 }
126
127 /**
128 * menu_point_to_item() - Point to a particular menu item
129 *
130 * Sets the currently pointed-to / highlighted menu item
131 */
menu_point_to_item(struct scene_obj_menu * menu,uint item_id)132 static int menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
133 {
134 int ret;
135
136 if (menu->cur_item_id) {
137 ret = update_pointers(menu, menu->cur_item_id, false);
138 if (ret)
139 return log_msg_ret("mpi", ret);
140 }
141 menu->cur_item_id = item_id;
142 ret = update_pointers(menu, item_id, true);
143 if (ret)
144 return log_msg_ret("mpu", ret);
145
146 return 0;
147 }
148
scene_menu_calc_bbox(struct scene_obj_menu * menu,struct vidconsole_bbox * bbox)149 void scene_menu_calc_bbox(struct scene_obj_menu *menu,
150 struct vidconsole_bbox *bbox)
151 {
152 const struct expo_theme *theme = &menu->obj.scene->expo->theme;
153 const struct scene_menitem *item;
154 int inset = theme->menu_inset;
155 int i;
156
157 for (i = 0; i < SCENEBB_count; i++)
158 bbox[i].valid = false;
159
160 scene_bbox_union(menu->obj.scene, menu->title_id, 0,
161 &bbox[SCENEBB_all]);
162
163 list_for_each_entry(item, &menu->item_head, sibling) {
164 struct vidconsole_bbox local;
165
166 local.valid = false;
167 scene_bbox_union(menu->obj.scene, item->label_id, inset,
168 &local);
169 scene_bbox_union(menu->obj.scene, item->key_id, 0, &local);
170 scene_bbox_union(menu->obj.scene, item->desc_id, 0, &local);
171 scene_bbox_union(menu->obj.scene, item->preview_id, 0, &local);
172
173 scene_bbox_join(&local, 0, &bbox[SCENEBB_all]);
174
175 /* Get the bounding box of all individual fields */
176 scene_bbox_union(menu->obj.scene, item->label_id, inset,
177 &bbox[SCENEBB_label]);
178 scene_bbox_union(menu->obj.scene, item->key_id, inset,
179 &bbox[SCENEBB_key]);
180 scene_bbox_union(menu->obj.scene, item->desc_id, inset,
181 &bbox[SCENEBB_desc]);
182
183 if (menu->cur_item_id == item->id)
184 scene_bbox_join(&local, 0, &bbox[SCENEBB_curitem]);
185 }
186
187 /*
188 * subtract the final menuitem's gap to keep the inset the same top and
189 * bottom
190 */
191 bbox[SCENEBB_label].y1 -= theme->menuitem_gap_y;
192 }
193
scene_menu_calc_dims(struct scene_obj_menu * menu)194 int scene_menu_calc_dims(struct scene_obj_menu *menu)
195 {
196 struct vidconsole_bbox bbox[SCENEBB_count], *cur;
197 const struct scene_menitem *item;
198
199 scene_menu_calc_bbox(menu, bbox);
200
201 /* Make all field types the same width */
202 list_for_each_entry(item, &menu->item_head, sibling) {
203 cur = &bbox[SCENEBB_label];
204 if (cur->valid)
205 scene_obj_set_width(menu->obj.scene, item->label_id,
206 cur->x1 - cur->x0);
207 cur = &bbox[SCENEBB_key];
208 if (cur->valid)
209 scene_obj_set_width(menu->obj.scene, item->key_id,
210 cur->x1 - cur->x0);
211 cur = &bbox[SCENEBB_desc];
212 if (cur->valid)
213 scene_obj_set_width(menu->obj.scene, item->desc_id,
214 cur->x1 - cur->x0);
215 }
216
217 cur = &bbox[SCENEBB_all];
218 if (cur->valid) {
219 menu->obj.dims.x = cur->x1 - cur->x0;
220 menu->obj.dims.y = cur->y1 - cur->y0;
221
222 menu->obj.bbox.x1 = cur->x1;
223 menu->obj.bbox.y1 = cur->y1;
224 }
225
226 return 0;
227 }
228
scene_menu_arrange(struct scene * scn,struct expo_arrange_info * arr,struct scene_obj_menu * menu)229 int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr,
230 struct scene_obj_menu *menu)
231 {
232 const bool open = menu->obj.flags & SCENEOF_OPEN;
233 struct expo *exp = scn->expo;
234 const bool stack = exp->popup;
235 const struct expo_theme *theme = &exp->theme;
236 struct scene_menitem *item;
237 uint sel_id;
238 int x, y;
239 int ret;
240
241 x = menu->obj.bbox.x0;
242 y = menu->obj.bbox.y0;
243 if (menu->title_id) {
244 int width;
245
246 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.bbox.x0, y);
247 if (ret < 0)
248 return log_msg_ret("tit", ret);
249
250 ret = scene_obj_get_hw(scn, menu->title_id, &width);
251 if (ret < 0)
252 return log_msg_ret("hei", ret);
253
254 if (stack)
255 x += arr->label_width + theme->menu_title_margin_x;
256 else
257 y += ret * 2;
258 }
259
260 /*
261 * Currently everything is hard-coded to particular columns so this
262 * won't work on small displays and looks strange if the font size is
263 * small. This can be updated once text measuring is supported in
264 * vidconsole
265 */
266 sel_id = menu->cur_item_id;
267 list_for_each_entry(item, &menu->item_head, sibling) {
268 bool selected;
269 int height;
270
271 ret = scene_obj_get_hw(scn, item->label_id, NULL);
272 if (ret < 0)
273 return log_msg_ret("get", ret);
274 height = ret;
275
276 if (item->flags & SCENEMIF_GAP_BEFORE)
277 y += height;
278
279 /* select an item if not done already */
280 if (!sel_id)
281 sel_id = item->id;
282
283 selected = sel_id == item->id;
284
285 /*
286 * Put the label on the left, then leave a space for the
287 * pointer, then the key and the description
288 */
289 ret = scene_obj_set_pos(scn, item->label_id,
290 x + theme->menu_inset, y);
291 if (ret < 0)
292 return log_msg_ret("nam", ret);
293 scene_obj_set_hide(scn, item->label_id,
294 stack && !open && !selected);
295
296 if (item->key_id) {
297 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
298 if (ret < 0)
299 return log_msg_ret("key", ret);
300 }
301
302 if (item->desc_id) {
303 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
304 if (ret < 0)
305 return log_msg_ret("des", ret);
306 }
307
308 if (item->preview_id) {
309 bool hide;
310
311 /*
312 * put all previews on top of each other, on the right
313 * size of the display
314 */
315 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
316 if (ret < 0)
317 return log_msg_ret("prev", ret);
318
319 hide = menu->cur_item_id != item->id;
320 ret = scene_obj_set_hide(scn, item->preview_id, hide);
321 if (ret < 0)
322 return log_msg_ret("hid", ret);
323 }
324
325 if (!stack || open)
326 y += height + theme->menuitem_gap_y;
327 }
328
329 if (sel_id)
330 menu_point_to_item(menu, sel_id);
331 menu->obj.bbox.x1 = menu->obj.bbox.x0 + menu->obj.dims.x;
332 menu->obj.bbox.y1 = menu->obj.bbox.y0 + menu->obj.dims.y;
333 menu->obj.flags |= SCENEOF_SIZE_VALID;
334
335 return 0;
336 }
337
scene_menu(struct scene * scn,const char * name,uint id,struct scene_obj_menu ** menup)338 int scene_menu(struct scene *scn, const char *name, uint id,
339 struct scene_obj_menu **menup)
340 {
341 struct scene_obj_menu *menu;
342 int ret;
343
344 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
345 sizeof(struct scene_obj_menu),
346 (struct scene_obj **)&menu);
347 if (ret < 0)
348 return log_msg_ret("obj", -ENOMEM);
349
350 if (menup)
351 *menup = menu;
352 INIT_LIST_HEAD(&menu->item_head);
353
354 return menu->obj.id;
355 }
356
scene_menu_find_key(struct scene * scn,struct scene_obj_menu * menu,int key)357 static struct scene_menitem *scene_menu_find_key(struct scene *scn,
358 struct scene_obj_menu *menu,
359 int key)
360 {
361 struct scene_menitem *item;
362
363 list_for_each_entry(item, &menu->item_head, sibling) {
364 if (item->key_id) {
365 struct scene_obj_txt *txt;
366 const char *str;
367
368 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
369 if (txt) {
370 str = expo_get_str(scn->expo, txt->gen.str_id);
371 if (str && *str == key)
372 return item;
373 }
374 }
375 }
376
377 return NULL;
378 }
379
scene_menu_send_key(struct scene * scn,struct scene_obj_menu * menu,int key,struct expo_action * event)380 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
381 struct expo_action *event)
382 {
383 const bool open = menu->obj.flags & SCENEOF_OPEN;
384 struct scene_menitem *item, *cur, *key_item;
385
386 cur = NULL;
387 key_item = NULL;
388
389 if (!list_empty(&menu->item_head)) {
390 list_for_each_entry(item, &menu->item_head, sibling) {
391 /* select an item if not done already */
392 if (menu->cur_item_id == item->id) {
393 cur = item;
394 break;
395 }
396 }
397 }
398
399 if (!cur)
400 return -ENOTTY;
401
402 switch (key) {
403 case BKEY_UP:
404 if (item != list_first_entry(&menu->item_head,
405 struct scene_menitem, sibling)) {
406 item = list_entry(item->sibling.prev,
407 struct scene_menitem, sibling);
408 event->type = EXPOACT_POINT_ITEM;
409 event->select.id = item->id;
410 log_debug("up to item %d\n", event->select.id);
411 }
412 break;
413 case BKEY_DOWN:
414 if (!list_is_last(&item->sibling, &menu->item_head)) {
415 item = list_entry(item->sibling.next,
416 struct scene_menitem, sibling);
417 event->type = EXPOACT_POINT_ITEM;
418 event->select.id = item->id;
419 log_debug("down to item %d\n", event->select.id);
420 }
421 break;
422 case BKEY_SELECT:
423 event->type = EXPOACT_SELECT;
424 event->select.id = item->id;
425 log_debug("select item %d\n", event->select.id);
426 break;
427 case BKEY_QUIT:
428 if (scn->expo->popup && open) {
429 event->type = EXPOACT_CLOSE;
430 event->select.id = menu->obj.id;
431 } else {
432 event->type = EXPOACT_QUIT;
433 log_debug("menu quit\n");
434 }
435 break;
436 case '0'...'9':
437 key_item = scene_menu_find_key(scn, menu, key);
438 if (key_item) {
439 event->type = EXPOACT_SELECT;
440 event->select.id = key_item->id;
441 }
442 break;
443 }
444
445 return 0;
446 }
447
scene_menuitem(struct scene * scn,uint menu_id,const char * name,uint id,uint key_id,uint label_id,uint desc_id,uint preview_id,uint flags,struct scene_menitem ** itemp)448 int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
449 uint key_id, uint label_id, uint desc_id, uint preview_id,
450 uint flags, struct scene_menitem **itemp)
451 {
452 struct scene_obj_menu *menu;
453 struct scene_menitem *item;
454
455 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
456 if (!menu)
457 return log_msg_ret("find", -ENOENT);
458
459 /* Check that the text ID is valid */
460 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
461 return log_msg_ret("txt", -EINVAL);
462
463 item = calloc(1, sizeof(struct scene_menitem));
464 if (!item)
465 return log_msg_ret("item", -ENOMEM);
466 item->name = strdup(name);
467 if (!item->name) {
468 free(item);
469 return log_msg_ret("name", -ENOMEM);
470 }
471
472 item->id = resolve_id(scn->expo, id);
473 item->key_id = key_id;
474 item->label_id = label_id;
475 item->desc_id = desc_id;
476 item->preview_id = preview_id;
477 item->flags = flags;
478 item->value = INT_MAX;
479 list_add_tail(&item->sibling, &menu->item_head);
480
481 if (itemp)
482 *itemp = item;
483
484 return item->id;
485 }
486
scene_menu_set_title(struct scene * scn,uint id,uint title_id)487 int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
488 {
489 struct scene_obj_menu *menu;
490 struct scene_obj_txt *txt;
491
492 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
493 if (!menu)
494 return log_msg_ret("menu", -ENOENT);
495
496 /* Check that the ID is valid */
497 if (title_id) {
498 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
499 if (!txt)
500 return log_msg_ret("txt", -EINVAL);
501 }
502
503 menu->title_id = title_id;
504
505 return 0;
506 }
507
scene_menu_set_pointer(struct scene * scn,uint id,uint pointer_id)508 int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
509 {
510 struct scene_obj_menu *menu;
511 struct scene_obj *obj;
512
513 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
514 if (!menu)
515 return log_msg_ret("menu", -ENOENT);
516
517 /* Check that the ID is valid */
518 if (pointer_id) {
519 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
520 if (!obj)
521 return log_msg_ret("obj", -EINVAL);
522 }
523
524 menu->pointer_id = pointer_id;
525
526 return 0;
527 }
528
scene_menu_select_item(struct scene * scn,uint id,uint cur_item_id)529 int scene_menu_select_item(struct scene *scn, uint id, uint cur_item_id)
530 {
531 struct scene_obj_menu *menu;
532 int ret;
533
534 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
535 if (!menu)
536 return log_msg_ret("menu", -ENOENT);
537
538 ret = menu_point_to_item(menu, cur_item_id);
539 if (ret)
540 return log_msg_ret("msi", ret);
541
542 return 0;
543 }
544
scene_menu_get_cur_item(struct scene * scn,uint id)545 int scene_menu_get_cur_item(struct scene *scn, uint id)
546 {
547 struct scene_obj_menu *menu;
548
549 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
550 if (!menu)
551 return log_msg_ret("menu", -ENOENT);
552
553 return menu->cur_item_id;
554 }
555
scene_menu_display(struct scene_obj_menu * menu)556 int scene_menu_display(struct scene_obj_menu *menu)
557 {
558 struct scene *scn = menu->obj.scene;
559 struct scene_obj_txt *pointer;
560 struct expo *exp = scn->expo;
561 struct scene_menitem *item;
562 const char *pstr;
563
564 printf("U-Boot : Boot Menu\n\n");
565 if (menu->title_id) {
566 struct scene_obj_txt *txt;
567 const char *str;
568
569 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
570 if (!txt)
571 return log_msg_ret("txt", -EINVAL);
572
573 str = expo_get_str(exp, txt->gen.str_id);
574 printf("%s\n\n", str ? str : "");
575 }
576
577 if (list_empty(&menu->item_head))
578 return 0;
579
580 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
581 if (pointer)
582 pstr = expo_get_str(scn->expo, pointer->gen.str_id);
583
584 list_for_each_entry(item, &menu->item_head, sibling) {
585 struct scene_obj_txt *key = NULL, *label = NULL;
586 struct scene_obj_txt *desc = NULL;
587 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
588
589 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
590 if (key)
591 kstr = expo_get_str(exp, key->gen.str_id);
592
593 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
594 if (label)
595 lstr = expo_get_str(exp, label->gen.str_id);
596
597 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
598 if (desc)
599 dstr = expo_get_str(exp, desc->gen.str_id);
600
601 printf("%3s %3s %-10s %s\n",
602 pointer && menu->cur_item_id == item->id ? pstr : "",
603 kstr, lstr, dstr);
604 }
605
606 return -ENOTSUPP;
607 }
608
scene_menu_render_deps(struct scene * scn,struct scene_obj_menu * menu)609 int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
610 {
611 struct scene_menitem *item;
612
613 scene_render_deps(scn, menu->title_id);
614 scene_render_deps(scn, menu->cur_item_id);
615 scene_render_deps(scn, menu->pointer_id);
616
617 list_for_each_entry(item, &menu->item_head, sibling) {
618 scene_render_deps(scn, item->key_id);
619 scene_render_deps(scn, item->label_id);
620 scene_render_deps(scn, item->desc_id);
621 }
622
623 return 0;
624 }
625