blob: eed7565f6a617d34e242c2144b1fc5740096c7c6 [file] [log] [blame]
Simon Glass9f513932023-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 Glassfe4c1e22023-06-01 10:22:43 -06009#define LOG_CATEGORY LOGC_EXPO
Simon Glass9f513932023-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
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
Simon Glass7b043952023-06-01 10:22:49 -060052 y = menu->obj.dim.y;
Simon Glass9f513932023-01-06 08:52:38 -060053 if (menu->title_id) {
Simon Glass7b043952023-06-01 10:22:49 -060054 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y);
Simon Glass9f513932023-01-06 08:52:38 -060055 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) {
Simon Glass7b043952023-06-01 10:22:49 -060092 ret = scene_obj_set_pos(scn, item->label_id, menu->obj.dim.x,
Simon Glass9f513932023-01-06 08:52:38 -060093 y);
94 if (ret < 0)
95 return log_msg_ret("nam", ret);
96 }
97
Simon Glass7b043952023-06-01 10:22:49 -060098 ret = scene_obj_set_pos(scn, item->key_id, menu->obj.dim.x + 230,
Simon Glass9f513932023-01-06 08:52:38 -060099 y);
100 if (ret < 0)
101 return log_msg_ret("key", ret);
102
Simon Glass7b043952023-06-01 10:22:49 -0600103 ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.dim.x + 280,
Simon Glass9f513932023-01-06 08:52:38 -0600104 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,
Simon Glass7b043952023-06-01 10:22:49 -0600137 menu->obj.dim.x + 200, cur_y);
Simon Glass9f513932023-01-06 08:52:38 -0600138 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
Simon Glass9f513932023-01-06 08:52:38 -0600161 return menu->obj.id;
162}
163
164static struct scene_menitem *scene_menu_find_key(struct scene *scn,
165 struct scene_obj_menu *menu,
166 int key)
167{
168 struct scene_menitem *item;
169
170 list_for_each_entry(item, &menu->item_head, sibling) {
171 if (item->key_id) {
172 struct scene_obj_txt *txt;
173 const char *str;
174
175 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
176 if (txt) {
177 str = expo_get_str(scn->expo, txt->str_id);
178 if (str && *str == key)
179 return item;
180 }
181 }
182 }
183
184 return NULL;
185}
186
187int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
188 struct expo_action *event)
189{
190 struct scene_menitem *item, *cur, *key_item;
191
192 cur = NULL;
193 key_item = NULL;
194
195 if (!list_empty(&menu->item_head)) {
196 list_for_each_entry(item, &menu->item_head, sibling) {
197 /* select an item if not done already */
198 if (menu->cur_item_id == item->id) {
199 cur = item;
200 break;
201 }
202 }
203 }
204
205 if (!cur)
206 return -ENOTTY;
207
208 switch (key) {
209 case BKEY_UP:
210 if (item != list_first_entry(&menu->item_head,
211 struct scene_menitem, sibling)) {
212 item = list_entry(item->sibling.prev,
213 struct scene_menitem, sibling);
214 event->type = EXPOACT_POINT;
215 event->select.id = item->id;
216 log_debug("up to item %d\n", event->select.id);
217 }
218 break;
219 case BKEY_DOWN:
220 if (!list_is_last(&item->sibling, &menu->item_head)) {
221 item = list_entry(item->sibling.next,
222 struct scene_menitem, sibling);
223 event->type = EXPOACT_POINT;
224 event->select.id = item->id;
225 log_debug("down to item %d\n", event->select.id);
226 }
227 break;
228 case BKEY_SELECT:
229 event->type = EXPOACT_SELECT;
230 event->select.id = item->id;
231 log_debug("select item %d\n", event->select.id);
232 break;
233 case BKEY_QUIT:
234 event->type = EXPOACT_QUIT;
235 log_debug("quit\n");
236 break;
237 case '0'...'9':
238 key_item = scene_menu_find_key(scn, menu, key);
239 if (key_item) {
240 event->type = EXPOACT_SELECT;
241 event->select.id = key_item->id;
242 }
243 break;
244 }
245
246 menu_point_to_item(menu, item->id);
247
248 return 0;
249}
250
251int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
252 uint key_id, uint label_id, uint desc_id, uint preview_id,
253 uint flags, struct scene_menitem **itemp)
254{
255 struct scene_obj_menu *menu;
256 struct scene_menitem *item;
Simon Glass9f513932023-01-06 08:52:38 -0600257
258 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
259 if (!menu)
260 return log_msg_ret("find", -ENOENT);
261
262 /* Check that the text ID is valid */
263 if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT))
264 return log_msg_ret("txt", -EINVAL);
265
266 item = calloc(1, sizeof(struct scene_obj_menu));
267 if (!item)
268 return log_msg_ret("item", -ENOMEM);
269 item->name = strdup(name);
270 if (!item->name) {
271 free(item);
272 return log_msg_ret("name", -ENOMEM);
273 }
274
275 item->id = resolve_id(scn->expo, id);
276 item->key_id = key_id;
277 item->label_id = label_id;
278 item->desc_id = desc_id;
279 item->preview_id = preview_id;
280 item->flags = flags;
281 list_add_tail(&item->sibling, &menu->item_head);
282
Simon Glass9f513932023-01-06 08:52:38 -0600283 if (itemp)
284 *itemp = item;
285
286 return item->id;
287}
288
289int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
290{
291 struct scene_obj_menu *menu;
292 struct scene_obj_txt *txt;
293
294 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
295 if (!menu)
296 return log_msg_ret("menu", -ENOENT);
297
298 /* Check that the ID is valid */
299 if (title_id) {
300 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
301 if (!txt)
302 return log_msg_ret("txt", -EINVAL);
303 }
304
305 menu->title_id = title_id;
306
307 return 0;
308}
309
310int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
311{
312 struct scene_obj_menu *menu;
313 struct scene_obj *obj;
314
315 menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
316 if (!menu)
317 return log_msg_ret("menu", -ENOENT);
318
319 /* Check that the ID is valid */
320 if (pointer_id) {
321 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
322 if (!obj)
323 return log_msg_ret("obj", -EINVAL);
324 }
325
326 menu->pointer_id = pointer_id;
327
328 return 0;
329}
330
331int scene_menu_display(struct scene_obj_menu *menu)
332{
333 struct scene *scn = menu->obj.scene;
334 struct scene_obj_txt *pointer;
335 struct expo *exp = scn->expo;
336 struct scene_menitem *item;
337 const char *pstr;
338
339 printf("U-Boot : Boot Menu\n\n");
340 if (menu->title_id) {
341 struct scene_obj_txt *txt;
342 const char *str;
343
344 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
345 if (!txt)
346 return log_msg_ret("txt", -EINVAL);
347
348 str = expo_get_str(exp, txt->str_id);
349 printf("%s\n\n", str);
350 }
351
352 if (list_empty(&menu->item_head))
353 return 0;
354
355 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
356 pstr = expo_get_str(scn->expo, pointer->str_id);
357
358 list_for_each_entry(item, &menu->item_head, sibling) {
359 struct scene_obj_txt *key = NULL, *label = NULL;
360 struct scene_obj_txt *desc = NULL;
361 const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
362
363 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
364 if (key)
365 kstr = expo_get_str(exp, key->str_id);
366
367 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
368 if (label)
369 lstr = expo_get_str(exp, label->str_id);
370
371 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
372 if (desc)
373 dstr = expo_get_str(exp, desc->str_id);
374
375 printf("%3s %3s %-10s %s\n",
376 pointer && menu->cur_item_id == item->id ? pstr : "",
377 kstr, lstr, dstr);
378 }
379
380 return -ENOTSUPP;
381}