"Das U-Boot" Source Tree
at master 553 lines 13 kB view raw
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 21static void scene_menuitem_destroy(struct scene_menitem *item) 22{ 23 free(item->name); 24 free(item); 25} 26 27void 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 35struct 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 48struct 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 64struct 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 */ 87static 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->popup; 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.dim.x + 200, label->dim.y); 106 if (ret < 0) 107 return log_msg_ret("ptr", ret); 108 } 109 110 if (stack) { 111 point &= scn->highlight_id == menu->obj.id; 112 scene_obj_flag_clrset(scn, item->label_id, SCENEOF_POINT, 113 point ? SCENEOF_POINT : 0); 114 } 115 116 return 0; 117} 118 119/** 120 * menu_point_to_item() - Point to a particular menu item 121 * 122 * Sets the currently pointed-to / highlighted menu item 123 */ 124static void menu_point_to_item(struct scene_obj_menu *menu, uint item_id) 125{ 126 if (menu->cur_item_id) 127 update_pointers(menu, menu->cur_item_id, false); 128 menu->cur_item_id = item_id; 129 update_pointers(menu, item_id, true); 130} 131 132void scene_menu_calc_bbox(struct scene_obj_menu *menu, 133 struct vidconsole_bbox *bbox, 134 struct vidconsole_bbox *label_bbox) 135{ 136 const struct expo_theme *theme = &menu->obj.scene->expo->theme; 137 const struct scene_menitem *item; 138 139 bbox->valid = false; 140 scene_bbox_union(menu->obj.scene, menu->title_id, 0, bbox); 141 142 label_bbox->valid = false; 143 144 list_for_each_entry(item, &menu->item_head, sibling) { 145 scene_bbox_union(menu->obj.scene, item->label_id, 146 theme->menu_inset, bbox); 147 scene_bbox_union(menu->obj.scene, item->key_id, 0, bbox); 148 scene_bbox_union(menu->obj.scene, item->desc_id, 0, bbox); 149 scene_bbox_union(menu->obj.scene, item->preview_id, 0, bbox); 150 151 /* Get the bounding box of all labels */ 152 scene_bbox_union(menu->obj.scene, item->label_id, 153 theme->menu_inset, label_bbox); 154 } 155 156 /* 157 * subtract the final menuitem's gap to keep the insert the same top 158 * and bottom 159 */ 160 label_bbox->y1 -= theme->menuitem_gap_y; 161} 162 163int scene_menu_calc_dims(struct scene_obj_menu *menu) 164{ 165 struct vidconsole_bbox bbox, label_bbox; 166 const struct scene_menitem *item; 167 168 scene_menu_calc_bbox(menu, &bbox, &label_bbox); 169 170 /* Make all labels the same size */ 171 if (label_bbox.valid) { 172 list_for_each_entry(item, &menu->item_head, sibling) { 173 scene_obj_set_size(menu->obj.scene, item->label_id, 174 label_bbox.x1 - label_bbox.x0, 175 label_bbox.y1 - label_bbox.y0); 176 } 177 } 178 179 if (bbox.valid) { 180 menu->obj.dim.w = bbox.x1 - bbox.x0; 181 menu->obj.dim.h = bbox.y1 - bbox.y0; 182 } 183 184 return 0; 185} 186 187int scene_menu_arrange(struct scene *scn, struct expo_arrange_info *arr, 188 struct scene_obj_menu *menu) 189{ 190 const bool open = menu->obj.flags & SCENEOF_OPEN; 191 struct expo *exp = scn->expo; 192 const bool stack = exp->popup; 193 const struct expo_theme *theme = &exp->theme; 194 struct scene_menitem *item; 195 uint sel_id; 196 int x, y; 197 int ret; 198 199 x = menu->obj.dim.x; 200 y = menu->obj.dim.y; 201 if (menu->title_id) { 202 int width; 203 204 ret = scene_obj_set_pos(scn, menu->title_id, menu->obj.dim.x, y); 205 if (ret < 0) 206 return log_msg_ret("tit", ret); 207 208 ret = scene_obj_get_hw(scn, menu->title_id, &width); 209 if (ret < 0) 210 return log_msg_ret("hei", ret); 211 212 if (stack) 213 x += arr->label_width + theme->menu_title_margin_x; 214 else 215 y += ret * 2; 216 } 217 218 /* 219 * Currently everything is hard-coded to particular columns so this 220 * won't work on small displays and looks strange if the font size is 221 * small. This can be updated once text measuring is supported in 222 * vidconsole 223 */ 224 sel_id = menu->cur_item_id; 225 list_for_each_entry(item, &menu->item_head, sibling) { 226 bool selected; 227 int height; 228 229 ret = scene_obj_get_hw(scn, item->label_id, NULL); 230 if (ret < 0) 231 return log_msg_ret("get", ret); 232 height = ret; 233 234 if (item->flags & SCENEMIF_GAP_BEFORE) 235 y += height; 236 237 /* select an item if not done already */ 238 if (!sel_id) 239 sel_id = item->id; 240 241 selected = sel_id == item->id; 242 243 /* 244 * Put the label on the left, then leave a space for the 245 * pointer, then the key and the description 246 */ 247 ret = scene_obj_set_pos(scn, item->label_id, 248 x + theme->menu_inset, y); 249 if (ret < 0) 250 return log_msg_ret("nam", ret); 251 scene_obj_set_hide(scn, item->label_id, 252 stack && !open && !selected); 253 254 if (item->key_id) { 255 ret = scene_obj_set_pos(scn, item->key_id, x + 230, y); 256 if (ret < 0) 257 return log_msg_ret("key", ret); 258 } 259 260 if (item->desc_id) { 261 ret = scene_obj_set_pos(scn, item->desc_id, x + 280, y); 262 if (ret < 0) 263 return log_msg_ret("des", ret); 264 } 265 266 if (item->preview_id) { 267 bool hide; 268 269 /* 270 * put all previews on top of each other, on the right 271 * size of the display 272 */ 273 ret = scene_obj_set_pos(scn, item->preview_id, -4, y); 274 if (ret < 0) 275 return log_msg_ret("prev", ret); 276 277 hide = menu->cur_item_id != item->id; 278 ret = scene_obj_set_hide(scn, item->preview_id, hide); 279 if (ret < 0) 280 return log_msg_ret("hid", ret); 281 } 282 283 if (!stack || open) 284 y += height + theme->menuitem_gap_y; 285 } 286 287 if (sel_id) 288 menu_point_to_item(menu, sel_id); 289 290 return 0; 291} 292 293int scene_menu(struct scene *scn, const char *name, uint id, 294 struct scene_obj_menu **menup) 295{ 296 struct scene_obj_menu *menu; 297 int ret; 298 299 ret = scene_obj_add(scn, name, id, SCENEOBJT_MENU, 300 sizeof(struct scene_obj_menu), 301 (struct scene_obj **)&menu); 302 if (ret < 0) 303 return log_msg_ret("obj", -ENOMEM); 304 305 if (menup) 306 *menup = menu; 307 INIT_LIST_HEAD(&menu->item_head); 308 309 return menu->obj.id; 310} 311 312static struct scene_menitem *scene_menu_find_key(struct scene *scn, 313 struct scene_obj_menu *menu, 314 int key) 315{ 316 struct scene_menitem *item; 317 318 list_for_each_entry(item, &menu->item_head, sibling) { 319 if (item->key_id) { 320 struct scene_obj_txt *txt; 321 const char *str; 322 323 txt = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT); 324 if (txt) { 325 str = expo_get_str(scn->expo, txt->str_id); 326 if (str && *str == key) 327 return item; 328 } 329 } 330 } 331 332 return NULL; 333} 334 335int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key, 336 struct expo_action *event) 337{ 338 const bool open = menu->obj.flags & SCENEOF_OPEN; 339 struct scene_menitem *item, *cur, *key_item; 340 341 cur = NULL; 342 key_item = NULL; 343 344 if (!list_empty(&menu->item_head)) { 345 list_for_each_entry(item, &menu->item_head, sibling) { 346 /* select an item if not done already */ 347 if (menu->cur_item_id == item->id) { 348 cur = item; 349 break; 350 } 351 } 352 } 353 354 if (!cur) 355 return -ENOTTY; 356 357 switch (key) { 358 case BKEY_UP: 359 if (item != list_first_entry(&menu->item_head, 360 struct scene_menitem, sibling)) { 361 item = list_entry(item->sibling.prev, 362 struct scene_menitem, sibling); 363 event->type = EXPOACT_POINT_ITEM; 364 event->select.id = item->id; 365 log_debug("up to item %d\n", event->select.id); 366 } 367 break; 368 case BKEY_DOWN: 369 if (!list_is_last(&item->sibling, &menu->item_head)) { 370 item = list_entry(item->sibling.next, 371 struct scene_menitem, sibling); 372 event->type = EXPOACT_POINT_ITEM; 373 event->select.id = item->id; 374 log_debug("down to item %d\n", event->select.id); 375 } 376 break; 377 case BKEY_SELECT: 378 event->type = EXPOACT_SELECT; 379 event->select.id = item->id; 380 log_debug("select item %d\n", event->select.id); 381 break; 382 case BKEY_QUIT: 383 if (scn->expo->popup && open) { 384 event->type = EXPOACT_CLOSE; 385 event->select.id = menu->obj.id; 386 } else { 387 event->type = EXPOACT_QUIT; 388 log_debug("menu quit\n"); 389 } 390 break; 391 case '0'...'9': 392 key_item = scene_menu_find_key(scn, menu, key); 393 if (key_item) { 394 event->type = EXPOACT_SELECT; 395 event->select.id = key_item->id; 396 } 397 break; 398 } 399 400 menu_point_to_item(menu, item->id); 401 402 return 0; 403} 404 405int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id, 406 uint key_id, uint label_id, uint desc_id, uint preview_id, 407 uint flags, struct scene_menitem **itemp) 408{ 409 struct scene_obj_menu *menu; 410 struct scene_menitem *item; 411 412 menu = scene_obj_find(scn, menu_id, SCENEOBJT_MENU); 413 if (!menu) 414 return log_msg_ret("find", -ENOENT); 415 416 /* Check that the text ID is valid */ 417 if (!scene_obj_find(scn, label_id, SCENEOBJT_TEXT)) 418 return log_msg_ret("txt", -EINVAL); 419 420 item = calloc(1, sizeof(struct scene_menitem)); 421 if (!item) 422 return log_msg_ret("item", -ENOMEM); 423 item->name = strdup(name); 424 if (!item->name) { 425 free(item); 426 return log_msg_ret("name", -ENOMEM); 427 } 428 429 item->id = resolve_id(scn->expo, id); 430 item->key_id = key_id; 431 item->label_id = label_id; 432 item->desc_id = desc_id; 433 item->preview_id = preview_id; 434 item->flags = flags; 435 item->value = INT_MAX; 436 list_add_tail(&item->sibling, &menu->item_head); 437 438 if (itemp) 439 *itemp = item; 440 441 return item->id; 442} 443 444int scene_menu_set_title(struct scene *scn, uint id, uint title_id) 445{ 446 struct scene_obj_menu *menu; 447 struct scene_obj_txt *txt; 448 449 menu = scene_obj_find(scn, id, SCENEOBJT_MENU); 450 if (!menu) 451 return log_msg_ret("menu", -ENOENT); 452 453 /* Check that the ID is valid */ 454 if (title_id) { 455 txt = scene_obj_find(scn, title_id, SCENEOBJT_TEXT); 456 if (!txt) 457 return log_msg_ret("txt", -EINVAL); 458 } 459 460 menu->title_id = title_id; 461 462 return 0; 463} 464 465int scene_menu_set_pointer(struct scene *scn, uint id, uint pointer_id) 466{ 467 struct scene_obj_menu *menu; 468 struct scene_obj *obj; 469 470 menu = scene_obj_find(scn, id, SCENEOBJT_MENU); 471 if (!menu) 472 return log_msg_ret("menu", -ENOENT); 473 474 /* Check that the ID is valid */ 475 if (pointer_id) { 476 obj = scene_obj_find(scn, pointer_id, SCENEOBJT_NONE); 477 if (!obj) 478 return log_msg_ret("obj", -EINVAL); 479 } 480 481 menu->pointer_id = pointer_id; 482 483 return 0; 484} 485 486int scene_menu_display(struct scene_obj_menu *menu) 487{ 488 struct scene *scn = menu->obj.scene; 489 struct scene_obj_txt *pointer; 490 struct expo *exp = scn->expo; 491 struct scene_menitem *item; 492 const char *pstr; 493 494 printf("U-Boot : Boot Menu\n\n"); 495 if (menu->title_id) { 496 struct scene_obj_txt *txt; 497 const char *str; 498 499 txt = scene_obj_find(scn, menu->title_id, SCENEOBJT_TEXT); 500 if (!txt) 501 return log_msg_ret("txt", -EINVAL); 502 503 str = expo_get_str(exp, txt->str_id); 504 printf("%s\n\n", str); 505 } 506 507 if (list_empty(&menu->item_head)) 508 return 0; 509 510 pointer = scene_obj_find(scn, menu->pointer_id, SCENEOBJT_TEXT); 511 pstr = expo_get_str(scn->expo, pointer->str_id); 512 513 list_for_each_entry(item, &menu->item_head, sibling) { 514 struct scene_obj_txt *key = NULL, *label = NULL; 515 struct scene_obj_txt *desc = NULL; 516 const char *kstr = NULL, *lstr = NULL, *dstr = NULL; 517 518 key = scene_obj_find(scn, item->key_id, SCENEOBJT_TEXT); 519 if (key) 520 kstr = expo_get_str(exp, key->str_id); 521 522 label = scene_obj_find(scn, item->label_id, SCENEOBJT_TEXT); 523 if (label) 524 lstr = expo_get_str(exp, label->str_id); 525 526 desc = scene_obj_find(scn, item->desc_id, SCENEOBJT_TEXT); 527 if (desc) 528 dstr = expo_get_str(exp, desc->str_id); 529 530 printf("%3s %3s %-10s %s\n", 531 pointer && menu->cur_item_id == item->id ? pstr : "", 532 kstr, lstr, dstr); 533 } 534 535 return -ENOTSUPP; 536} 537 538int scene_menu_render_deps(struct scene *scn, struct scene_obj_menu *menu) 539{ 540 struct scene_menitem *item; 541 542 scene_render_deps(scn, menu->title_id); 543 scene_render_deps(scn, menu->cur_item_id); 544 scene_render_deps(scn, menu->pointer_id); 545 546 list_for_each_entry(item, &menu->item_head, sibling) { 547 scene_render_deps(scn, item->key_id); 548 scene_render_deps(scn, item->label_id); 549 scene_render_deps(scn, item->desc_id); 550 } 551 552 return 0; 553}