blob: 18998e862ab3b75790581a22b17d00662e07dd52 [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
9#define LOG_CATEGORY LOGC_BOOT
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
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
36/**
37 * menu_point_to_item() - Point to a particular menu item
38 *
39 * Sets the currently pointed-to / highlighted menu item
40 */
41static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
42{
43 menu->cur_item_id = item_id;
44}
45
46int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
47{
48 struct scene_menitem *item;
49 int y, cur_y;
50 int ret;
51
52 y = menu->obj.y;
53 if (menu->title_id) {
54 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y);
55 if (ret < 0)
56 return log_msg_ret("tit", ret);
57
58 ret = scene_obj_get_hw(scn, menu->title_id, NULL);
59 if (ret < 0)
60 return log_msg_ret("hei", ret);
61
62 y += ret * 2;
63 }
64
65 /*
66 * Currently everything is hard-coded to particular columns so this
67 * won't work on small displays and looks strange if the font size is
68 * small. This can be updated once text measuring is supported in
69 * vidconsole
70 */
71 cur_y = -1;
72 list_for_each_entry(item, &menu->item_head, sibling) {
73 int height;
74
75 ret = scene_obj_get_hw(scn, item->desc_id, NULL);
76 if (ret < 0)
77 return log_msg_ret("get", ret);
78 height = ret;
79
80 if (item->flags & SCENEMIF_GAP_BEFORE)
81 y += height;
82
83 /* select an item if not done already */
84 if (!menu->cur_item_id)
85 menu_point_to_item(menu, item->id);
86
87 /*
88 * Put the label on the left, then leave a space for the
89 * pointer, then the key and the description
90 */
91 if (item->label_id) {
92 ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x,
93 y);
94 if (ret < 0)
95 return log_msg_ret("nam", ret);
96 }
97
98 ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230,
99 y);
100 if (ret < 0)
101 return log_msg_ret("key", ret);
102
103 ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280,
104 y);
105 if (ret < 0)
106 return log_msg_ret("des", ret);
107
108 if (menu->cur_item_id == item->id)
109 cur_y = y;
110
111 if (item->preview_id) {
112 bool hide;
113
114 /*
115 * put all previews on top of each other, on the right
116 * size of the display
117 */
118 ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
119 if (ret < 0)
120 return log_msg_ret("prev", ret);
121
122 hide = menu->cur_item_id != item->id;
123 ret = scene_obj_set_hide(scn, item->preview_id, hide);
124 if (ret < 0)
125 return log_msg_ret("hid", ret);
126 }
127
128 y += height;
129 }
130
131 if (menu->pointer_id && cur_y != -1) {
132 /*
133 * put the pointer to the right of and level with the item it
134 * points to
135 */
136 ret = scene_obj_set_pos(scn, menu->pointer_id,
137 menu->obj.x + 200, cur_y);
138 if (ret < 0)
139 return log_msg_ret("ptr", ret);
140 }
141
142 return 0;
143}
144
145int scene_menu(struct scene *scn, const char *name, uint id,
146 struct scene_obj_menu **menup)
147{
148 struct scene_obj_menu *menu;
149 int ret;
150
151 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
152 sizeof(struct scene_obj_menu),
153 (struct scene_obj **)&menu);
154 if (ret < 0)
155 return log_msg_ret("obj", -ENOMEM);
156
157 if (menup)
158 *menup = menu;
159 INIT_LIST_HEAD(&menu->item_head);
160
161 ret = scene_menu_arrange(scn, menu);
162 if (ret)
163 return log_msg_ret("pos", ret);
164
165 return menu->obj.id;
166}
167
168static struct scene_menitem *scene_menu_find_key(struct scene *scn,
169 struct scene_obj_menu *menu,
170 int key)
171{
172 struct scene_menitem *item;
173
174 list_for_each_entry(item, &menu->item_head, sibling) {
175 if (item->key_id) {
176 struct scene_obj_txt *txt;
177 const char *str;
178
179 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
180 if (txt) {
181 str = expo_get_str(scn->expo, txt->str_id);
182 if (str && *str == key)
183 return item;
184 }
185 }
186 }
187
188 return NULL;
189}
190
191int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
192 struct expo_action *event)
193{
194 struct scene_menitem *item, *cur, *key_item;
195
196 cur = NULL;
197 key_item = NULL;
198
199 if (!list_empty(&menu->item_head)) {
200 list_for_each_entry(item, &menu->item_head, sibling) {
201 /* select an item if not done already */
202 if (menu->cur_item_id == item->id) {
203 cur = item;
204 break;
205 }
206 }
207 }
208
209 if (!cur)
210 return -ENOTTY;
211
212 switch (key) {
213 case BKEY_UP:
214 if (item != list_first_entry(&menu->item_head,
215 struct scene_menitem, sibling)) {
216 item = list_entry(item->sibling.prev,
217 struct scene_menitem, sibling);
218 event->type = EXPOACT_POINT;
219 event->select.id = item->id;
220 log_debug("up to item %d\n", event->select.id);
221 }
222 break;
223 case BKEY_DOWN:
224 if (!list_is_last(&item->sibling, &menu->item_head)) {
225 item = list_entry(item->sibling.next,
226 struct scene_menitem, sibling);
227 event->type = EXPOACT_POINT;
228 event->select.id = item->id;
229 log_debug("down to item %d\n", event->select.id);
230 }
231 break;
232 case BKEY_SELECT:
233 event->type = EXPOACT_SELECT;
234 event->select.id = item->id;
235 log_debug("select item %d\n", event->select.id);
236 break;
237 case BKEY_QUIT:
238 event->type = EXPOACT_QUIT;
239 log_debug("quit\n");
240 break;
241 case '0'...'9':
242 key_item = scene_menu_find_key(scn, menu, key);
243 if (key_item) {
244 event->type = EXPOACT_SELECT;
245 event->select.id = key_item->id;
246 }
247 break;
248 }
249
250 menu_point_to_item(menu, item->id);
251
252 return 0;
253}
254
255int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
256 uint key_id, uint label_id, uint desc_id, uint preview_id,
257 uint flags, struct scene_menitem **itemp)
258{
259 struct scene_obj_menu *menu;
260 struct scene_menitem *item;
261 int ret;
262
263 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
264 if (!menu)
265 return log_msg_ret("find", -ENOENT);
266
267 /* Check that the text ID is valid */
268 if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
269 return log_msg_ret("txt", -EINVAL);
270
271 item = calloc(1, sizeof(struct scene_obj_menu));
272 if (!item)
273 return log_msg_ret("item", -ENOMEM);
274 item->name = strdup(name);
275 if (!item->name) {
276 free(item);
277 return log_msg_ret("name", -ENOMEM);
278 }
279
280 item->id = resolve_id(scn->expo, id);
281 item->key_id = key_id;
282 item->label_id = label_id;
283 item->desc_id = desc_id;
284 item->preview_id = preview_id;
285 item->flags = flags;
286 list_add_tail(&item->sibling, &menu->item_head);
287
288 ret = scene_menu_arrange(scn, menu);
289 if (ret)
290 return log_msg_ret("pos", ret);
291
292 if (itemp)
293 *itemp = item;
294
295 return item->id;
296}
297
298int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
299{
300 struct scene_obj_menu *menu;
301 struct scene_obj_txt *txt;
302
303 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
304 if (!menu)
305 return log_msg_ret("menu", -ENOENT);
306
307 /* Check that the ID is valid */
308 if (title_id) {
309 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
310 if (!txt)
311 return log_msg_ret("txt", -EINVAL);
312 }
313
314 menu->title_id = title_id;
315
316 return 0;
317}
318
319int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
320{
321 struct scene_obj_menu *menu;
322 struct scene_obj *obj;
323
324 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
325 if (!menu)
326 return log_msg_ret("menu", -ENOENT);
327
328 /* Check that the ID is valid */
329 if (pointer_id) {
330 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
331 if (!obj)
332 return log_msg_ret("obj", -EINVAL);
333 }
334
335 menu->pointer_id = pointer_id;
336
337 return 0;
338}
339
340int scene_menu_display(struct scene_obj_menu *menu)
341{
342 struct scene *scn = menu->obj.scene;
343 struct scene_obj_txt *pointer;
344 struct expo *exp = scn->expo;
345 struct scene_menitem *item;
346 const char *pstr;
347
348 printf("U-Boot : Boot Menu\n\n");
349 if (menu->title_id) {
350 struct scene_obj_txt *txt;
351 const char *str;
352
353 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
354 if (!txt)
355 return log_msg_ret("txt", -EINVAL);
356
357 str = expo_get_str(exp, txt->str_id);
358 printf("%s\n\n", str);
359 }
360
361 if (list_empty(&menu->item_head))
362 return 0;
363
364 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
365 pstr = expo_get_str(scn->expo, pointer->str_id);
366
367 list_for_each_entry(item, &menu->item_head, sibling) {
368 struct scene_obj_txt *key = NULL, *label = NULL;
369 struct scene_obj_txt *desc = NULL;
370 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
371
372 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
373 if (key)
374 kstr = expo_get_str(exp, key->str_id);
375
376 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
377 if (label)
378 lstr = expo_get_str(exp, label->str_id);
379
380 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
381 if (desc)
382 dstr = expo_get_str(exp, desc->str_id);
383
384 printf("%3s %3s %-10s %s\n",
385 pointer && menu->cur_item_id == item->id ? pstr : "",
386 kstr, lstr, dstr);
387 }
388
389 return -ENOTSUPP;
390}