// SPDX-License-Identifier: GPL-2.0+ /* * Implementation of a scene, a collection of text/image/menu items in an expo * * Copyright 2022 Google LLC * Written by Simon Glass */ #define LOG_CATEGORY LOGC_EXPO #include #include #include #include #include #include #include #include #include "scene_internal.h" int scene_new(struct expo *exp, const char *name, uint id, struct scene **scnp) { struct scene *scn; scn = calloc(1, sizeof(struct scene)); if (!scn) return log_msg_ret("expo", -ENOMEM); scn->name = strdup(name); if (!scn->name) { free(scn); return log_msg_ret("name", -ENOMEM); } abuf_init(&scn->buf); if (!abuf_realloc(&scn->buf, EXPO_MAX_CHARS + 1)) { free(scn->name); free(scn); return log_msg_ret("buf", -ENOMEM); } abuf_init(&scn->entry_save); INIT_LIST_HEAD(&scn->obj_head); scn->id = resolve_id(exp, id); scn->expo = exp; list_add_tail(&scn->sibling, &exp->scene_head); *scnp = scn; return scn->id; } void scene_obj_destroy(struct scene_obj *obj) { if (obj->type == SCENEOBJT_MENU) scene_menu_destroy((struct scene_obj_menu *)obj); free(obj->name); free(obj); } void scene_destroy(struct scene *scn) { struct scene_obj *obj, *next; list_for_each_entry_safe(obj, next, &scn->obj_head, sibling) scene_obj_destroy(obj); abuf_uninit(&scn->entry_save); abuf_uninit(&scn->buf); free(scn->name); free(scn); } int scene_title_set(struct scene *scn, uint id) { scn->title_id = id; return 0; } int scene_obj_count(struct scene *scn) { struct scene_obj *obj; int count = 0; list_for_each_entry(obj, &scn->obj_head, sibling) count++; return count; } void *scene_obj_find(const struct scene *scn, uint id, enum scene_obj_t type) { struct scene_obj *obj; list_for_each_entry(obj, &scn->obj_head, sibling) { if (obj->id == id && (type == SCENEOBJT_NONE || obj->type == type)) return obj; } return NULL; } void *scene_obj_find_by_name(struct scene *scn, const char *name) { struct scene_obj *obj; list_for_each_entry(obj, &scn->obj_head, sibling) { if (!strcmp(name, obj->name)) return obj; } return NULL; } int scene_obj_add(struct scene *scn, const char *name, uint id, enum scene_obj_t type, uint size, struct scene_obj **objp) { struct scene_obj *obj; obj = calloc(1, size); if (!obj) return log_msg_ret("obj", -ENOMEM); obj->name = strdup(name); if (!obj->name) { free(obj); return log_msg_ret("name", -ENOMEM); } obj->id = resolve_id(scn->expo, id); obj->scene = scn; obj->type = type; list_add_tail(&obj->sibling, &scn->obj_head); *objp = obj; return obj->id; } int scene_img(struct scene *scn, const char *name, uint id, char *data, struct scene_obj_img **imgp) { struct scene_obj_img *img; int ret; ret = scene_obj_add(scn, name, id, SCENEOBJT_IMAGE, sizeof(struct scene_obj_img), (struct scene_obj **)&img); if (ret < 0) return log_msg_ret("obj", ret); img->data = data; if (imgp) *imgp = img; return img->obj.id; } int scene_txt(struct scene *scn, const char *name, uint id, uint str_id, struct scene_obj_txt **txtp) { struct scene_obj_txt *txt; int ret; ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT, sizeof(struct scene_obj_txt), (struct scene_obj **)&txt); if (ret < 0) return log_msg_ret("obj", ret); txt->str_id = str_id; if (txtp) *txtp = txt; return txt->obj.id; } int scene_txt_str(struct scene *scn, const char *name, uint id, uint str_id, const char *str, struct scene_obj_txt **txtp) { struct scene_obj_txt *txt; int ret; ret = expo_str(scn->expo, name, str_id, str); if (ret < 0) return log_msg_ret("str", ret); if (str_id && ret != str_id) return log_msg_ret("id", -EEXIST); str_id = ret; ret = scene_obj_add(scn, name, id, SCENEOBJT_TEXT, sizeof(struct scene_obj_txt), (struct scene_obj **)&txt); if (ret < 0) return log_msg_ret("obj", ret); txt->str_id = str_id; if (txtp) *txtp = txt; return txt->obj.id; } int scene_txt_set_font(struct scene *scn, uint id, const char *font_name, uint font_size) { struct scene_obj_txt *txt; txt = scene_obj_find(scn, id, SCENEOBJT_TEXT); if (!txt) return log_msg_ret("find", -ENOENT); txt->font_name = font_name; txt->font_size = font_size; return 0; } int scene_obj_set_pos(struct scene *scn, uint id, int x, int y) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); obj->dim.x = x; obj->dim.y = y; return 0; } int scene_obj_set_size(struct scene *scn, uint id, int w, int h) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); obj->dim.w = w; obj->dim.h = h; return 0; } int scene_obj_set_hide(struct scene *scn, uint id, bool hide) { int ret; ret = scene_obj_flag_clrset(scn, id, SCENEOF_HIDE, hide ? SCENEOF_HIDE : 0); if (ret) return log_msg_ret("flg", ret); return 0; } int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); obj->flags &= ~clr; obj->flags |= set; return 0; } int scene_obj_get_hw(struct scene *scn, uint id, int *widthp) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_MENU: case SCENEOBJT_TEXTLINE: break; case SCENEOBJT_IMAGE: { struct scene_obj_img *img = (struct scene_obj_img *)obj; ulong width, height; uint bpix; video_bmp_get_info(img->data, &width, &height, &bpix); if (widthp) *widthp = width; return height; } case SCENEOBJT_TEXT: { struct scene_obj_txt *txt = (struct scene_obj_txt *)obj; struct expo *exp = scn->expo; struct vidconsole_bbox bbox; const char *str; int len, ret; str = expo_get_str(exp, txt->str_id); if (!str) return log_msg_ret("str", -ENOENT); len = strlen(str); /* if there is no console, make it up */ if (!exp->cons) { if (widthp) *widthp = 8 * len; return 16; } ret = vidconsole_measure(scn->expo->cons, txt->font_name, txt->font_size, str, &bbox); if (ret) return log_msg_ret("mea", ret); if (widthp) *widthp = bbox.x1; return bbox.y1; } } return 0; } /** * scene_render_background() - Render the background for an object * * @obj: Object to render * @box_only: true to show a box around the object, but keep the normal * background colour inside */ static void scene_render_background(struct scene_obj *obj, bool box_only) { struct expo *exp = obj->scene->expo; const struct expo_theme *theme = &exp->theme; struct vidconsole_bbox bbox, label_bbox; struct udevice *dev = exp->display; struct video_priv *vid_priv; struct udevice *cons = exp->cons; struct vidconsole_colour old; enum colour_idx fore, back; uint inset = theme->menu_inset; /* draw a background for the object */ if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) { fore = VID_BLACK; back = VID_WHITE; } else { fore = VID_LIGHT_GRAY; back = VID_BLACK; } /* see if this object wants to render a background */ if (scene_obj_calc_bbox(obj, &bbox, &label_bbox)) return; vidconsole_push_colour(cons, fore, back, &old); vid_priv = dev_get_uclass_priv(dev); video_fill_part(dev, label_bbox.x0 - inset, label_bbox.y0 - inset, label_bbox.x1 + inset, label_bbox.y1 + inset, vid_priv->colour_fg); vidconsole_pop_colour(cons, &old); if (box_only) { video_fill_part(dev, label_bbox.x0, label_bbox.y0, label_bbox.x1, label_bbox.y1, vid_priv->colour_bg); } } /** * scene_obj_render() - Render an object * */ static int scene_obj_render(struct scene_obj *obj, bool text_mode) { struct scene *scn = obj->scene; struct expo *exp = scn->expo; const struct expo_theme *theme = &exp->theme; struct udevice *dev = exp->display; struct udevice *cons = text_mode ? NULL : exp->cons; int x, y, ret; x = obj->dim.x; y = obj->dim.y; switch (obj->type) { case SCENEOBJT_NONE: break; case SCENEOBJT_IMAGE: { struct scene_obj_img *img = (struct scene_obj_img *)obj; if (!cons) return -ENOTSUPP; ret = video_bmp_display(dev, map_to_sysmem(img->data), x, y, true); if (ret < 0) return log_msg_ret("img", ret); break; } case SCENEOBJT_TEXT: { struct scene_obj_txt *txt = (struct scene_obj_txt *)obj; const char *str; if (!cons) return -ENOTSUPP; if (txt->font_name || txt->font_size) { ret = vidconsole_select_font(cons, txt->font_name, txt->font_size); } else { ret = vidconsole_select_font(cons, NULL, 0); } if (ret && ret != -ENOSYS) return log_msg_ret("font", ret); str = expo_get_str(exp, txt->str_id); if (str) { struct video_priv *vid_priv; struct vidconsole_colour old; enum colour_idx fore, back; if (CONFIG_IS_ENABLED(SYS_WHITE_ON_BLACK)) { fore = VID_BLACK; back = VID_WHITE; } else { fore = VID_LIGHT_GRAY; back = VID_BLACK; } vid_priv = dev_get_uclass_priv(dev); if (obj->flags & SCENEOF_POINT) { vidconsole_push_colour(cons, fore, back, &old); video_fill_part(dev, x - theme->menu_inset, y, x + obj->dim.w, y + obj->dim.h, vid_priv->colour_bg); } vidconsole_set_cursor_pos(cons, x, y); vidconsole_put_string(cons, str); if (obj->flags & SCENEOF_POINT) vidconsole_pop_colour(cons, &old); } break; } case SCENEOBJT_MENU: { struct scene_obj_menu *menu = (struct scene_obj_menu *)obj; if (exp->popup && (obj->flags & SCENEOF_OPEN)) { if (!cons) return -ENOTSUPP; /* draw a background behind the menu items */ scene_render_background(obj, false); } /* * With a vidconsole, the text and item pointer are rendered as * normal objects so we don't need to do anything here. The menu * simply controls where they are positioned. */ if (cons) return -ENOTSUPP; ret = scene_menu_display(menu); if (ret < 0) return log_msg_ret("img", ret); break; } case SCENEOBJT_TEXTLINE: if (obj->flags & SCENEOF_OPEN) scene_render_background(obj, true); break; } return 0; } int scene_arrange(struct scene *scn) { struct scene_obj *obj; int ret; list_for_each_entry(obj, &scn->obj_head, sibling) { switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, ret = scene_menu_arrange(scn, menu); if (ret) return log_msg_ret("arr", ret); break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj, ret = scene_textline_arrange(scn, tline); if (ret) return log_msg_ret("arr", ret); break; } } } return 0; } int scene_render_deps(struct scene *scn, uint id) { struct scene_obj *obj; int ret; if (!id) return 0; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("obj", -ENOENT); if (!(obj->flags & SCENEOF_HIDE)) { ret = scene_obj_render(obj, false); if (ret && ret != -ENOTSUPP) return log_msg_ret("ren", ret); switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: break; case SCENEOBJT_MENU: scene_menu_render_deps(scn, (struct scene_obj_menu *)obj); break; case SCENEOBJT_TEXTLINE: scene_textline_render_deps(scn, (struct scene_obj_textline *)obj); break; } } return 0; } int scene_render(struct scene *scn) { struct expo *exp = scn->expo; struct scene_obj *obj; int ret; list_for_each_entry(obj, &scn->obj_head, sibling) { if (!(obj->flags & SCENEOF_HIDE)) { ret = scene_obj_render(obj, exp->text_mode); if (ret && ret != -ENOTSUPP) return log_msg_ret("ren", ret); } } /* render any highlighted object on top of the others */ if (scn->highlight_id && !exp->text_mode) { ret = scene_render_deps(scn, scn->highlight_id); if (ret && ret != -ENOTSUPP) return log_msg_ret("dep", ret); } return 0; } /** * send_key_obj() - Handle a keypress for moving between objects * * @scn: Scene to receive the key * @key: Key to send (KEYCODE_UP) * @event: Returns resulting event from this keypress * Returns: 0 if OK, -ve on error */ static void send_key_obj(struct scene *scn, struct scene_obj *obj, int key, struct expo_action *event) { switch (key) { case BKEY_UP: while (obj != list_first_entry(&scn->obj_head, struct scene_obj, sibling)) { obj = list_entry(obj->sibling.prev, struct scene_obj, sibling); if (scene_obj_can_highlight(obj)) { event->type = EXPOACT_POINT_OBJ; event->select.id = obj->id; log_debug("up to obj %d\n", event->select.id); break; } } break; case BKEY_DOWN: while (!list_is_last(&obj->sibling, &scn->obj_head)) { obj = list_entry(obj->sibling.next, struct scene_obj, sibling); if (scene_obj_can_highlight(obj)) { event->type = EXPOACT_POINT_OBJ; event->select.id = obj->id; log_debug("down to obj %d\n", event->select.id); break; } } break; case BKEY_SELECT: if (scene_obj_can_highlight(obj)) { event->type = EXPOACT_OPEN; event->select.id = obj->id; log_debug("open obj %d\n", event->select.id); } break; case BKEY_QUIT: event->type = EXPOACT_QUIT; log_debug("obj quit\n"); break; } } int scene_send_key(struct scene *scn, int key, struct expo_action *event) { struct scene_obj *obj; int ret; event->type = EXPOACT_NONE; /* * In 'popup' mode, arrow keys move betwen objects, unless a menu is * opened */ if (scn->expo->popup) { obj = NULL; if (scn->highlight_id) { obj = scene_obj_find(scn, scn->highlight_id, SCENEOBJT_NONE); } if (!obj) return 0; if (!(obj->flags & SCENEOF_OPEN)) { send_key_obj(scn, obj, key, event); return 0; } switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, ret = scene_menu_send_key(scn, menu, key, event); if (ret) return log_msg_ret("key", ret); break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj, ret = scene_textline_send_key(scn, tline, key, event); if (ret) return log_msg_ret("key", ret); break; } } return 0; } list_for_each_entry(obj, &scn->obj_head, sibling) { if (obj->type == SCENEOBJT_MENU) { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, ret = scene_menu_send_key(scn, menu, key, event); if (ret) return log_msg_ret("key", ret); break; } } return 0; } int scene_obj_calc_bbox(struct scene_obj *obj, struct vidconsole_bbox *bbox, struct vidconsole_bbox *label_bbox) { switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: return -ENOSYS; case SCENEOBJT_MENU: { struct scene_obj_menu *menu = (struct scene_obj_menu *)obj; scene_menu_calc_bbox(menu, bbox, label_bbox); break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj; scene_textline_calc_bbox(tline, bbox, label_bbox); break; } } return 0; } int scene_calc_dims(struct scene *scn, bool do_menus) { struct scene_obj *obj; int ret; list_for_each_entry(obj, &scn->obj_head, sibling) { switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_TEXT: case SCENEOBJT_IMAGE: { int width; if (!do_menus) { ret = scene_obj_get_hw(scn, obj->id, &width); if (ret < 0) return log_msg_ret("get", ret); obj->dim.w = width; obj->dim.h = ret; } break; } case SCENEOBJT_MENU: { struct scene_obj_menu *menu; if (do_menus) { menu = (struct scene_obj_menu *)obj; ret = scene_menu_calc_dims(menu); if (ret) return log_msg_ret("men", ret); } break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj; ret = scene_textline_calc_dims(tline); if (ret) return log_msg_ret("men", ret); break; } } } return 0; } int scene_apply_theme(struct scene *scn, struct expo_theme *theme) { struct scene_obj *obj; int ret; /* Avoid error-checking optional items */ scene_txt_set_font(scn, scn->title_id, NULL, theme->font_size); list_for_each_entry(obj, &scn->obj_head, sibling) { switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_MENU: case SCENEOBJT_TEXTLINE: break; case SCENEOBJT_TEXT: scene_txt_set_font(scn, obj->id, NULL, theme->font_size); break; } } ret = scene_arrange(scn); if (ret) return log_msg_ret("arr", ret); return 0; } void scene_set_highlight_id(struct scene *scn, uint id) { scn->highlight_id = id; } void scene_highlight_first(struct scene *scn) { struct scene_obj *obj; list_for_each_entry(obj, &scn->obj_head, sibling) { if (scene_obj_can_highlight(obj)) { scene_set_highlight_id(scn, obj->id); return; } } } static int scene_obj_open(struct scene *scn, struct scene_obj *obj) { int ret; switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_MENU: case SCENEOBJT_TEXT: break; case SCENEOBJT_TEXTLINE: ret = scene_textline_open(scn, (struct scene_obj_textline *)obj); if (ret) return log_msg_ret("op", ret); break; } return 0; } int scene_set_open(struct scene *scn, uint id, bool open) { struct scene_obj *obj; int ret; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); if (open) { ret = scene_obj_open(scn, obj); if (ret) return log_msg_ret("op", ret); } ret = scene_obj_flag_clrset(scn, id, SCENEOF_OPEN, open ? SCENEOF_OPEN : 0); if (ret) return log_msg_ret("flg", ret); return 0; } int scene_iter_objs(struct scene *scn, expo_scene_obj_iterator iter, void *priv) { struct scene_obj *obj; list_for_each_entry(obj, &scn->obj_head, sibling) { int ret; ret = iter(obj, priv); if (ret) return log_msg_ret("itr", ret); } return 0; } int scene_bbox_union(struct scene *scn, uint id, int inset, struct vidconsole_bbox *bbox) { struct scene_obj *obj; if (!id) return 0; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("obj", -ENOENT); if (bbox->valid) { bbox->x0 = min(bbox->x0, obj->dim.x - inset); bbox->y0 = min(bbox->y0, obj->dim.y); bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w + inset); bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h); } else { bbox->x0 = obj->dim.x - inset; bbox->y0 = obj->dim.y; bbox->x1 = obj->dim.x + obj->dim.w + inset; bbox->y1 = obj->dim.y + obj->dim.h; bbox->valid = true; } return 0; }