Enable network on VF2 board
[platform/kernel/u-boot.git] / boot / scene_menu.c
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 <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
22 static void scene_menuitem_destroy(struct scene_menitem *item)
23 {
24         free(item->name);
25         free(item);
26 }
27
28 void 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
36 static 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  */
56 static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
57 {
58         struct scene *scn = menu->obj.scene;
59         const bool stack = scn->expo->popup;
60         const struct scene_menitem *item;
61         int ret;
62
63         item = scene_menuitem_find(menu, id);
64         if (!item)
65                 return log_msg_ret("itm", -ENOENT);
66
67         /* adjust the pointer object to point to the selected item */
68         if (menu->pointer_id && item && point) {
69                 struct scene_obj *label;
70
71                 label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
72
73                 ret = scene_obj_set_pos(scn, menu->pointer_id,
74                                         menu->obj.dim.x + 200, label->dim.y);
75                 if (ret < 0)
76                         return log_msg_ret("ptr", ret);
77         }
78
79         if (stack) {
80                 point &= scn->highlight_id == menu->obj.id;
81                 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT,
82                                       point ? SCENEOF_POINT : 0);
83         }
84
85         return 0;
86 }
87
88 /**
89  * menu_point_to_item() - Point to a particular menu item
90  *
91  * Sets the currently pointed-to / highlighted menu item
92  */
93 static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
94 {
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);
99 }
100
101 static int scene_bbox_union(struct scene *scn, uint id, int inset,
102                             struct vidconsole_bbox *bbox)
103 {
104         struct scene_obj *obj;
105
106         if (!id)
107                 return 0;
108         obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
109         if (!obj)
110                 return log_msg_ret("obj", -ENOENT);
111         if (bbox->valid) {
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);
116         } else {
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;
121                 bbox->valid = true;
122         }
123
124         return 0;
125 }
126
127 /**
128  * scene_menu_calc_bbox() - Calculate bounding boxes for the menu
129  *
130  * @menu: Menu to process
131  * @bbox: Returns bounding box of menu including prompts
132  * @label_bbox: Returns bounding box of labels
133  */
134 static void scene_menu_calc_bbox(struct scene_obj_menu *menu,
135                                  struct vidconsole_bbox *bbox,
136                                  struct vidconsole_bbox *label_bbox)
137 {
138         const struct expo_theme *theme = &menu->obj.scene->expo->theme;
139         const struct scene_menitem *item;
140
141         bbox->valid = false;
142         scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox);
143
144         label_bbox->valid = false;
145
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);
152
153                 /* Get the bounding box of all labels */
154                 scene_bbox_union(menu->obj.scene, item->label_id,
155                                  theme->menu_inset, label_bbox);
156         }
157
158         /*
159          * subtract the final menuitem's gap to keep the insert the same top
160          * and bottom
161          */
162         label_bbox->y1 -= theme->menuitem_gap_y;
163 }
164
165 int scene_menu_calc_dims(struct scene_obj_menu *menu)
166 {
167         struct vidconsole_bbox bbox, label_bbox;
168         const struct scene_menitem *item;
169
170         scene_menu_calc_bbox(menu, &bbox, &label_bbox);
171
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);
178                 }
179         }
180
181         if (bbox.valid) {
182                 menu->obj.dim.w = bbox.x1 - bbox.x0;
183                 menu->obj.dim.h = bbox.y1 - bbox.y0;
184         }
185
186         return 0;
187 }
188
189 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
190 {
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;
196         uint sel_id;
197         int x, y;
198         int ret;
199
200         x = menu->obj.dim.x;
201         y = menu->obj.dim.y;
202         if (menu->title_id) {
203                 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
204                 if (ret < 0)
205                         return log_msg_ret("tit", ret);
206
207                 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
208                 if (ret < 0)
209                         return log_msg_ret("hei", ret);
210
211                 if (stack)
212                         x += 200;
213                 else
214                         y += ret * 2;
215         }
216
217         /*
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
221          * vidconsole
222          */
223         sel_id = menu->cur_item_id;
224         list_for_each_entry(item, &menu->item_head, sibling) {
225                 bool selected;
226                 int height;
227
228                 ret = scene_obj_get_hw(scn, item->label_id, NULL);
229                 if (ret < 0)
230                         return log_msg_ret("get", ret);
231                 height = ret;
232
233                 if (item->flags & SCENEMIF_GAP_BEFORE)
234                         y += height;
235
236                 /* select an item if not done already */
237                 if (!sel_id)
238                         sel_id = item->id;
239
240                 selected = sel_id == item->id;
241
242                 /*
243                  * Put the label on the left, then leave a space for the
244                  * pointer, then the key and the description
245                  */
246                 ret = scene_obj_set_pos(scn, item->label_id,
247                                         x + theme->menu_inset, y);
248                 if (ret < 0)
249                         return log_msg_ret("nam", ret);
250                 scene_obj_set_hide(scn, item->label_id,
251                                    stack && !open && !selected);
252
253                 if (item->key_id) {
254                         ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
255                         if (ret < 0)
256                                 return log_msg_ret("key", ret);
257                 }
258
259                 if (item->desc_id) {
260                         ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
261                         if (ret < 0)
262                                 return log_msg_ret("des", ret);
263                 }
264
265                 if (item->preview_id) {
266                         bool hide;
267
268                         /*
269                          * put all previews on top of each other, on the right
270                          * size of the display
271                          */
272                         ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
273                         if (ret < 0)
274                                 return log_msg_ret("prev", ret);
275
276                         hide = menu->cur_item_id != item->id;
277                         ret = scene_obj_set_hide(scn, item->preview_id, hide);
278                         if (ret < 0)
279                                 return log_msg_ret("hid", ret);
280                 }
281
282                 if (!stack || open)
283                         y += height + theme->menuitem_gap_y;
284         }
285
286         if (sel_id)
287                 menu_point_to_item(menu, sel_id);
288
289         return 0;
290 }
291
292 int scene_menu(struct scene *scn, const char *name, uint id,
293                struct scene_obj_menu **menup)
294 {
295         struct scene_obj_menu *menu;
296         int ret;
297
298         ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
299                             sizeof(struct scene_obj_menu),
300                             (struct scene_obj **)&menu);
301         if (ret < 0)
302                 return log_msg_ret("obj", -ENOMEM);
303
304         if (menup)
305                 *menup = menu;
306         INIT_LIST_HEAD(&menu->item_head);
307
308         return menu->obj.id;
309 }
310
311 static struct scene_menitem *scene_menu_find_key(struct scene *scn,
312                                                   struct scene_obj_menu *menu,
313                                                   int key)
314 {
315         struct scene_menitem *item;
316
317         list_for_each_entry(item, &menu->item_head, sibling) {
318                 if (item->key_id) {
319                         struct scene_obj_txt *txt;
320                         const char *str;
321
322                         txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
323                         if (txt) {
324                                 str = expo_get_str(scn->expo, txt->str_id);
325                                 if (str && *str == key)
326                                         return item;
327                         }
328                 }
329         }
330
331         return NULL;
332 }
333
334 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
335                         struct expo_action *event)
336 {
337         const bool open = menu->obj.flags & SCENEOF_OPEN;
338         struct scene_menitem *item, *cur, *key_item;
339
340         cur = NULL;
341         key_item = NULL;
342
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) {
347                                 cur = item;
348                                 break;
349                         }
350                 }
351         }
352
353         if (!cur)
354                 return -ENOTTY;
355
356         switch (key) {
357         case BKEY_UP:
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);
365                 }
366                 break;
367         case BKEY_DOWN:
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);
374                 }
375                 break;
376         case BKEY_SELECT:
377                 event->type = EXPOACT_SELECT;
378                 event->select.id = item->id;
379                 log_debug("select item %d\n", event->select.id);
380                 break;
381         case BKEY_QUIT:
382                 if (scn->expo->popup && open) {
383                         event->type = EXPOACT_CLOSE;
384                         event->select.id = menu->obj.id;
385                 } else {
386                         event->type = EXPOACT_QUIT;
387                         log_debug("menu quit\n");
388                 }
389                 break;
390         case '0'...'9':
391                 key_item = scene_menu_find_key(scn, menu, key);
392                 if (key_item) {
393                         event->type = EXPOACT_SELECT;
394                         event->select.id = key_item->id;
395                 }
396                 break;
397         }
398
399         menu_point_to_item(menu, item->id);
400
401         return 0;
402 }
403
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)
407 {
408         struct scene_obj_menu *menu;
409         struct scene_menitem *item;
410
411         menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
412         if (!menu)
413                 return log_msg_ret("find", -ENOENT);
414
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);
418
419         item = calloc(1, sizeof(struct scene_obj_menu));
420         if (!item)
421                 return log_msg_ret("item", -ENOMEM);
422         item->name = strdup(name);
423         if (!item->name) {
424                 free(item);
425                 return log_msg_ret("name", -ENOMEM);
426         }
427
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;
433         item->flags = flags;
434         list_add_tail(&item->sibling, &menu->item_head);
435
436         if (itemp)
437                 *itemp = item;
438
439         return item->id;
440 }
441
442 int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
443 {
444         struct scene_obj_menu *menu;
445         struct scene_obj_txt *txt;
446
447         menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
448         if (!menu)
449                 return log_msg_ret("menu", -ENOENT);
450
451         /* Check that the ID is valid */
452         if (title_id) {
453                 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
454                 if (!txt)
455                         return log_msg_ret("txt", -EINVAL);
456         }
457
458         menu->title_id = title_id;
459
460         return 0;
461 }
462
463 int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
464 {
465         struct scene_obj_menu *menu;
466         struct scene_obj *obj;
467
468         menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
469         if (!menu)
470                 return log_msg_ret("menu", -ENOENT);
471
472         /* Check that the ID is valid */
473         if (pointer_id) {
474                 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
475                 if (!obj)
476                         return log_msg_ret("obj", -EINVAL);
477         }
478
479         menu->pointer_id = pointer_id;
480
481         return 0;
482 }
483
484 int scene_menu_display(struct scene_obj_menu *menu)
485 {
486         struct scene *scn = menu->obj.scene;
487         struct scene_obj_txt *pointer;
488         struct expo *exp = scn->expo;
489         struct scene_menitem *item;
490         const char *pstr;
491
492         printf("U-Boot    :    Boot Menu\n\n");
493         if (menu->title_id) {
494                 struct scene_obj_txt *txt;
495                 const char *str;
496
497                 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
498                 if (!txt)
499                         return log_msg_ret("txt", -EINVAL);
500
501                 str = expo_get_str(exp, txt->str_id);
502                 printf("%s\n\n", str);
503         }
504
505         if (list_empty(&menu->item_head))
506                 return 0;
507
508         pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
509         pstr = expo_get_str(scn->expo, pointer->str_id);
510
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;
515
516                 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
517                 if (key)
518                         kstr = expo_get_str(exp, key->str_id);
519
520                 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
521                 if (label)
522                         lstr = expo_get_str(exp, label->str_id);
523
524                 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
525                 if (desc)
526                         dstr = expo_get_str(exp, desc->str_id);
527
528                 printf("%3s  %3s  %-10s  %s\n",
529                        pointer && menu->cur_item_id == item->id ? pstr : "",
530                        kstr, lstr, dstr);
531         }
532
533         return -ENOTSUPP;
534 }
535
536 void scene_menu_render(struct scene_obj_menu *menu)
537 {
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;
546
547         if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
548                 fore = VID_BLACK;
549                 back = VID_WHITE;
550         } else {
551                 fore = VID_LIGHT_GRAY;
552                 back = VID_BLACK;
553         }
554
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);
563 }
564
565 int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
566 {
567         struct scene_menitem *item;
568
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);
572
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);
577         }
578
579         return 0;
580 }