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 <s...@chromium.org>
---

Changes in v2:
- Drop the _add suffix on expo creation function
- Put strings in a separate structure referenced by ID

 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 f0c31549213..0b30fcd64a9 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 00000000000..18998e862ab
--- /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 <s...@chromium.org>
+ */
+
+#define LOG_CATEGORY   LOGC_BOOT
+
+#include <common.h>
+#include <dm.h>
+#include <expo.h>
+#include <malloc.h>
+#include <mapmem.h>
+#include <menu.h>
+#include <video.h>
+#include <video_console.h>
+#include <linux/input.h>
+#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.38.1.431.g37b22c650d-goog

Reply via email to