blob: eed7565f6a617d34e242c2144b1fc5740096c7c6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Implementation of a menu in a scene
*
* Copyright 2022 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEGORY LOGC_EXPO
#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.dim.y;
if (menu->title_id) {
ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.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.dim.x,
y);
if (ret < 0)
return log_msg_ret("nam", ret);
}
ret = scene_obj_set_pos(scn, item->key_id, menu->obj.dim.x + 230,
y);
if (ret < 0)
return log_msg_ret("key", ret);
ret = scene_obj_set_pos(scn, item->desc_id, menu->obj.dim.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.dim.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);
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;
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);
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;
}