From 226777f170f1533eacc097e73e59a7a3ab1c62a0 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 6 Jan 2023 08:52:38 -0600 Subject: [PATCH] expo: Add support for scene menus A menu is a key part of the expo design. It consists of a number of items which the user can select from. Add the initial implementation of this. Signed-off-by: Simon Glass --- boot/Makefile | 2 + boot/scene_menu.c | 390 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+) create mode 100644 boot/scene_menu.c diff --git a/boot/Makefile b/boot/Makefile index f0c3154921..0b30fcd64a 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -47,6 +47,8 @@ ifdef CONFIG_SPL_BUILD obj-$(CONFIG_SPL_LOAD_FIT) += common_fit.o endif +obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += expo.o scene.o scene_menu.o + obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE) += vbe.o vbe_request.o obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE) += vbe_simple.o obj-$(CONFIG_$(SPL_TPL_)BOOTMETH_VBE_SIMPLE_FW) += vbe_simple_fw.o diff --git a/boot/scene_menu.c b/boot/scene_menu.c new file mode 100644 index 0000000000..18998e862a --- /dev/null +++ b/boot/scene_menu.c @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Implementation of a menu in a scene + * + * Copyright 2022 Google LLC + * Written by Simon Glass + */ + +#define LOG_CATEGORY LOGC_BOOT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scene_internal.h" + +static void scene_menuitem_destroy(struct scene_menitem *item) +{ + free(item->name); + free(item); +} + +void scene_menu_destroy(struct scene_obj_menu *menu) +{ + struct scene_menitem *item, *next; + + list_for_each_entry_safe(item, next, &menu->item_head, sibling) + scene_menuitem_destroy(item); +} + +/** + * menu_point_to_item() - Point to a particular menu item + * + * Sets the currently pointed-to / highlighted menu item + */ +static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id) +{ + menu->cur_item_id = item_id; +} + +int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu) +{ + struct scene_menitem *item; + int y, cur_y; + int ret; + + y = menu->obj.y; + if (menu->title_id) { + ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.x, y); + if (ret < 0) + return log_msg_ret("tit", ret); + + ret = scene_obj_get_hw(scn, menu->title_id, NULL); + if (ret < 0) + return log_msg_ret("hei", ret); + + y += ret * 2; + } + + /* + * Currently everything is hard-coded to particular columns so this + * won't work on small displays and looks strange if the font size is + * small. This can be updated once text measuring is supported in + * vidconsole + */ + cur_y = -1; + list_for_each_entry(item, &menu->item_head, sibling) { + int height; + + ret = scene_obj_get_hw(scn, item->desc_id, NULL); + if (ret < 0) + return log_msg_ret("get", ret); + height = ret; + + if (item->flags & SCENEMIF_GAP_BEFORE) + y += height; + + /* select an item if not done already */ + if (!menu->cur_item_id) + menu_point_to_item(menu, item->id); + + /* + * Put the label on the left, then leave a space for the + * pointer, then the key and the description + */ + if (item->label_id) { + ret = scene_obj_set_pos(scn, item->label_id, menu->obj.x, + y); + if (ret < 0) + return log_msg_ret("nam", ret); + } + + ret = scene_obj_set_pos(scn, item->key_id, menu->obj.x + 230, + y); + if (ret < 0) + return log_msg_ret("key", ret); + + ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.x + 280, + y); + if (ret < 0) + return log_msg_ret("des", ret); + + if (menu->cur_item_id == item->id) + cur_y = y; + + if (item->preview_id) { + bool hide; + + /* + * put all previews on top of each other, on the right + * size of the display + */ + ret = scene_obj_set_pos(scn, item->preview_id, -4, y); + if (ret < 0) + return log_msg_ret("prev", ret); + + hide = menu->cur_item_id != item->id; + ret = scene_obj_set_hide(scn, item->preview_id, hide); + if (ret < 0) + return log_msg_ret("hid", ret); + } + + y += height; + } + + if (menu->pointer_id && cur_y != -1) { + /* + * put the pointer to the right of and level with the item it + * points to + */ + ret = scene_obj_set_pos(scn, menu->pointer_id, + menu->obj.x + 200, cur_y); + if (ret < 0) + return log_msg_ret("ptr", ret); + } + + return 0; +} + +int scene_menu(struct scene *scn, const char *name, uint id, + struct scene_obj_menu **menup) +{ + struct scene_obj_menu *menu; + int ret; + + ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU, + sizeof(struct scene_obj_menu), + (struct scene_obj **)&menu); + if (ret < 0) + return log_msg_ret("obj", -ENOMEM); + + if (menup) + *menup = menu; + INIT_LIST_HEAD(&menu->item_head); + + ret = scene_menu_arrange(scn, menu); + if (ret) + return log_msg_ret("pos", ret); + + return menu->obj.id; +} + +static struct scene_menitem *scene_menu_find_key(struct scene *scn, + struct scene_obj_menu *menu, + int key) +{ + struct scene_menitem *item; + + list_for_each_entry(item, &menu->item_head, sibling) { + if (item->key_id) { + struct scene_obj_txt *txt; + const char *str; + + txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT); + if (txt) { + str = expo_get_str(scn->expo, txt->str_id); + if (str && *str == key) + return item; + } + } + } + + return NULL; +} + +int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key, + struct expo_action *event) +{ + struct scene_menitem *item, *cur, *key_item; + + cur = NULL; + key_item = NULL; + + if (!list_empty(&menu->item_head)) { + list_for_each_entry(item, &menu->item_head, sibling) { + /* select an item if not done already */ + if (menu->cur_item_id == item->id) { + cur = item; + break; + } + } + } + + if (!cur) + return -ENOTTY; + + switch (key) { + case BKEY_UP: + if (item != list_first_entry(&menu->item_head, + struct scene_menitem, sibling)) { + item = list_entry(item->sibling.prev, + struct scene_menitem, sibling); + event->type = EXPOACT_POINT; + event->select.id = item->id; + log_debug("up to item %d\n", event->select.id); + } + break; + case BKEY_DOWN: + if (!list_is_last(&item->sibling, &menu->item_head)) { + item = list_entry(item->sibling.next, + struct scene_menitem, sibling); + event->type = EXPOACT_POINT; + event->select.id = item->id; + log_debug("down to item %d\n", event->select.id); + } + break; + case BKEY_SELECT: + event->type = EXPOACT_SELECT; + event->select.id = item->id; + log_debug("select item %d\n", event->select.id); + break; + case BKEY_QUIT: + event->type = EXPOACT_QUIT; + log_debug("quit\n"); + break; + case '0'...'9': + key_item = scene_menu_find_key(scn, menu, key); + if (key_item) { + event->type = EXPOACT_SELECT; + event->select.id = key_item->id; + } + break; + } + + menu_point_to_item(menu, item->id); + + return 0; +} + +int 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) +{ + struct scene_obj_menu *menu; + struct scene_menitem *item; + int ret; + + menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU); + if (!menu) + return log_msg_ret("find", -ENOENT); + + /* Check that the text ID is valid */ + if (!scene_obj_find(scn, desc_id, SCENEOBJT_TEXT)) + return log_msg_ret("txt", -EINVAL); + + item = calloc(1, sizeof(struct scene_obj_menu)); + if (!item) + return log_msg_ret("item", -ENOMEM); + item->name = strdup(name); + if (!item->name) { + free(item); + return log_msg_ret("name", -ENOMEM); + } + + item->id = resolve_id(scn->expo, id); + item->key_id = key_id; + item->label_id = label_id; + item->desc_id = desc_id; + item->preview_id = preview_id; + item->flags = flags; + list_add_tail(&item->sibling, &menu->item_head); + + ret = scene_menu_arrange(scn, menu); + if (ret) + return log_msg_ret("pos", ret); + + if (itemp) + *itemp = item; + + return item->id; +} + +int scene_menu_set_title(struct scene *scn, uint id, uint title_id) +{ + struct scene_obj_menu *menu; + struct scene_obj_txt *txt; + + menu = scene_obj_find(scn, id, SCENEOBJT_MENU); + if (!menu) + return log_msg_ret("menu", -ENOENT); + + /* Check that the ID is valid */ + if (title_id) { + txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT); + if (!txt) + return log_msg_ret("txt", -EINVAL); + } + + menu->title_id = title_id; + + return 0; +} + +int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id) +{ + struct scene_obj_menu *menu; + struct scene_obj *obj; + + menu = scene_obj_find(scn, id, SCENEOBJT_MENU); + if (!menu) + return log_msg_ret("menu", -ENOENT); + + /* Check that the ID is valid */ + if (pointer_id) { + obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE); + if (!obj) + return log_msg_ret("obj", -EINVAL); + } + + menu->pointer_id = pointer_id; + + return 0; +} + +int scene_menu_display(struct scene_obj_menu *menu) +{ + struct scene *scn = menu->obj.scene; + struct scene_obj_txt *pointer; + struct expo *exp = scn->expo; + struct scene_menitem *item; + const char *pstr; + + printf("U-Boot : Boot Menu\n\n"); + if (menu->title_id) { + struct scene_obj_txt *txt; + const char *str; + + txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT); + if (!txt) + return log_msg_ret("txt", -EINVAL); + + str = expo_get_str(exp, txt->str_id); + printf("%s\n\n", str); + } + + if (list_empty(&menu->item_head)) + return 0; + + pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT); + pstr = expo_get_str(scn->expo, pointer->str_id); + + list_for_each_entry(item, &menu->item_head, sibling) { + struct scene_obj_txt *key = NULL, *label = NULL; + struct scene_obj_txt *desc = NULL; + const char *kstr = NULL, *lstr = NULL, *dstr = NULL; + + key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT); + if (key) + kstr = expo_get_str(exp, key->str_id); + + label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT); + if (label) + lstr = expo_get_str(exp, label->str_id); + + desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT); + if (desc) + dstr = expo_get_str(exp, desc->str_id); + + printf("%3s %3s %-10s %s\n", + pointer && menu->cur_item_id == item->id ? pstr : "", + kstr, lstr, dstr); + } + + return -ENOTSUPP; +} -- 2.39.5