1 // SPDX-License-Identifier: GPL-2.0+
3 * Implementation of a scene, a collection of text/image/menu items in an expo
5 * Copyright 2022 Google LLC
9 #define LOG_CATEGORY LOGC_EXPO
18 #include <video_console.h>
19 #include <linux/input.h>
20 #include "scene_internal.h"
22 int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp)
26 scn = calloc(1, sizeof(struct scene));
28 return log_msg_ret("expo", -ENOMEM);
29 scn->name = strdup(name);
32 return log_msg_ret("name", -ENOMEM);
36 if (!abuf_realloc(&scn->buf, EXPO_MAX_CHARS + 1)) {
39 return log_msg_ret("buf", -ENOMEM);
41 abuf_init(&scn->entry_save);
43 INIT_LIST_HEAD(&scn->obj_head);
44 scn->id = resolve_id(exp, id);
46 list_add_tail(&scn->sibling, &exp->scene_head);
53 void scene_obj_destroy(struct scene_obj *obj)
55 if (obj->type == SCENEOBJT_MENU)
56 scene_menu_destroy((struct scene_obj_menu *)obj);
61 void scene_destroy(struct scene *scn)
63 struct scene_obj *obj, *next;
65 list_for_each_entry_safe(obj, next, &scn->obj_head, sibling)
66 scene_obj_destroy(obj);
68 abuf_uninit(&scn->entry_save);
69 abuf_uninit(&scn->buf);
74 int scene_title_set(struct scene *scn, uint id)
81 int scene_obj_count(struct scene *scn)
83 struct scene_obj *obj;
86 list_for_each_entry(obj, &scn->obj_head, sibling)
92 void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type)
94 struct scene_obj *obj;
96 list_for_each_entry(obj, &scn->obj_head, sibling) {
98 (type == SCENEOBJT_NONE || obj->type == type))
105 void *scene_obj_find_by_name(struct scene *scn, const char *name)
107 struct scene_obj *obj;
109 list_for_each_entry(obj, &scn->obj_head, sibling) {
110 if (!strcmp(name, obj->name))
117 int scene_obj_add(struct scene *scn, const char *name, uint id,
118 enum scene_obj_t type, uint size, struct scene_obj **objp)
120 struct scene_obj *obj;
122 obj = calloc(1, size);
124 return log_msg_ret("obj", -ENOMEM);
125 obj->name = strdup(name);
128 return log_msg_ret("name", -ENOMEM);
131 obj->id = resolve_id(scn->expo, id);
134 list_add_tail(&obj->sibling, &scn->obj_head);
140 int scene_img(struct scene *scn, const char *name, uint id, char *data,
141 struct scene_obj_img **imgp)
143 struct scene_obj_img *img;
146 ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE,
147 sizeof(struct scene_obj_img),
148 (struct scene_obj **)&img);
150 return log_msg_ret("obj", ret);
160 int scene_txt(struct scene *scn, const char *name, uint id, uint str_id,
161 struct scene_obj_txt **txtp)
163 struct scene_obj_txt *txt;
166 ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
167 sizeof(struct scene_obj_txt),
168 (struct scene_obj **)&txt);
170 return log_msg_ret("obj", ret);
172 txt->str_id = str_id;
180 int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id,
181 const char *str, struct scene_obj_txt **txtp)
183 struct scene_obj_txt *txt;
186 ret = expo_str(scn->expo, name, str_id, str);
188 return log_msg_ret("str", ret);
189 if (str_id && ret != str_id)
190 return log_msg_ret("id", -EEXIST);
193 ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT,
194 sizeof(struct scene_obj_txt),
195 (struct scene_obj **)&txt);
197 return log_msg_ret("obj", ret);
199 txt->str_id = str_id;
207 int scene_txt_set_font(struct scene *scn, uint id, const char *font_name,
210 struct scene_obj_txt *txt;
212 txt = scene_obj_find(scn, id, SCENEOBJT_TEXT);
214 return log_msg_ret("find", -ENOENT);
215 txt->font_name = font_name;
216 txt->font_size = font_size;
221 int scene_obj_set_pos(struct scene *scn, uint id, int x, int y)
223 struct scene_obj *obj;
225 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
227 return log_msg_ret("find", -ENOENT);
234 int scene_obj_set_size(struct scene *scn, uint id, int w, int h)
236 struct scene_obj *obj;
238 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
240 return log_msg_ret("find", -ENOENT);
247 int scene_obj_set_hide(struct scene *scn, uint id, bool hide)
251 ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE,
252 hide ? SCENEOF_HIDE : 0);
254 return log_msg_ret("flg", ret);
259 int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set)
261 struct scene_obj *obj;
263 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
265 return log_msg_ret("find", -ENOENT);
272 int scene_obj_get_hw(struct scene *scn, uint id, int *widthp)
274 struct scene_obj *obj;
276 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
278 return log_msg_ret("find", -ENOENT);
283 case SCENEOBJT_TEXTLINE:
285 case SCENEOBJT_IMAGE: {
286 struct scene_obj_img *img = (struct scene_obj_img *)obj;
290 video_bmp_get_info(img->data, &width, &height, &bpix);
295 case SCENEOBJT_TEXT: {
296 struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
297 struct expo *exp = scn->expo;
298 struct vidconsole_bbox bbox;
302 str = expo_get_str(exp, txt->str_id);
304 return log_msg_ret("str", -ENOENT);
307 /* if there is no console, make it up */
314 ret = vidconsole_measure(scn->expo->cons, txt->font_name,
315 txt->font_size, str, &bbox);
317 return log_msg_ret("mea", ret);
329 * scene_render_background() - Render the background for an object
331 * @obj: Object to render
332 * @box_only: true to show a box around the object, but keep the normal
333 * background colour inside
335 static void scene_render_background(struct scene_obj *obj, bool box_only)
337 struct expo *exp = obj->scene->expo;
338 const struct expo_theme *theme = &exp->theme;
339 struct vidconsole_bbox bbox, label_bbox;
340 struct udevice *dev = exp->display;
341 struct video_priv *vid_priv;
342 struct udevice *cons = exp->cons;
343 struct vidconsole_colour old;
344 enum colour_idx fore, back;
345 uint inset = theme->menu_inset;
347 /* draw a background for the object */
348 if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
352 fore = VID_LIGHT_GRAY;
356 /* see if this object wants to render a background */
357 if (scene_obj_calc_bbox(obj, &bbox, &label_bbox))
360 vidconsole_push_colour(cons, fore, back, &old);
361 vid_priv = dev_get_uclass_priv(dev);
362 video_fill_part(dev, label_bbox.x0 - inset, label_bbox.y0 - inset,
363 label_bbox.x1 + inset, label_bbox.y1 + inset,
364 vid_priv->colour_fg);
365 vidconsole_pop_colour(cons, &old);
367 video_fill_part(dev, label_bbox.x0, label_bbox.y0,
368 label_bbox.x1, label_bbox.y1,
369 vid_priv->colour_bg);
374 * scene_obj_render() - Render an object
377 static int scene_obj_render(struct scene_obj *obj, bool text_mode)
379 struct scene *scn = obj->scene;
380 struct expo *exp = scn->expo;
381 const struct expo_theme *theme = &exp->theme;
382 struct udevice *dev = exp->display;
383 struct udevice *cons = text_mode ? NULL : exp->cons;
392 case SCENEOBJT_IMAGE: {
393 struct scene_obj_img *img = (struct scene_obj_img *)obj;
397 ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y,
400 return log_msg_ret("img", ret);
403 case SCENEOBJT_TEXT: {
404 struct scene_obj_txt *txt = (struct scene_obj_txt *)obj;
410 if (txt->font_name || txt->font_size) {
411 ret = vidconsole_select_font(cons,
415 ret = vidconsole_select_font(cons, NULL, 0);
417 if (ret && ret != -ENOSYS)
418 return log_msg_ret("font", ret);
419 str = expo_get_str(exp, txt->str_id);
421 struct video_priv *vid_priv;
422 struct vidconsole_colour old;
423 enum colour_idx fore, back;
425 if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) {
429 fore = VID_LIGHT_GRAY;
433 vid_priv = dev_get_uclass_priv(dev);
434 if (obj->flags & SCENEOF_POINT) {
435 vidconsole_push_colour(cons, fore, back, &old);
436 video_fill_part(dev, x - theme->menu_inset, y,
439 vid_priv->colour_bg);
441 vidconsole_set_cursor_pos(cons, x, y);
442 vidconsole_put_string(cons, str);
443 if (obj->flags & SCENEOF_POINT)
444 vidconsole_pop_colour(cons, &old);
448 case SCENEOBJT_MENU: {
449 struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
451 if (exp->popup && (obj->flags & SCENEOF_OPEN)) {
455 /* draw a background behind the menu items */
456 scene_render_background(obj, false);
459 * With a vidconsole, the text and item pointer are rendered as
460 * normal objects so we don't need to do anything here. The menu
461 * simply controls where they are positioned.
466 ret = scene_menu_display(menu);
468 return log_msg_ret("img", ret);
472 case SCENEOBJT_TEXTLINE:
473 if (obj->flags & SCENEOF_OPEN)
474 scene_render_background(obj, true);
481 int scene_arrange(struct scene *scn)
483 struct scene_obj *obj;
486 list_for_each_entry(obj, &scn->obj_head, sibling) {
489 case SCENEOBJT_IMAGE:
492 case SCENEOBJT_MENU: {
493 struct scene_obj_menu *menu;
495 menu = (struct scene_obj_menu *)obj,
496 ret = scene_menu_arrange(scn, menu);
498 return log_msg_ret("arr", ret);
501 case SCENEOBJT_TEXTLINE: {
502 struct scene_obj_textline *tline;
504 tline = (struct scene_obj_textline *)obj,
505 ret = scene_textline_arrange(scn, tline);
507 return log_msg_ret("arr", ret);
516 int scene_render_deps(struct scene *scn, uint id)
518 struct scene_obj *obj;
523 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
525 return log_msg_ret("obj", -ENOENT);
527 if (!(obj->flags & SCENEOF_HIDE)) {
528 ret = scene_obj_render(obj, false);
529 if (ret && ret != -ENOTSUPP)
530 return log_msg_ret("ren", ret);
534 case SCENEOBJT_IMAGE:
538 scene_menu_render_deps(scn,
539 (struct scene_obj_menu *)obj);
541 case SCENEOBJT_TEXTLINE:
542 scene_textline_render_deps(scn,
543 (struct scene_obj_textline *)obj);
551 int scene_render(struct scene *scn)
553 struct expo *exp = scn->expo;
554 struct scene_obj *obj;
557 list_for_each_entry(obj, &scn->obj_head, sibling) {
558 if (!(obj->flags & SCENEOF_HIDE)) {
559 ret = scene_obj_render(obj, exp->text_mode);
560 if (ret && ret != -ENOTSUPP)
561 return log_msg_ret("ren", ret);
565 /* render any highlighted object on top of the others */
566 if (scn->highlight_id && !exp->text_mode) {
567 ret = scene_render_deps(scn, scn->highlight_id);
568 if (ret && ret != -ENOTSUPP)
569 return log_msg_ret("dep", ret);
576 * send_key_obj() - Handle a keypress for moving between objects
578 * @scn: Scene to receive the key
579 * @key: Key to send (KEYCODE_UP)
580 * @event: Returns resulting event from this keypress
581 * Returns: 0 if OK, -ve on error
583 static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key,
584 struct expo_action *event)
588 while (obj != list_first_entry(&scn->obj_head, struct scene_obj,
590 obj = list_entry(obj->sibling.prev,
591 struct scene_obj, sibling);
592 if (scene_obj_can_highlight(obj)) {
593 event->type = EXPOACT_POINT_OBJ;
594 event->select.id = obj->id;
595 log_debug("up to obj %d\n", event->select.id);
601 while (!list_is_last(&obj->sibling, &scn->obj_head)) {
602 obj = list_entry(obj->sibling.next, struct scene_obj,
604 if (scene_obj_can_highlight(obj)) {
605 event->type = EXPOACT_POINT_OBJ;
606 event->select.id = obj->id;
607 log_debug("down to obj %d\n", event->select.id);
613 if (scene_obj_can_highlight(obj)) {
614 event->type = EXPOACT_OPEN;
615 event->select.id = obj->id;
616 log_debug("open obj %d\n", event->select.id);
620 event->type = EXPOACT_QUIT;
621 log_debug("obj quit\n");
626 int scene_send_key(struct scene *scn, int key, struct expo_action *event)
628 struct scene_obj *obj;
631 event->type = EXPOACT_NONE;
634 * In 'popup' mode, arrow keys move betwen objects, unless a menu is
637 if (scn->expo->popup) {
639 if (scn->highlight_id) {
640 obj = scene_obj_find(scn, scn->highlight_id,
646 if (!(obj->flags & SCENEOF_OPEN)) {
647 send_key_obj(scn, obj, key, event);
653 case SCENEOBJT_IMAGE:
656 case SCENEOBJT_MENU: {
657 struct scene_obj_menu *menu;
659 menu = (struct scene_obj_menu *)obj,
660 ret = scene_menu_send_key(scn, menu, key, event);
662 return log_msg_ret("key", ret);
665 case SCENEOBJT_TEXTLINE: {
666 struct scene_obj_textline *tline;
668 tline = (struct scene_obj_textline *)obj,
669 ret = scene_textline_send_key(scn, tline, key, event);
671 return log_msg_ret("key", ret);
678 list_for_each_entry(obj, &scn->obj_head, sibling) {
679 if (obj->type == SCENEOBJT_MENU) {
680 struct scene_obj_menu *menu;
682 menu = (struct scene_obj_menu *)obj,
683 ret = scene_menu_send_key(scn, menu, key, event);
685 return log_msg_ret("key", ret);
693 int scene_obj_calc_bbox(struct scene_obj *obj, struct vidconsole_bbox *bbox,
694 struct vidconsole_bbox *label_bbox)
698 case SCENEOBJT_IMAGE:
701 case SCENEOBJT_MENU: {
702 struct scene_obj_menu *menu = (struct scene_obj_menu *)obj;
704 scene_menu_calc_bbox(menu, bbox, label_bbox);
707 case SCENEOBJT_TEXTLINE: {
708 struct scene_obj_textline *tline;
710 tline = (struct scene_obj_textline *)obj;
711 scene_textline_calc_bbox(tline, bbox, label_bbox);
719 int scene_calc_dims(struct scene *scn, bool do_menus)
721 struct scene_obj *obj;
724 list_for_each_entry(obj, &scn->obj_head, sibling) {
728 case SCENEOBJT_IMAGE: {
732 ret = scene_obj_get_hw(scn, obj->id, &width);
734 return log_msg_ret("get", ret);
740 case SCENEOBJT_MENU: {
741 struct scene_obj_menu *menu;
744 menu = (struct scene_obj_menu *)obj;
746 ret = scene_menu_calc_dims(menu);
748 return log_msg_ret("men", ret);
752 case SCENEOBJT_TEXTLINE: {
753 struct scene_obj_textline *tline;
755 tline = (struct scene_obj_textline *)obj;
756 ret = scene_textline_calc_dims(tline);
758 return log_msg_ret("men", ret);
768 int scene_apply_theme(struct scene *scn, struct expo_theme *theme)
770 struct scene_obj *obj;
773 /* Avoid error-checking optional items */
774 scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size);
776 list_for_each_entry(obj, &scn->obj_head, sibling) {
779 case SCENEOBJT_IMAGE:
781 case SCENEOBJT_TEXTLINE:
784 scene_txt_set_font(scn, obj->id, NULL,
790 ret = scene_arrange(scn);
792 return log_msg_ret("arr", ret);
797 void scene_set_highlight_id(struct scene *scn, uint id)
799 scn->highlight_id = id;
802 void scene_highlight_first(struct scene *scn)
804 struct scene_obj *obj;
806 list_for_each_entry(obj, &scn->obj_head, sibling) {
807 if (scene_obj_can_highlight(obj)) {
808 scene_set_highlight_id(scn, obj->id);
814 static int scene_obj_open(struct scene *scn, struct scene_obj *obj)
820 case SCENEOBJT_IMAGE:
824 case SCENEOBJT_TEXTLINE:
825 ret = scene_textline_open(scn,
826 (struct scene_obj_textline *)obj);
828 return log_msg_ret("op", ret);
835 int scene_set_open(struct scene *scn, uint id, bool open)
837 struct scene_obj *obj;
840 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
842 return log_msg_ret("find", -ENOENT);
845 ret = scene_obj_open(scn, obj);
847 return log_msg_ret("op", ret);
850 ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN,
851 open ? SCENEOF_OPEN : 0);
853 return log_msg_ret("flg", ret);
858 int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter,
861 struct scene_obj *obj;
863 list_for_each_entry(obj, &scn->obj_head, sibling) {
866 ret = iter(obj, priv);
868 return log_msg_ret("itr", ret);
874 int scene_bbox_union(struct scene *scn, uint id, int inset,
875 struct vidconsole_bbox *bbox)
877 struct scene_obj *obj;
881 obj = scene_obj_find(scn, id, SCENEOBJT_NONE);
883 return log_msg_ret("obj", -ENOENT);
885 bbox->x0 = min(bbox->x0, obj->dim.x - inset);
886 bbox->y0 = min(bbox->y0, obj->dim.y);
887 bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset);
888 bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
890 bbox->x0 = obj->dim.x - inset;
891 bbox->y0 = obj->dim.y;
892 bbox->x1 = obj->dim.x + obj->dim.w + inset;
893 bbox->y1 = obj->dim.y + obj->dim.h;