]>
Commit | Line | Data |
---|---|---|
226777f1 SG |
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 <[email protected]> | |
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 | ||
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 | /** | |
37 | * menu_point_to_item() - Point to a particular menu item | |
38 | * | |
39 | * Sets the currently pointed-to / highlighted menu item | |
40 | */ | |
41 | static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id) | |
42 | { | |
43 | menu->cur_item_id = item_id; | |
44 | } | |
45 | ||
46 | int 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 | ||
145 | int 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 | ||
168 | static 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 | ||
191 | int 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 | ||
255 | int 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 | ||
298 | int 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 | ||
319 | int 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 | ||
340 | int 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 | } |