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 <dm.h>
12 #include <expo.h>
13 #include <malloc.h>
14 #include <mapmem.h>
15 #include <menu.h>
16 #include <video.h>
17 #include <video_console.h>
18 #include <linux/input.h>
19 #include "scene_internal.h"
20 
scene_menuitem_destroy(struct scene_menitem * item)21 static void scene_menuitem_destroy(struct scene_menitem *item)
22 {
23 	free(item->name);
24 	free(item);
25 }
26 
scene_menu_destroy(struct scene_obj_menu * menu)27 void scene_menu_destroy(struct scene_obj_menu *menu)
28 {
29 	struct scene_menitem *item, *next;
30 
31 	list_for_each_entry_safe(item, next, &menu->item_head, sibling)
32 		scene_menuitem_destroy(item);
33 }
34 
scene_menuitem_find(const struct scene_obj_menu * menu,int id)35 struct scene_menitem *scene_menuitem_find(const struct scene_obj_menu *menu,
36 					  int id)
37 {
38 	struct scene_menitem *item;
39 
40 	list_for_each_entry(item, &menu->item_head, sibling) {
41 		if (item->id == id)
42 			return item;
43 	}
44 
45 	return NULL;
46 }
47 
scene_menuitem_find_seq(const struct scene_obj_menu * menu,uint seq)48 struct scene_menitem *scene_menuitem_find_seq(const struct scene_obj_menu *menu,
49 					      uint seq)
50 {
51 	struct scene_menitem *item;
52 	uint i;
53 
54 	i = 0;
55 	list_for_each_entry(item, &menu->item_head, sibling) {
56 		if (i == seq)
57 			return item;
58 		i++;
59 	}
60 
61 	return NULL;
62 }
63 
scene_menuitem_find_val(const struct scene_obj_menu * menu,int val)64 struct scene_menitem *scene_menuitem_find_val(const struct scene_obj_menu *menu,
65 					      int val)
66 {
67 	struct scene_menitem *item;
68 	uint i;
69 
70 	i = 0;
71 	list_for_each_entry(item, &menu->item_head, sibling) {
72 		if (item->value == INT_MAX ? val == i : item->value == val)
73 			return item;
74 		i++;
75 	}
76 
77 	return NULL;
78 }
79 
80 /**
81  * update_pointers() - Update the pointer object and handle highlights
82  *
83  * @menu: Menu to update
84  * @id: ID of menu item to select/deselect
85  * @point: true if @id is being selected, false if it is being deselected
86  */
update_pointers(struct scene_obj_menu * menu,uint id,bool point)87 static int update_pointers(struct scene_obj_menu *menu, uint id, bool point)
88 {
89 	struct scene *scn = menu->obj.scene;
90 	const bool stack = scn->expo->show_highlight;
91 	const struct scene_menitem *item;
92 	int ret;
93 
94 	item = scene_menuitem_find(menu, id);
95 	if (!item)
96 		return log_msg_ret("itm", -ENOENT);
97 
98 	/* adjust the pointer object to point to the selected item */
99 	if (menu->pointer_id && item && point) {
100 		struct scene_obj *label;
101 
102 		label = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
103 
104 		ret = scene_obj_set_pos(scn, menu->pointer_id,
105 					menu->obj.bbox.x0 + 200, label->bbox.y0);
106 		if (ret < 0)
107 			return log_msg_ret("ptr", ret);
108 	}
109 
110 	if (stack) {
111 		uint id;
112 		int val;
113 
114 		point &= scn->highlight_id == menu->obj.id;
115 		val = point ? SCENEOF_POINT : 0;
116 		id = item->desc_id;
117 		if (!id)
118 			id = item->label_id;
119 		if (!id)
120 			id = item->key_id;
121 		scene_obj_flag_clrset(scn, id, SCENEOF_POINT, val);
122 	}
123 
124 	return 0;
125 }
126 
127 /**
128  * menu_point_to_item() - Point to a particular menu item
129  *
130  * Sets the currently pointed-to / highlighted menu item
131  */
menu_point_to_item(struct scene_obj_menu * menu,uint item_id)132 static int menu_point_to_item(struct scene_obj_menu *menu, uint item_id)
133 {
134 	int ret;
135 
136 	if (menu->cur_item_id) {
137 		ret = update_pointers(menu, menu->cur_item_id, false);
138 		if (ret)
139 			return log_msg_ret("mpi", ret);
140 	}
141 	menu->cur_item_id = item_id;
142 	ret = update_pointers(menu, item_id, true);
143 	if (ret)
144 		return log_msg_ret("mpu", ret);
145 
146 	return 0;
147 }
148 
scene_menu_calc_bbox(struct scene_obj_menu * menu,struct vidconsole_bbox * bbox)149 void scene_menu_calc_bbox(struct scene_obj_menu *menu,
150 			  struct vidconsole_bbox *bbox)
151 {
152 	const struct expo_theme *theme = &menu->obj.scene->expo->theme;
153 	const struct scene_menitem *item;
154 	int inset = theme->menu_inset;
155 	int i;
156 
157 	for (i = 0; i < SCENEBB_count; i++)
158 		bbox[i].valid = false;
159 
160 	scene_bbox_union(menu->obj.scene, menu->title_id, 0,
161 			 &bbox[SCENEBB_all]);
162 
163 	list_for_each_entry(item, &menu->item_head, sibling) {
164 		struct vidconsole_bbox local;
165 
166 		local.valid = false;
167 		scene_bbox_union(menu->obj.scene, item->label_id, inset,
168 				 &local);
169 		scene_bbox_union(menu->obj.scene, item->key_id, 0, &local);
170 		scene_bbox_union(menu->obj.scene, item->desc_id, 0, &local);
171 		scene_bbox_union(menu->obj.scene, item->preview_id, 0, &local);
172 
173 		scene_bbox_join(&local, 0, &bbox[SCENEBB_all]);
174 
175 		/* Get the bounding box of all individual fields */
176 		scene_bbox_union(menu->obj.scene, item->label_id, inset,
177 				 &bbox[SCENEBB_label]);
178 		scene_bbox_union(menu->obj.scene, item->key_id, inset,
179 				 &bbox[SCENEBB_key]);
180 		scene_bbox_union(menu->obj.scene, item->desc_id, inset,
181 				 &bbox[SCENEBB_desc]);
182 
183 		if (menu->cur_item_id == item->id)
184 			scene_bbox_join(&local, 0, &bbox[SCENEBB_curitem]);
185 	}
186 
187 	/*
188 	 * subtract the final menuitem's gap to keep the inset the same top and
189 	 * bottom
190 	 */
191 	bbox[SCENEBB_label].y1 -= theme->menuitem_gap_y;
192 }
193 
scene_menu_calc_dims(struct scene_obj_menu * menu)194 int scene_menu_calc_dims(struct scene_obj_menu *menu)
195 {
196 	struct vidconsole_bbox bbox[SCENEBB_count], *cur;
197 	const struct scene_menitem *item;
198 
199 	scene_menu_calc_bbox(menu, bbox);
200 
201 	/* Make all field types the same width */
202 	list_for_each_entry(item, &menu->item_head, sibling) {
203 		cur = &bbox[SCENEBB_label];
204 		if (cur->valid)
205 			scene_obj_set_width(menu->obj.scene, item->label_id,
206 					    cur->x1 - cur->x0);
207 		cur = &bbox[SCENEBB_key];
208 		if (cur->valid)
209 			scene_obj_set_width(menu->obj.scene, item->key_id,
210 					    cur->x1 - cur->x0);
211 		cur = &bbox[SCENEBB_desc];
212 		if (cur->valid)
213 			scene_obj_set_width(menu->obj.scene, item->desc_id,
214 					    cur->x1 - cur->x0);
215 	}
216 
217 	cur = &bbox[SCENEBB_all];
218 	if (cur->valid) {
219 		menu->obj.dims.x = cur->x1 - cur->x0;
220 		menu->obj.dims.y = cur->y1 - cur->y0;
221 
222 		menu->obj.bbox.x1 = cur->x1;
223 		menu->obj.bbox.y1 = cur->y1;
224 	}
225 
226 	return 0;
227 }
228 
scene_menu_arrange(struct scene * scn,struct expo_arrange_info * arr,struct scene_obj_menu * menu)229 int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr,
230 		       struct scene_obj_menu *menu)
231 {
232 	const bool open = menu->obj.flags & SCENEOF_OPEN;
233 	struct expo *exp = scn->expo;
234 	const bool stack = exp->popup;
235 	const struct expo_theme *theme = &exp->theme;
236 	struct scene_menitem *item;
237 	uint sel_id;
238 	int x, y;
239 	int ret;
240 
241 	x = menu->obj.bbox.x0;
242 	y = menu->obj.bbox.y0;
243 	if (menu->title_id) {
244 		int width;
245 
246 		ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.bbox.x0, y);
247 		if (ret < 0)
248 			return log_msg_ret("tit", ret);
249 
250 		ret = scene_obj_get_hw(scn, menu->title_id, &width);
251 		if (ret < 0)
252 			return log_msg_ret("hei", ret);
253 
254 		if (stack)
255 			x += arr->label_width + theme->menu_title_margin_x;
256 		else
257 			y += ret * 2;
258 	}
259 
260 	/*
261 	 * Currently everything is hard-coded to particular columns so this
262 	 * won't work on small displays and looks strange if the font size is
263 	 * small. This can be updated once text measuring is supported in
264 	 * vidconsole
265 	 */
266 	sel_id = menu->cur_item_id;
267 	list_for_each_entry(item, &menu->item_head, sibling) {
268 		bool selected;
269 		int height;
270 
271 		ret = scene_obj_get_hw(scn, item->label_id, NULL);
272 		if (ret < 0)
273 			return log_msg_ret("get", ret);
274 		height = ret;
275 
276 		if (item->flags & SCENEMIF_GAP_BEFORE)
277 			y += height;
278 
279 		/* select an item if not done already */
280 		if (!sel_id)
281 			sel_id = item->id;
282 
283 		selected = sel_id == item->id;
284 
285 		/*
286 		 * Put the label on the left, then leave a space for the
287 		 * pointer, then the key and the description
288 		 */
289 		ret = scene_obj_set_pos(scn, item->label_id,
290 					x + theme->menu_inset, y);
291 		if (ret < 0)
292 			return log_msg_ret("nam", ret);
293 		scene_obj_set_hide(scn, item->label_id,
294 				   stack && !open && !selected);
295 
296 		if (item->key_id) {
297 			ret = scene_obj_set_pos(scn, item->key_id, x + 230, y);
298 			if (ret < 0)
299 				return log_msg_ret("key", ret);
300 		}
301 
302 		if (item->desc_id) {
303 			ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y);
304 			if (ret < 0)
305 				return log_msg_ret("des", ret);
306 		}
307 
308 		if (item->preview_id) {
309 			bool hide;
310 
311 			/*
312 			 * put all previews on top of each other, on the right
313 			 * size of the display
314 			 */
315 			ret = scene_obj_set_pos(scn, item->preview_id, -4, y);
316 			if (ret < 0)
317 				return log_msg_ret("prev", ret);
318 
319 			hide = menu->cur_item_id != item->id;
320 			ret = scene_obj_set_hide(scn, item->preview_id, hide);
321 			if (ret < 0)
322 				return log_msg_ret("hid", ret);
323 		}
324 
325 		if (!stack || open)
326 			y += height + theme->menuitem_gap_y;
327 	}
328 
329 	if (sel_id)
330 		menu_point_to_item(menu, sel_id);
331 	menu->obj.bbox.x1 = menu->obj.bbox.x0 + menu->obj.dims.x;
332 	menu->obj.bbox.y1 = menu->obj.bbox.y0 + menu->obj.dims.y;
333 	menu->obj.flags |= SCENEOF_SIZE_VALID;
334 
335 	return 0;
336 }
337 
scene_menu(struct scene * scn,const char * name,uint id,struct scene_obj_menu ** menup)338 int scene_menu(struct scene *scn, const char *name, uint id,
339 	       struct scene_obj_menu **menup)
340 {
341 	struct scene_obj_menu *menu;
342 	int ret;
343 
344 	ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU,
345 			    sizeof(struct scene_obj_menu),
346 			    (struct scene_obj **)&menu);
347 	if (ret < 0)
348 		return log_msg_ret("obj", -ENOMEM);
349 
350 	if (menup)
351 		*menup = menu;
352 	INIT_LIST_HEAD(&menu->item_head);
353 
354 	return menu->obj.id;
355 }
356 
scene_menu_find_key(struct scene * scn,struct scene_obj_menu * menu,int key)357 static struct scene_menitem *scene_menu_find_key(struct scene *scn,
358 						  struct scene_obj_menu *menu,
359 						  int key)
360 {
361 	struct scene_menitem *item;
362 
363 	list_for_each_entry(item, &menu->item_head, sibling) {
364 		if (item->key_id) {
365 			struct scene_obj_txt *txt;
366 			const char *str;
367 
368 			txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
369 			if (txt) {
370 				str = expo_get_str(scn->expo, txt->gen.str_id);
371 				if (str && *str == key)
372 					return item;
373 			}
374 		}
375 	}
376 
377 	return NULL;
378 }
379 
scene_menu_send_key(struct scene * scn,struct scene_obj_menu * menu,int key,struct expo_action * event)380 int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
381 			struct expo_action *event)
382 {
383 	const bool open = menu->obj.flags & SCENEOF_OPEN;
384 	struct scene_menitem *item, *cur, *key_item;
385 
386 	cur = NULL;
387 	key_item = NULL;
388 
389 	if (!list_empty(&menu->item_head)) {
390 		list_for_each_entry(item, &menu->item_head, sibling) {
391 			/* select an item if not done already */
392 			if (menu->cur_item_id == item->id) {
393 				cur = item;
394 				break;
395 			}
396 		}
397 	}
398 
399 	if (!cur)
400 		return -ENOTTY;
401 
402 	switch (key) {
403 	case BKEY_UP:
404 		if (item != list_first_entry(&menu->item_head,
405 					     struct scene_menitem, sibling)) {
406 			item = list_entry(item->sibling.prev,
407 					  struct scene_menitem, sibling);
408 			event->type = EXPOACT_POINT_ITEM;
409 			event->select.id = item->id;
410 			log_debug("up to item %d\n", event->select.id);
411 		}
412 		break;
413 	case BKEY_DOWN:
414 		if (!list_is_last(&item->sibling, &menu->item_head)) {
415 			item = list_entry(item->sibling.next,
416 					  struct scene_menitem, sibling);
417 			event->type = EXPOACT_POINT_ITEM;
418 			event->select.id = item->id;
419 			log_debug("down to item %d\n", event->select.id);
420 		}
421 		break;
422 	case BKEY_SELECT:
423 		event->type = EXPOACT_SELECT;
424 		event->select.id = item->id;
425 		log_debug("select item %d\n", event->select.id);
426 		break;
427 	case BKEY_QUIT:
428 		if (scn->expo->popup && open) {
429 			event->type = EXPOACT_CLOSE;
430 			event->select.id = menu->obj.id;
431 		} else {
432 			event->type = EXPOACT_QUIT;
433 			log_debug("menu quit\n");
434 		}
435 		break;
436 	case '0'...'9':
437 		key_item = scene_menu_find_key(scn, menu, key);
438 		if (key_item) {
439 			event->type = EXPOACT_SELECT;
440 			event->select.id = key_item->id;
441 		}
442 		break;
443 	}
444 
445 	return 0;
446 }
447 
scene_menuitem(struct scene * scn,uint menu_id,const char * name,uint id,uint key_id,uint label_id,uint desc_id,uint preview_id,uint flags,struct scene_menitem ** itemp)448 int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
449 		   uint key_id, uint label_id, uint desc_id, uint preview_id,
450 		   uint flags, struct scene_menitem **itemp)
451 {
452 	struct scene_obj_menu *menu;
453 	struct scene_menitem *item;
454 
455 	menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU);
456 	if (!menu)
457 		return log_msg_ret("find", -ENOENT);
458 
459 	/* Check that the text ID is valid */
460 	if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT))
461 		return log_msg_ret("txt", -EINVAL);
462 
463 	item = calloc(1, sizeof(struct scene_menitem));
464 	if (!item)
465 		return log_msg_ret("item", -ENOMEM);
466 	item->name = strdup(name);
467 	if (!item->name) {
468 		free(item);
469 		return log_msg_ret("name", -ENOMEM);
470 	}
471 
472 	item->id = resolve_id(scn->expo, id);
473 	item->key_id = key_id;
474 	item->label_id = label_id;
475 	item->desc_id = desc_id;
476 	item->preview_id = preview_id;
477 	item->flags = flags;
478 	item->value = INT_MAX;
479 	list_add_tail(&item->sibling, &menu->item_head);
480 
481 	if (itemp)
482 		*itemp = item;
483 
484 	return item->id;
485 }
486 
scene_menu_set_title(struct scene * scn,uint id,uint title_id)487 int scene_menu_set_title(struct scene *scn, uint id, uint title_id)
488 {
489 	struct scene_obj_menu *menu;
490 	struct scene_obj_txt *txt;
491 
492 	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
493 	if (!menu)
494 		return log_msg_ret("menu", -ENOENT);
495 
496 	/* Check that the ID is valid */
497 	if (title_id) {
498 		txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT);
499 		if (!txt)
500 			return log_msg_ret("txt", -EINVAL);
501 	}
502 
503 	menu->title_id = title_id;
504 
505 	return 0;
506 }
507 
scene_menu_set_pointer(struct scene * scn,uint id,uint pointer_id)508 int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id)
509 {
510 	struct scene_obj_menu *menu;
511 	struct scene_obj *obj;
512 
513 	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
514 	if (!menu)
515 		return log_msg_ret("menu", -ENOENT);
516 
517 	/* Check that the ID is valid */
518 	if (pointer_id) {
519 		obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE);
520 		if (!obj)
521 			return log_msg_ret("obj", -EINVAL);
522 	}
523 
524 	menu->pointer_id = pointer_id;
525 
526 	return 0;
527 }
528 
scene_menu_select_item(struct scene * scn,uint id,uint cur_item_id)529 int scene_menu_select_item(struct scene *scn, uint id, uint cur_item_id)
530 {
531 	struct scene_obj_menu *menu;
532 	int ret;
533 
534 	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
535 	if (!menu)
536 		return log_msg_ret("menu", -ENOENT);
537 
538 	ret = menu_point_to_item(menu, cur_item_id);
539 	if (ret)
540 		return log_msg_ret("msi", ret);
541 
542 	return 0;
543 }
544 
scene_menu_get_cur_item(struct scene * scn,uint id)545 int scene_menu_get_cur_item(struct scene *scn, uint id)
546 {
547 	struct scene_obj_menu *menu;
548 
549 	menu = scene_obj_find(scn, id, SCENEOBJT_MENU);
550 	if (!menu)
551 		return log_msg_ret("menu", -ENOENT);
552 
553 	return menu->cur_item_id;
554 }
555 
scene_menu_display(struct scene_obj_menu * menu)556 int scene_menu_display(struct scene_obj_menu *menu)
557 {
558 	struct scene *scn = menu->obj.scene;
559 	struct scene_obj_txt *pointer;
560 	struct expo *exp = scn->expo;
561 	struct scene_menitem *item;
562 	const char *pstr;
563 
564 	printf("U-Boot    :    Boot Menu\n\n");
565 	if (menu->title_id) {
566 		struct scene_obj_txt *txt;
567 		const char *str;
568 
569 		txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT);
570 		if (!txt)
571 			return log_msg_ret("txt", -EINVAL);
572 
573 		str = expo_get_str(exp, txt->gen.str_id);
574 		printf("%s\n\n", str ? str : "");
575 	}
576 
577 	if (list_empty(&menu->item_head))
578 		return 0;
579 
580 	pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT);
581 	if (pointer)
582 		pstr = expo_get_str(scn->expo, pointer->gen.str_id);
583 
584 	list_for_each_entry(item, &menu->item_head, sibling) {
585 		struct scene_obj_txt *key = NULL, *label = NULL;
586 		struct scene_obj_txt *desc = NULL;
587 		const char *kstr = NULL, *lstr = NULL, *dstr = NULL;
588 
589 		key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT);
590 		if (key)
591 			kstr = expo_get_str(exp, key->gen.str_id);
592 
593 		label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT);
594 		if (label)
595 			lstr = expo_get_str(exp, label->gen.str_id);
596 
597 		desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT);
598 		if (desc)
599 			dstr = expo_get_str(exp, desc->gen.str_id);
600 
601 		printf("%3s  %3s  %-10s  %s\n",
602 		       pointer && menu->cur_item_id == item->id ? pstr : "",
603 		       kstr, lstr, dstr);
604 	}
605 
606 	return -ENOTSUPP;
607 }
608 
scene_menu_render_deps(struct scene * scn,struct scene_obj_menu * menu)609 int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu)
610 {
611 	struct scene_menitem *item;
612 
613 	scene_render_deps(scn, menu->title_id);
614 	scene_render_deps(scn, menu->cur_item_id);
615 	scene_render_deps(scn, menu->pointer_id);
616 
617 	list_for_each_entry(item, &menu->item_head, sibling) {
618 		scene_render_deps(scn, item->key_id);
619 		scene_render_deps(scn, item->label_id);
620 		scene_render_deps(scn, item->desc_id);
621 	}
622 
623 	return 0;
624 }
625