blob: 6c52948eb69c1942d4ac7ce2106019f9ff1360d7 [file] [log] [blame]
Simon Glass0a4d14b2023-01-06 08:52:37 -06001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Implementation of a scene, a collection of text/image/menu items in an expo
4 *
5 * Copyright 2022 Google LLC
6 * Written by Simon Glass <sjg@chromium.org>
7 */
8
Simon Glassfe4c1e22023-06-01 10:22:43 -06009#define LOG_CATEGORY LOGC_EXPO
10
Simon Glass0a4d14b2023-01-06 08:52:37 -060011#include <common.h>
12#include <dm.h>
13#include <expo.h>
14#include <malloc.h>
15#include <mapmem.h>
Simon Glassf0e1e8c2023-06-01 10:22:59 -060016#include <menu.h>
Simon Glass0a4d14b2023-01-06 08:52:37 -060017#include <video.h>
18#include <video_console.h>
19#include <linux/input.h>
20#include "scene_internal.h"
21
Simon Glass0a4d14b2023-01-06 08:52:37 -060022int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
23{
24 struct scene *scn;
25
26 scn = calloc(1, sizeof(struct scene));
27 if (!scn)
28 return log_msg_ret("expo", -ENOMEM);
29 scn->name = strdup(name);
30 if (!scn->name) {
31 free(scn);
32 return log_msg_ret("name", -ENOMEM);
33 }
34
35 INIT_LIST_HEAD(&scn->obj_head);
36 scn->id = resolve_id(exp, id);
37 scn->expo = exp;
38 list_add_tail(&scn->sibling, &exp->scene_head);
39
40 *scnp = scn;
41
42 return scn->id;
43}
44
45void scene_obj_destroy(struct scene_obj *obj)
46{
47 if (obj->type == SCENEOBJT_MENU)
48 scene_menu_destroy((struct scene_obj_menu *)obj);
49 free(obj->name);
50 free(obj);
51}
52
53void scene_destroy(struct scene *scn)
54{
55 struct scene_obj *obj, *next;
56
57 list_for_each_entry_safe(obj, next, &scn->obj_head, sibling)
58 scene_obj_destroy(obj);
59
60 free(scn->name);
Simon Glass0a4d14b2023-01-06 08:52:37 -060061 free(scn);
62}
63
Simon Glassea274b62023-06-01 10:22:27 -060064int scene_title_set(struct scene *scn, uint id)
Simon Glass0a4d14b2023-01-06 08:52:37 -060065{
Simon Glassea274b62023-06-01 10:22:27 -060066 scn->title_id = id;
Simon Glass0a4d14b2023-01-06 08:52:37 -060067
68 return 0;
69}
70
71int scene_obj_count(struct scene *scn)
72{
73 struct scene_obj *obj;
74 int count = 0;
75
76 list_for_each_entry(obj, &scn->obj_head, sibling)
77 count++;
78
79 return count;
80}
81
Simon Glass45ff0bc2023-08-14 16:40:21 -060082void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type)
Simon Glass0a4d14b2023-01-06 08:52:37 -060083{
84 struct scene_obj *obj;
85
86 list_for_each_entry(obj, &scn->obj_head, sibling) {
87 if (obj->id == id &&
88 (type == SCENEOBJT_NONE || obj->type == type))
89 return obj;
90 }
91
92 return NULL;
93}
94
Simon Glassc8925112023-06-01 10:23:02 -060095void *scene_obj_find_by_name(struct scene *scn, const char *name)
96{
97 struct scene_obj *obj;
98
99 list_for_each_entry(obj, &scn->obj_head, sibling) {
100 if (!strcmp(name, obj->name))
101 return obj;
102 }
103
104 return NULL;
105}
106
Simon Glass0a4d14b2023-01-06 08:52:37 -0600107int scene_obj_add(struct scene *scn, const char *name, uint id,
108 enum scene_obj_t type, uint size, struct scene_obj **objp)
109{
110 struct scene_obj *obj;
111
112 obj = calloc(1, size);
113 if (!obj)
114 return log_msg_ret("obj", -ENOMEM);
115 obj->name = strdup(name);
116 if (!obj->name) {
117 free(obj);
118 return log_msg_ret("name", -ENOMEM);
119 }
120
121 obj->id = resolve_id(scn->expo, id);
122 obj->scene = scn;
123 obj->type = type;
124 list_add_tail(&obj->sibling, &scn->obj_head);
125 *objp = obj;
126
127 return obj->id;
128}
129
130int scene_img(struct scene *scn, const char *name, uint id, char *data,
131 struct scene_obj_img **imgp)
132{
133 struct scene_obj_img *img;
134 int ret;
135
136 ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE,
137 sizeof(struct scene_obj_img),
138 (struct scene_obj **)&img);
139 if (ret < 0)
140 return log_msg_ret("obj", -ENOMEM);
141
142 img->data = data;
143
144 if (imgp)
145 *imgp = img;
146
147 return img->obj.id;
148}
149
150int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
151 struct scene_obj_txt **txtp)
152{
153 struct scene_obj_txt *txt;
154 int ret;
155
156 ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
157 sizeof(struct scene_obj_txt),
158 (struct scene_obj **)&txt);
159 if (ret < 0)
160 return log_msg_ret("obj", -ENOMEM);
161
162 txt->str_id = str_id;
163
164 if (txtp)
165 *txtp = txt;
166
167 return txt->obj.id;
168}
169
170int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
171 const char *str, struct scene_obj_txt **txtp)
172{
173 struct scene_obj_txt *txt;
174 int ret;
175
176 ret = expo_str(scn->expo, name, str_id, str);
177 if (ret < 0)
178 return log_msg_ret("str", ret);
179 else if (ret != str_id)
180 return log_msg_ret("id", -EEXIST);
181
182 ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
183 sizeof(struct scene_obj_txt),
184 (struct scene_obj **)&txt);
185 if (ret < 0)
186 return log_msg_ret("obj", -ENOMEM);
187
188 txt->str_id = str_id;
189
190 if (txtp)
191 *txtp = txt;
192
193 return txt->obj.id;
194}
195
196int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
197 uint font_size)
198{
199 struct scene_obj_txt *txt;
200
201 txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
202 if (!txt)
203 return log_msg_ret("find", -ENOENT);
204 txt->font_name = font_name;
205 txt->font_size = font_size;
206
207 return 0;
208}
209
210int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
211{
212 struct scene_obj *obj;
213
214 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
215 if (!obj)
216 return log_msg_ret("find", -ENOENT);
Simon Glass7b043952023-06-01 10:22:49 -0600217 obj->dim.x = x;
218 obj->dim.y = y;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600219
220 return 0;
221}
222
Simon Glass7a960052023-06-01 10:22:52 -0600223int scene_obj_set_size(struct scene *scn, uint id, int w, int h)
224{
225 struct scene_obj *obj;
226
227 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
228 if (!obj)
229 return log_msg_ret("find", -ENOENT);
230 obj->dim.w = w;
231 obj->dim.h = h;
232
233 return 0;
234}
235
Simon Glass0a4d14b2023-01-06 08:52:37 -0600236int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
237{
Simon Glass6081b0f2023-06-01 10:22:50 -0600238 int ret;
239
240 ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE,
241 hide ? SCENEOF_HIDE : 0);
242 if (ret)
243 return log_msg_ret("flg", ret);
244
245 return 0;
246}
247
248int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set)
249{
Simon Glass0a4d14b2023-01-06 08:52:37 -0600250 struct scene_obj *obj;
251
252 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
253 if (!obj)
254 return log_msg_ret("find", -ENOENT);
Simon Glass6081b0f2023-06-01 10:22:50 -0600255 obj->flags &= ~clr;
256 obj->flags |= set;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600257
258 return 0;
259}
260
261int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
262{
263 struct scene_obj *obj;
264
265 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
266 if (!obj)
267 return log_msg_ret("find", -ENOENT);
268
269 switch (obj->type) {
270 case SCENEOBJT_NONE:
271 case SCENEOBJT_MENU:
272 break;
273 case SCENEOBJT_IMAGE: {
274 struct scene_obj_img *img = (struct scene_obj_img *)obj;
275 ulong width, height;
276 uint bpix;
277
278 video_bmp_get_info(img->data, &width, &height, &bpix);
279 if (widthp)
280 *widthp = width;
281 return height;
282 }
283 case SCENEOBJT_TEXT: {
284 struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
285 struct expo *exp = scn->expo;
Simon Glass9e1a86d2023-06-01 10:22:51 -0600286 struct vidconsole_bbox bbox;
287 const char *str;
288 int len, ret;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600289
Simon Glass9e1a86d2023-06-01 10:22:51 -0600290 str = expo_get_str(exp, txt->str_id);
291 if (!str)
292 return log_msg_ret("str", -ENOENT);
293 len = strlen(str);
294
295 /* if there is no console, make it up */
296 if (!exp->cons) {
297 if (widthp)
298 *widthp = 8 * len;
299 return 16;
300 }
301
302 ret = vidconsole_measure(scn->expo->cons, txt->font_name,
303 txt->font_size, str, &bbox);
304 if (ret)
305 return log_msg_ret("mea", ret);
Simon Glass0a4d14b2023-01-06 08:52:37 -0600306 if (widthp)
Simon Glass9e1a86d2023-06-01 10:22:51 -0600307 *widthp = bbox.x1;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600308
Simon Glass9e1a86d2023-06-01 10:22:51 -0600309 return bbox.y1;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600310 }
311 }
312
313 return 0;
314}
315
316/**
317 * scene_obj_render() - Render an object
318 *
319 */
320static int scene_obj_render(struct scene_obj *obj, bool text_mode)
321{
322 struct scene *scn = obj->scene;
323 struct expo *exp = scn->expo;
Simon Glass86f1ac52023-06-01 10:23:00 -0600324 const struct expo_theme *theme = &exp->theme;
Simon Glass67e2af12023-06-01 10:22:34 -0600325 struct udevice *dev = exp->display;
326 struct udevice *cons = text_mode ? NULL : exp->cons;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600327 int x, y, ret;
328
Simon Glass7b043952023-06-01 10:22:49 -0600329 x = obj->dim.x;
330 y = obj->dim.y;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600331
332 switch (obj->type) {
333 case SCENEOBJT_NONE:
334 break;
335 case SCENEOBJT_IMAGE: {
336 struct scene_obj_img *img = (struct scene_obj_img *)obj;
337
338 if (!cons)
339 return -ENOTSUPP;
340 ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y,
341 true);
342 if (ret < 0)
343 return log_msg_ret("img", ret);
344 break;
345 }
346 case SCENEOBJT_TEXT: {
347 struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
348 const char *str;
349
350 if (!cons)
351 return -ENOTSUPP;
352
353 if (txt->font_name || txt->font_size) {
354 ret = vidconsole_select_font(cons,
355 txt->font_name,
356 txt->font_size);
357 } else {
358 ret = vidconsole_select_font(cons, NULL, 0);
359 }
360 if (ret && ret != -ENOSYS)
361 return log_msg_ret("font", ret);
Simon Glass0a4d14b2023-01-06 08:52:37 -0600362 str = expo_get_str(exp, txt->str_id);
Simon Glass01922ec2023-06-01 10:22:57 -0600363 if (str) {
364 struct video_priv *vid_priv;
365 struct vidconsole_colour old;
366 enum colour_idx fore, back;
367
368 if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
369 fore = VID_BLACK;
370 back = VID_WHITE;
371 } else {
372 fore = VID_LIGHT_GRAY;
373 back = VID_BLACK;
374 }
375
376 vid_priv = dev_get_uclass_priv(dev);
377 if (obj->flags & SCENEOF_POINT) {
378 vidconsole_push_colour(cons, fore, back, &old);
Simon Glass86f1ac52023-06-01 10:23:00 -0600379 video_fill_part(dev, x - theme->menu_inset, y,
380 x + obj->dim.w,
381 y + obj->dim.h,
Simon Glass01922ec2023-06-01 10:22:57 -0600382 vid_priv->colour_bg);
383 }
384 vidconsole_set_cursor_pos(cons, x, y);
Simon Glass0a4d14b2023-01-06 08:52:37 -0600385 vidconsole_put_string(cons, str);
Simon Glass01922ec2023-06-01 10:22:57 -0600386 if (obj->flags & SCENEOF_POINT)
387 vidconsole_pop_colour(cons, &old);
388 }
Simon Glass0a4d14b2023-01-06 08:52:37 -0600389 break;
390 }
391 case SCENEOBJT_MENU: {
392 struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
Simon Glass01922ec2023-06-01 10:22:57 -0600393
394 if (exp->popup && (obj->flags & SCENEOF_OPEN)) {
395 if (!cons)
396 return -ENOTSUPP;
397
398 /* draw a background behind the menu items */
399 scene_menu_render(menu);
400 }
Simon Glass0a4d14b2023-01-06 08:52:37 -0600401 /*
402 * With a vidconsole, the text and item pointer are rendered as
403 * normal objects so we don't need to do anything here. The menu
404 * simply controls where they are positioned.
405 */
406 if (cons)
407 return -ENOTSUPP;
408
409 ret = scene_menu_display(menu);
410 if (ret < 0)
411 return log_msg_ret("img", ret);
412
413 break;
414 }
415 }
416
417 return 0;
418}
419
420int scene_arrange(struct scene *scn)
421{
422 struct scene_obj *obj;
423 int ret;
424
425 list_for_each_entry(obj, &scn->obj_head, sibling) {
426 if (obj->type == SCENEOBJT_MENU) {
427 struct scene_obj_menu *menu;
428
429 menu = (struct scene_obj_menu *)obj,
430 ret = scene_menu_arrange(scn, menu);
431 if (ret)
432 return log_msg_ret("arr", ret);
433 }
434 }
435
436 return 0;
437}
438
Simon Glass12f57732023-06-01 10:22:58 -0600439int scene_render_deps(struct scene *scn, uint id)
440{
441 struct scene_obj *obj;
442 int ret;
443
444 if (!id)
445 return 0;
446 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
447 if (!obj)
448 return log_msg_ret("obj", -ENOENT);
449
450 if (!(obj->flags & SCENEOF_HIDE)) {
451 ret = scene_obj_render(obj, false);
452 if (ret && ret != -ENOTSUPP)
453 return log_msg_ret("ren", ret);
454
455 if (obj->type == SCENEOBJT_MENU)
456 scene_menu_render_deps(scn,
457 (struct scene_obj_menu *)obj);
458 }
459
460 return 0;
461}
462
Simon Glass0a4d14b2023-01-06 08:52:37 -0600463int scene_render(struct scene *scn)
464{
465 struct expo *exp = scn->expo;
466 struct scene_obj *obj;
467 int ret;
468
469 list_for_each_entry(obj, &scn->obj_head, sibling) {
Simon Glass6081b0f2023-06-01 10:22:50 -0600470 if (!(obj->flags & SCENEOF_HIDE)) {
Simon Glass0a4d14b2023-01-06 08:52:37 -0600471 ret = scene_obj_render(obj, exp->text_mode);
472 if (ret && ret != -ENOTSUPP)
473 return log_msg_ret("ren", ret);
474 }
475 }
476
Simon Glass12f57732023-06-01 10:22:58 -0600477 /* render any highlighted object on top of the others */
478 if (scn->highlight_id && !exp->text_mode) {
479 ret = scene_render_deps(scn, scn->highlight_id);
480 if (ret && ret != -ENOTSUPP)
481 return log_msg_ret("dep", ret);
482 }
483
Simon Glass0a4d14b2023-01-06 08:52:37 -0600484 return 0;
485}
486
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600487/**
488 * send_key_obj() - Handle a keypress for moving between objects
489 *
490 * @scn: Scene to receive the key
491 * @key: Key to send (KEYCODE_UP)
492 * @event: Returns resulting event from this keypress
493 * Returns: 0 if OK, -ve on error
494 */
495static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key,
496 struct expo_action *event)
497{
498 switch (key) {
499 case BKEY_UP:
500 while (obj != list_first_entry(&scn->obj_head, struct scene_obj,
501 sibling)) {
502 obj = list_entry(obj->sibling.prev,
503 struct scene_obj, sibling);
504 if (obj->type == SCENEOBJT_MENU) {
505 event->type = EXPOACT_POINT_OBJ;
506 event->select.id = obj->id;
507 log_debug("up to obj %d\n", event->select.id);
508 break;
509 }
510 }
511 break;
512 case BKEY_DOWN:
513 while (!list_is_last(&obj->sibling, &scn->obj_head)) {
514 obj = list_entry(obj->sibling.next, struct scene_obj,
515 sibling);
516 if (obj->type == SCENEOBJT_MENU) {
517 event->type = EXPOACT_POINT_OBJ;
518 event->select.id = obj->id;
519 log_debug("down to obj %d\n", event->select.id);
520 break;
521 }
522 }
523 break;
524 case BKEY_SELECT:
525 if (obj->type == SCENEOBJT_MENU) {
526 event->type = EXPOACT_OPEN;
527 event->select.id = obj->id;
528 log_debug("open obj %d\n", event->select.id);
529 }
530 break;
531 case BKEY_QUIT:
532 event->type = EXPOACT_QUIT;
533 log_debug("obj quit\n");
534 break;
535 }
536}
537
Simon Glass0a4d14b2023-01-06 08:52:37 -0600538int scene_send_key(struct scene *scn, int key, struct expo_action *event)
539{
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600540 struct scene_obj_menu *menu;
Simon Glass0a4d14b2023-01-06 08:52:37 -0600541 struct scene_obj *obj;
542 int ret;
543
Simon Glassf0e1e8c2023-06-01 10:22:59 -0600544 event->type = EXPOACT_NONE;
545
546 /*
547 * In 'popup' mode, arrow keys move betwen objects, unless a menu is
548 * opened
549 */
550 if (scn->expo->popup) {
551 obj = NULL;
552 if (scn->highlight_id) {
553 obj = scene_obj_find(scn, scn->highlight_id,
554 SCENEOBJT_NONE);
555 }
556 if (!obj)
557 return 0;
558
559 if (!(obj->flags & SCENEOF_OPEN)) {
560 send_key_obj(scn, obj, key, event);
561 return 0;
562 }
563
564 menu = (struct scene_obj_menu *)obj,
565 ret = scene_menu_send_key(scn, menu, key, event);
566 if (ret)
567 return log_msg_ret("key", ret);
568 return 0;
569 }
570
Simon Glass0a4d14b2023-01-06 08:52:37 -0600571 list_for_each_entry(obj, &scn->obj_head, sibling) {
572 if (obj->type == SCENEOBJT_MENU) {
573 struct scene_obj_menu *menu;
574
575 menu = (struct scene_obj_menu *)obj,
576 ret = scene_menu_send_key(scn, menu, key, event);
577 if (ret)
578 return log_msg_ret("key", ret);
Simon Glass0a4d14b2023-01-06 08:52:37 -0600579 break;
580 }
581 }
582
583 return 0;
584}
Simon Glass7a960052023-06-01 10:22:52 -0600585
586int scene_calc_dims(struct scene *scn, bool do_menus)
587{
588 struct scene_obj *obj;
589 int ret;
590
591 list_for_each_entry(obj, &scn->obj_head, sibling) {
592 switch (obj->type) {
593 case SCENEOBJT_NONE:
594 case SCENEOBJT_TEXT:
595 case SCENEOBJT_IMAGE: {
596 int width;
597
598 if (!do_menus) {
599 ret = scene_obj_get_hw(scn, obj->id, &width);
600 if (ret < 0)
601 return log_msg_ret("get", ret);
602 obj->dim.w = width;
603 obj->dim.h = ret;
604 }
605 break;
606 }
607 case SCENEOBJT_MENU: {
608 struct scene_obj_menu *menu;
609
610 if (do_menus) {
611 menu = (struct scene_obj_menu *)obj;
612
613 ret = scene_menu_calc_dims(menu);
614 if (ret)
615 return log_msg_ret("men", ret);
616 }
617 break;
618 }
619 }
620 }
621
622 return 0;
623}
Simon Glassc999e172023-06-01 10:22:53 -0600624
625int scene_apply_theme(struct scene *scn, struct expo_theme *theme)
626{
627 struct scene_obj *obj;
628 int ret;
629
630 /* Avoid error-checking optional items */
631 scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size);
632
633 list_for_each_entry(obj, &scn->obj_head, sibling) {
634 switch (obj->type) {
635 case SCENEOBJT_NONE:
636 case SCENEOBJT_IMAGE:
637 case SCENEOBJT_MENU:
638 break;
639 case SCENEOBJT_TEXT:
640 scene_txt_set_font(scn, obj->id, NULL,
641 theme->font_size);
642 break;
643 }
644 }
645
646 ret = scene_arrange(scn);
647 if (ret)
648 return log_msg_ret("arr", ret);
649
650 return 0;
651}
Simon Glass01922ec2023-06-01 10:22:57 -0600652
653void scene_set_highlight_id(struct scene *scn, uint id)
654{
655 scn->highlight_id = id;
656}
657
658void scene_highlight_first(struct scene *scn)
659{
660 struct scene_obj *obj;
661
662 list_for_each_entry(obj, &scn->obj_head, sibling) {
663 switch (obj->type) {
664 case SCENEOBJT_MENU:
665 scene_set_highlight_id(scn, obj->id);
666 return;
667 default:
668 break;
669 }
670 }
671}
672
673int scene_set_open(struct scene *scn, uint id, bool open)
674{
675 int ret;
676
677 ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN,
678 open ? SCENEOF_OPEN : 0);
679 if (ret)
680 return log_msg_ret("flg", ret);
681
682 return 0;
683}
Simon Glasse90acd82023-08-14 16:40:23 -0600684
685int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter,
686 void *priv)
687{
688 struct scene_obj *obj;
689
690 list_for_each_entry(obj, &scn->obj_head, sibling) {
691 int ret;
692
693 ret = iter(obj, priv);
694 if (ret)
695 return log_msg_ret("itr", ret);
696 }
697
698 return 0;
699}