blob: 154be7f0127f834ad3f0a3536e44069dd6d2d4b3 [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;
59 const struct scene_menitem *item;
60 int ret;
61
62 item = scene_menuitem_find(menu, id);
63 if (!item)
64 return log_msg_ret("itm", -ENOENT);
65
66 /* adjust the pointer object to point to the selected item */
67 if (menu->pointer_id && item && point) {
68 struct scene_obj *label;
69
70 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
71
72 ret = scene_obj_set_pos(scn, menu->pointer_id,
73 menu->obj.dim.x + 200, label->dim.y);
74 if (ret < 0)
75 return log_msg_ret("ptr", ret);
76 }
77
78 return 0;
79}
80
Simon Glass226777f2023-01-06 08:52:38 -060081/**
82 * menu_point_to_item() - Point to a particular menu item
83 *
84 * Sets the currently pointed-to / highlighted menu item
85 */
86static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
87{
Simon Glass8872bc72023-06-01 10:22:54 -060088 if (menu->cur_item_id)
89 update_pointers(menu, menu->cur_item_id, false);
Simon Glass226777f2023-01-06 08:52:38 -060090 menu->cur_item_id = item_id;
Simon Glass8872bc72023-06-01 10:22:54 -060091 update_pointers(menu, item_id, true);
Simon Glass226777f2023-01-06 08:52:38 -060092}
93
Simon Glass699b0ac2023-06-01 10:22:52 -060094static int scene_bbox_union(struct scene *scn, uint id,
95 struct vidconsole_bbox *bbox)
96{
97 struct scene_obj *obj;
98
99 if (!id)
100 return 0;
101 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
102 if (!obj)
103 return log_msg_ret("obj", -ENOENT);
104 if (bbox->valid) {
105 bbox->x0 = min(bbox->x0, obj->dim.x);
106 bbox->y0 = min(bbox->y0, obj->dim.y);
107 bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w);
108 bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
109 } else {
110 bbox->x0 = obj->dim.x;
111 bbox->y0 = obj->dim.y;
112 bbox->x1 = obj->dim.x + obj->dim.w;
113 bbox->y1 = obj->dim.y + obj->dim.h;
114 bbox->valid = true;
115 }
116
117 return 0;
118}
119
120/**
121 * scene_menu_calc_bbox() - Calculate bounding boxes for the menu
122 *
123 * @menu: Menu to process
124 * @bbox: Returns bounding box of menu including prompts
125 * @label_bbox: Returns bounding box of labels
126 */
127static void scene_menu_calc_bbox(struct scene_obj_menu *menu,
128 struct vidconsole_bbox *bbox,
129 struct vidconsole_bbox *label_bbox)
130{
131 const struct scene_menitem *item;
132
133 bbox->valid = false;
134 scene_bbox_union(menu->obj.scene, menu->title_id, bbox);
135
136 label_bbox->valid = false;
137
138 list_for_each_entry(item, &menu->item_head, sibling) {
139 scene_bbox_union(menu->obj.scene, item->label_id, bbox);
140 scene_bbox_union(menu->obj.scene, item->key_id, bbox);
141 scene_bbox_union(menu->obj.scene, item->desc_id, bbox);
142 scene_bbox_union(menu->obj.scene, item->preview_id, bbox);
143
144 /* Get the bounding box of all labels */
145 scene_bbox_union(menu->obj.scene, item->label_id, label_bbox);
146 }
147}
148
149int scene_menu_calc_dims(struct scene_obj_menu *menu)
150{
151 struct vidconsole_bbox bbox, label_bbox;
152 const struct scene_menitem *item;
153
154 scene_menu_calc_bbox(menu, &bbox, &label_bbox);
155
156 /* Make all labels the same size */
157 if (label_bbox.valid) {
158 list_for_each_entry(item, &menu->item_head, sibling) {
159 scene_obj_set_size(menu->obj.scene, item->label_id,
160 label_bbox.x1 - label_bbox.x0,
161 label_bbox.y1 - label_bbox.y0);
162 }
163 }
164
165 if (bbox.valid) {
166 menu->obj.dim.w = bbox.x1 - bbox.x0;
167 menu->obj.dim.h = bbox.y1 - bbox.y0;
168 }
169
170 return 0;
171}
172
Simon Glass226777f2023-01-06 08:52:38 -0600173int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
174{
175 struct scene_menitem *item;
Simon Glass8872bc72023-06-01 10:22:54 -0600176 uint sel_id;
Simon Glass226777f2023-01-06 08:52:38 -0600177 int y, cur_y;
178 int ret;
179
Simon Glassae45d6c2023-06-01 10:22:49 -0600180 y = menu->obj.dim.y;
Simon Glass226777f2023-01-06 08:52:38 -0600181 if (menu->title_id) {
Simon Glassae45d6c2023-06-01 10:22:49 -0600182 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
Simon Glass226777f2023-01-06 08:52:38 -0600183 if (ret < 0)
184 return log_msg_ret("tit", ret);
185
186 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
187 if (ret < 0)
188 return log_msg_ret("hei", ret);
189
190 y += ret * 2;
191 }
192
193 /*
194 * Currently everything is hard-coded to particular columns so this
195 * won't work on small displays and looks strange if the font size is
196 * small. This can be updated once text measuring is supported in
197 * vidconsole
198 */
Simon Glass8872bc72023-06-01 10:22:54 -0600199 sel_id = menu->cur_item_id;
Simon Glass226777f2023-01-06 08:52:38 -0600200 list_for_each_entry(item, &menu->item_head, sibling) {
201 int height;
202
203 ret = scene_obj_get_hw(scn, item->desc_id, NULL);
204 if (ret < 0)
205 return log_msg_ret("get", ret);
206 height = ret;
207
208 if (item->flags & SCENEMIF_GAP_BEFORE)
209 y += height;
210
211 /* select an item if not done already */
Simon Glass8872bc72023-06-01 10:22:54 -0600212 if (!sel_id)
213 sel_id = item->id;
Simon Glass226777f2023-01-06 08:52:38 -0600214
215 /*
216 * Put the label on the left, then leave a space for the
217 * pointer, then the key and the description
218 */
219 if (item->label_id) {
Simon Glassae45d6c2023-06-01 10:22:49 -0600220 ret = scene_obj_set_pos(scn, item->label_id, menu->obj.dim.x,
Simon Glass226777f2023-01-06 08:52:38 -0600221 y);
222 if (ret < 0)
223 return log_msg_ret("nam", ret);
224 }
225
Simon Glassae45d6c2023-06-01 10:22:49 -0600226 ret = scene_obj_set_pos(scn, item->key_id, menu->obj.dim.x + 230,
Simon Glass226777f2023-01-06 08:52:38 -0600227 y);
228 if (ret < 0)
229 return log_msg_ret("key", ret);
230
Simon Glassae45d6c2023-06-01 10:22:49 -0600231 ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.dim.x + 280,
Simon Glass226777f2023-01-06 08:52:38 -0600232 y);
233 if (ret < 0)
234 return log_msg_ret("des", ret);
235
236 if (menu->cur_item_id == item->id)
237 cur_y = y;
238
239 if (item->preview_id) {
240 bool hide;
241
242 /*
243 * put all previews on top of each other, on the right
244 * size of the display
245 */
246 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
247 if (ret < 0)
248 return log_msg_ret("prev", ret);
249
250 hide = menu->cur_item_id != item->id;
251 ret = scene_obj_set_hide(scn, item->preview_id, hide);
252 if (ret < 0)
253 return log_msg_ret("hid", ret);
254 }
255
256 y += height;
257 }
258
Simon Glass8872bc72023-06-01 10:22:54 -0600259 if (sel_id)
260 menu_point_to_item(menu, sel_id);
Simon Glass226777f2023-01-06 08:52:38 -0600261
262 return 0;
263}
264
265int scene_menu(struct scene *scn, const char *name, uint id,
266 struct scene_obj_menu **menup)
267{
268 struct scene_obj_menu *menu;
269 int ret;
270
271 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
272 sizeof(struct scene_obj_menu),
273 (struct scene_obj **)&menu);
274 if (ret < 0)
275 return log_msg_ret("obj", -ENOMEM);
276
277 if (menup)
278 *menup = menu;
279 INIT_LIST_HEAD(&menu->item_head);
280
Simon Glass226777f2023-01-06 08:52:38 -0600281 return menu->obj.id;
282}
283
284static struct scene_menitem *scene_menu_find_key(struct scene *scn,
285 struct scene_obj_menu *menu,
286 int key)
287{
288 struct scene_menitem *item;
289
290 list_for_each_entry(item, &menu->item_head, sibling) {
291 if (item->key_id) {
292 struct scene_obj_txt *txt;
293 const char *str;
294
295 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
296 if (txt) {
297 str = expo_get_str(scn->expo, txt->str_id);
298 if (str && *str == key)
299 return item;
300 }
301 }
302 }
303
304 return NULL;
305}
306
307int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
308 struct expo_action *event)
309{
310 struct scene_menitem *item, *cur, *key_item;
311
312 cur = NULL;
313 key_item = NULL;
314
315 if (!list_empty(&menu->item_head)) {
316 list_for_each_entry(item, &menu->item_head, sibling) {
317 /* select an item if not done already */
318 if (menu->cur_item_id == item->id) {
319 cur = item;
320 break;
321 }
322 }
323 }
324
325 if (!cur)
326 return -ENOTTY;
327
328 switch (key) {
329 case BKEY_UP:
330 if (item != list_first_entry(&menu->item_head,
331 struct scene_menitem, sibling)) {
332 item = list_entry(item->sibling.prev,
333 struct scene_menitem, sibling);
334 event->type = EXPOACT_POINT;
335 event->select.id = item->id;
336 log_debug("up to item %d\n", event->select.id);
337 }
338 break;
339 case BKEY_DOWN:
340 if (!list_is_last(&item->sibling, &menu->item_head)) {
341 item = list_entry(item->sibling.next,
342 struct scene_menitem, sibling);
343 event->type = EXPOACT_POINT;
344 event->select.id = item->id;
345 log_debug("down to item %d\n", event->select.id);
346 }
347 break;
348 case BKEY_SELECT:
349 event->type = EXPOACT_SELECT;
350 event->select.id = item->id;
351 log_debug("select item %d\n", event->select.id);
352 break;
353 case BKEY_QUIT:
354 event->type = EXPOACT_QUIT;
355 log_debug("quit\n");
356 break;
357 case '0'...'9':
358 key_item = scene_menu_find_key(scn, menu, key);
359 if (key_item) {
360 event->type = EXPOACT_SELECT;
361 event->select.id = key_item->id;
362 }
363 break;
364 }
365
366 menu_point_to_item(menu, item->id);
367
368 return 0;
369}
370
371int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
372 uint key_id, uint label_id, uint desc_id, uint preview_id,
373 uint flags, struct scene_menitem **itemp)
374{
375 struct scene_obj_menu *menu;
376 struct scene_menitem *item;
Simon Glass226777f2023-01-06 08:52:38 -0600377
378 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
379 if (!menu)
380 return log_msg_ret("find", -ENOENT);
381
382 /* Check that the text ID is valid */
383 if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
384 return log_msg_ret("txt", -EINVAL);
385
386 item = calloc(1, sizeof(struct scene_obj_menu));
387 if (!item)
388 return log_msg_ret("item", -ENOMEM);
389 item->name = strdup(name);
390 if (!item->name) {
391 free(item);
392 return log_msg_ret("name", -ENOMEM);
393 }
394
395 item->id = resolve_id(scn->expo, id);
396 item->key_id = key_id;
397 item->label_id = label_id;
398 item->desc_id = desc_id;
399 item->preview_id = preview_id;
400 item->flags = flags;
401 list_add_tail(&item->sibling, &menu->item_head);
402
Simon Glass226777f2023-01-06 08:52:38 -0600403 if (itemp)
404 *itemp = item;
405
406 return item->id;
407}
408
409int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
410{
411 struct scene_obj_menu *menu;
412 struct scene_obj_txt *txt;
413
414 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
415 if (!menu)
416 return log_msg_ret("menu", -ENOENT);
417
418 /* Check that the ID is valid */
419 if (title_id) {
420 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
421 if (!txt)
422 return log_msg_ret("txt", -EINVAL);
423 }
424
425 menu->title_id = title_id;
426
427 return 0;
428}
429
430int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
431{
432 struct scene_obj_menu *menu;
433 struct scene_obj *obj;
434
435 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
436 if (!menu)
437 return log_msg_ret("menu", -ENOENT);
438
439 /* Check that the ID is valid */
440 if (pointer_id) {
441 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
442 if (!obj)
443 return log_msg_ret("obj", -EINVAL);
444 }
445
446 menu->pointer_id = pointer_id;
447
448 return 0;
449}
450
451int scene_menu_display(struct scene_obj_menu *menu)
452{
453 struct scene *scn = menu->obj.scene;
454 struct scene_obj_txt *pointer;
455 struct expo *exp = scn->expo;
456 struct scene_menitem *item;
457 const char *pstr;
458
459 printf("U-Boot : Boot Menu\n\n");
460 if (menu->title_id) {
461 struct scene_obj_txt *txt;
462 const char *str;
463
464 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
465 if (!txt)
466 return log_msg_ret("txt", -EINVAL);
467
468 str = expo_get_str(exp, txt->str_id);
469 printf("%s\n\n", str);
470 }
471
472 if (list_empty(&menu->item_head))
473 return 0;
474
475 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
476 pstr = expo_get_str(scn->expo, pointer->str_id);
477
478 list_for_each_entry(item, &menu->item_head, sibling) {
479 struct scene_obj_txt *key = NULL, *label = NULL;
480 struct scene_obj_txt *desc = NULL;
481 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
482
483 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
484 if (key)
485 kstr = expo_get_str(exp, key->str_id);
486
487 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
488 if (label)
489 lstr = expo_get_str(exp, label->str_id);
490
491 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
492 if (desc)
493 dstr = expo_get_str(exp, desc->str_id);
494
495 printf("%3s %3s %-10s %s\n",
496 pointer && menu->cur_item_id == item->id ? pstr : "",
497 kstr, lstr, dstr);
498 }
499
500 return -ENOTSUPP;
501}