// 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 #include "scene_internal.h" static const char *const scene_flag_names[] = { "hide", "point", "open", "size_valid", "sync_pos", "sync_size", "sync_width", "sync_bbox", "manual", "dirty", }; static const char *const scene_obj_type_names[] = { "none", "image", "text", "box", "textedit", "menu", "textline", }; 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); } if (!abuf_init_size(&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_obj_count(struct scene *scn) { return list_count_nodes(&scn->obj_head); } 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_generic_init(struct expo *exp, struct scene_txt_generic *gen, const char *name, uint str_id, const char *str) { int ret; if (str) { ret = expo_str(exp, 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; } else { ret = resolve_id(exp, str_id); if (ret < 0) return log_msg_ret("nst", ret); if (str_id && ret != str_id) return log_msg_ret("nid", -EEXIST); } gen->str_id = str_id; alist_init_struct(&gen->lines, struct vidconsole_mline); return 0; } 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); ret = scene_txt_generic_init(scn->expo, &txt->gen, name, str_id, NULL); if (ret) return log_msg_ret("stg", ret); 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 = 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); ret = scene_txt_generic_init(scn->expo, &txt->gen, name, str_id, str); if (ret) return log_msg_ret("tsg", ret); if (txtp) *txtp = txt; return txt->obj.id; } int scene_box(struct scene *scn, const char *name, uint id, uint width, bool fill, struct scene_obj_box **boxp) { struct scene_obj_box *box; int ret; ret = scene_obj_add(scn, name, id, SCENEOBJT_BOX, sizeof(struct scene_obj_box), (struct scene_obj **)&box); if (ret < 0) return log_msg_ret("obj", ret); box->width = width; box->fill = fill; if (boxp) *boxp = box; return box->obj.id; } int scene_box_set_fill(struct scene *scn, uint id, bool fill) { struct scene_obj_box *box; box = scene_obj_find(scn, id, SCENEOBJT_BOX); if (!box) return log_msg_ret("find", -ENOENT); box->fill = fill; return 0; } 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->gen.font_name = font_name; txt->gen.font_size = font_size; return 0; } int scene_obj_set_pos(struct scene *scn, uint id, int x, int y) { struct scene_obj *obj; int w, h; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); w = obj->req_bbox.x1 - obj->req_bbox.x0; h = obj->req_bbox.y1 - obj->req_bbox.y0; obj->req_bbox.x0 = x; obj->req_bbox.y0 = y; obj->req_bbox.x1 = obj->req_bbox.x0 + w; obj->req_bbox.y1 = obj->req_bbox.y0 + h; obj->flags |= SCENEOF_SYNC_POS; 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); log_debug("obj %s w %d h %d\n", obj->name, w, h); obj->req_bbox.x1 = obj->req_bbox.x0 + w; obj->req_bbox.y1 = obj->req_bbox.y0 + h; obj->flags |= SCENEOF_SIZE_VALID | SCENEOF_SYNC_SIZE; return 0; } int scene_obj_set_width_flags(struct scene *scn, uint id, int w, uint flags) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); obj->req_bbox.x1 = obj->req_bbox.x0 + w; obj->flags |= flags; return 0; } int scene_obj_set_width(struct scene *scn, uint id, int w) { return scene_obj_set_width_flags(scn, id, w, SCENEOF_SYNC_WIDTH); } int scene_obj_set_bbox(struct scene *scn, uint id, int x0, int y0, int x1, int y1) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("find", -ENOENT); obj->req_bbox.x0 = x0; obj->req_bbox.y0 = y0; obj->req_bbox.x1 = x1; obj->req_bbox.y1 = y1; obj->flags |= SCENEOF_SIZE_VALID | SCENEOF_SYNC_BBOX; return 0; } int scene_obj_set_halign(struct scene *scn, uint id, enum scene_obj_align aln) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("osh", -ENOENT); obj->horiz = aln; return 0; } int scene_obj_set_valign(struct scene *scn, uint id, enum scene_obj_align aln) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("osv", -ENOENT); obj->vert = aln; 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_set_manual(struct scene *scn, uint id, bool manual) { int ret; ret = scene_obj_flag_clrset(scn, id, SCENEOF_MANUAL, manual ? SCENEOF_MANUAL : 0); if (ret) return log_msg_ret("fla", 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; } static void handle_alignment(enum scene_obj_align horiz, enum scene_obj_align vert, struct vid_bbox *bbox, struct scene_obj_dims *dims, int xsize, int ysize, struct scene_obj_offset *offset) { int width, height; if (bbox->x1 == SCENEOB_DISPLAY_MAX) bbox->x1 = xsize ?: 1280; if (bbox->y1 == SCENEOB_DISPLAY_MAX) bbox->y1 = ysize ?: 1024; width = bbox->x1 - bbox->x0; height = bbox->y1 - bbox->y0; switch (horiz) { case SCENEOA_CENTRE: offset->xofs = (width - dims->x) / 2; break; case SCENEOA_RIGHT: offset->xofs = width - dims->x; break; case SCENEOA_LEFT: offset->xofs = 0; break; } switch (vert) { case SCENEOA_CENTRE: offset->yofs = (height - dims->y) / 2; break; case SCENEOA_BOTTOM: offset->yofs = height - dims->y; break; case SCENEOA_TOP: default: offset->yofs = 0; break; } } 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: case SCENEOBJT_BOX: 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: case SCENEOBJT_TEXTEDIT: { struct scene_txt_generic *gen; struct expo *exp = scn->expo; struct vidconsole_bbox bbox; int len, ret, limit; const char *str; if (obj->type == SCENEOBJT_TEXT) gen = &((struct scene_obj_txt *)obj)->gen; else gen = &((struct scene_obj_txtedit *)obj)->gen; str = expo_get_str(exp, gen->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; } limit = obj->flags & SCENEOF_SIZE_VALID ? obj->req_bbox.x1 - obj->req_bbox.x0 : -1; log_debug("obj %s limit %d\n", obj->name, limit); ret = vidconsole_measure(scn->expo->cons, gen->font_name, gen->font_size, str, limit, &bbox, &gen->lines); 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 * @cur_item: true to render the background only for the current menu item */ static void scene_render_background(struct scene_obj *obj, bool box_only, bool cur_item) { struct vidconsole_bbox bbox[SCENEBB_count], *sel; struct expo *exp = obj->scene->expo; const struct expo_theme *theme = &exp->theme; 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; vid_priv = dev_get_uclass_priv(dev); /* draw a background for the object */ if (vid_priv->white_on_black) { fore = VID_DARK_GREY; 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)) return; sel = cur_item ? &bbox[SCENEBB_curitem] : &bbox[SCENEBB_label]; if (!sel->valid) return; vidconsole_push_colour(cons, fore, back, &old); video_fill_part(dev, sel->x0 - inset, sel->y0 - inset, sel->x1 + inset, sel->y1 + inset, vid_priv->colour_fg); vidconsole_pop_colour(cons, &old); if (box_only) { video_fill_part(dev, sel->x0, sel->y0, sel->x1, sel->y1, vid_priv->colour_bg); } } static void draw_string(struct udevice *cons, const char *str, int len, bool password) { if (password) { int i; for (i = 0; i < len; i++) vidconsole_put_char(cons, '*'); } else { vidconsole_put_stringn(cons, str, len); } } static int scene_txt_render(struct expo *exp, struct udevice *dev, struct udevice *cons, struct scene_obj *obj, struct scene_txt_generic *gen, int x, int y, int menu_inset) { const struct vidconsole_mline *mline, *last; struct video_priv *vid_priv; struct vidconsole_colour old; enum colour_idx fore, back; struct scene_obj_dims dims; struct vid_bbox bbox; const char *str; int ret; if (!cons) return -ENOTSUPP; if (gen->font_name || gen->font_size) { ret = vidconsole_select_font(cons, gen->font_name, gen->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, gen->str_id); if (!str) return 0; vid_priv = dev_get_uclass_priv(dev); if (vid_priv->white_on_black) { fore = VID_BLACK; back = VID_WHITE; } else { fore = VID_LIGHT_GRAY; back = VID_BLACK; } if (obj->flags & SCENEOF_POINT) { int inset; inset = exp->popup ? menu_inset : 0; vidconsole_push_colour(cons, fore, back, &old); video_fill_part(dev, x - inset, y, obj->bbox.x1 + inset, obj->bbox.y1, vid_priv->colour_bg); } mline = alist_get(&gen->lines, 0, typeof(*mline)); last = alist_get(&gen->lines, gen->lines.count - 1, typeof(*mline)); if (mline) dims.y = last->bbox.y1 - mline->bbox.y0; bbox.y0 = obj->bbox.y0; bbox.y1 = obj->bbox.y1; if (!mline) { vidconsole_set_cursor_pos(cons, x, y); draw_string(cons, str, strlen(str), obj->flags & SCENEOF_PASSWORD); } alist_for_each(mline, &gen->lines) { struct scene_obj_offset offset; bbox.x0 = obj->bbox.x0; bbox.x1 = obj->bbox.x1; dims.x = mline->bbox.x1 - mline->bbox.x0; handle_alignment(obj->horiz, obj->vert, &bbox, &dims, obj->bbox.x1 - obj->bbox.x0, obj->bbox.y1 - obj->bbox.y0, &offset); x = obj->bbox.x0 + offset.xofs; y = obj->bbox.y0 + offset.yofs + mline->bbox.y0; if (y > bbox.y1) break; /* clip this line and any following */ vidconsole_set_cursor_pos(cons, x, y); draw_string(cons, str + mline->start, mline->len, obj->flags & SCENEOF_PASSWORD); } if (obj->flags & SCENEOF_POINT) vidconsole_pop_colour(cons, &old); return 0; } /** * scene_obj_render() - Render an object * * @obj: Object to render * @text_mode: true to use text mode * Return: 0 if OK, -ve on error */ 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; struct video_priv *vid_priv; int x, y, ret; y = obj->bbox.y0; x = obj->bbox.x0 + obj->ofs.xofs; vid_priv = dev_get_uclass_priv(dev); 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; ret = scene_txt_render(exp, dev, cons, obj, &txt->gen, x, y, theme->menu_inset); break; } case SCENEOBJT_MENU: { struct scene_obj_menu *menu = (struct scene_obj_menu *)obj; if (exp->popup) { if (obj->flags & SCENEOF_OPEN) { if (!cons) return -ENOTSUPP; /* draw a background behind the menu items */ scene_render_background(obj, false, false); } } else if (exp->show_highlight) { /* do nothing */ } /* * 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, false); break; case SCENEOBJT_BOX: { struct scene_obj_box *box = (struct scene_obj_box *)obj; video_draw_box(dev, obj->bbox.x0, obj->bbox.y0, obj->bbox.x1, obj->bbox.y1, box->width, vid_priv->colour_fg, box->fill); break; } case SCENEOBJT_TEXTEDIT: { struct scene_obj_txtedit *ted = (struct scene_obj_txtedit *)obj; ret = scene_txt_render(exp, dev, cons, obj, &ted->gen, x, y, theme->menu_inset); break; } } return 0; } int scene_calc_arrange(struct scene *scn, struct expo_arrange_info *arr) { struct scene_obj *obj; arr->label_width = 0; list_for_each_entry(obj, &scn->obj_head, sibling) { uint label_id = 0; int width; switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: case SCENEOBJT_TEXTEDIT: break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, label_id = menu->title_id; break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj, label_id = tline->label_id; break; } } if (label_id) { int ret; ret = scene_obj_get_hw(scn, label_id, &width); if (ret < 0) return log_msg_ret("hei", ret); arr->label_width = max(arr->label_width, width); } } return 0; } /** * scene_set_default_bbox() - Set a default for each object's size * * If there is not already a size, use the dims property to set one */ static int scene_set_default_bbox(struct scene *scn) { struct scene_obj *obj; list_for_each_entry(obj, &scn->obj_head, sibling) { switch (obj->type) { case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: if (obj->flags & SCENEOF_SIZE_VALID) break; obj->req_bbox.x1 = obj->req_bbox.x0 + obj->dims.x; obj->req_bbox.y1 = obj->req_bbox.y0 + obj->dims.y; obj->flags |= SCENEOF_SYNC_SIZE; break; default: break; } } return 0; } int scene_arrange(struct scene *scn) { struct expo_arrange_info arr; int xsize = 0, ysize = 0; struct scene_obj *obj; struct udevice *dev; int ret; dev = scn->expo->display; if (dev) { struct video_priv *priv = dev_get_uclass_priv(dev); xsize = priv->xsize; ysize = priv->ysize; } ret = scene_calc_dims(scn); if (ret) return log_msg_ret("scd", ret); ret = scene_set_default_bbox(scn); if (ret) return log_msg_ret("sce", ret); ret = scene_calc_arrange(scn, &arr); if (ret < 0) return log_msg_ret("arr", ret); list_for_each_entry(obj, &scn->obj_head, sibling) { handle_alignment(obj->horiz, obj->vert, &obj->bbox, &obj->dims, xsize, ysize, &obj->ofs); if (obj->flags & SCENEOF_MANUAL) continue; switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: case SCENEOBJT_TEXTEDIT: break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, ret = scene_menu_arrange(scn, &arr, 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, &arr, tline); if (ret) return log_msg_ret("arr", ret); break; } } } ret = scene_sync_bbox(scn); if (ret) return log_msg_ret("saf", ret); return 0; } int scene_render_obj(struct scene *scn, uint id) { struct scene_obj *obj; int ret; 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); } 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: case SCENEOBJT_BOX: case SCENEOBJT_TEXTEDIT: 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; } /** * scene_get_dirty_bbox() - Get bounding box of all dirty objects in a scene * * @scn: Scene to scan * @bbox: Returns bounding box of all dirty objects * Return: 0 if dirty objects found, -ENOENT if no dirty objects */ static int scene_get_dirty_bbox(struct scene *scn, struct vid_bbox *bbox) { struct scene_obj *obj; bool found_dirty = false; list_for_each_entry(obj, &scn->obj_head, sibling) { if (obj->flags & SCENEOF_DIRTY) { if (!found_dirty) { /* First dirty object - initialize bbox */ *bbox = obj->bbox; found_dirty = true; } else { /* Expand bbox to include this object */ if (obj->bbox.x0 < bbox->x0) bbox->x0 = obj->bbox.x0; if (obj->bbox.y0 < bbox->y0) bbox->y0 = obj->bbox.y0; if (obj->bbox.x1 > bbox->x1) bbox->x1 = obj->bbox.x1; if (obj->bbox.y1 > bbox->y1) bbox->y1 = obj->bbox.y1; } } } return found_dirty ? 0 : -ENOENT; } /** * bbox_intersects() - Check if two bounding boxes intersect * * @bbox1: First bounding box * @bbox2: Second bounding box * Return: true if bounding boxes intersect, false otherwise */ static bool bbox_intersects(const struct vid_bbox *bbox1, const struct vid_bbox *bbox2) { return !(bbox1->x1 <= bbox2->x0 || bbox2->x1 <= bbox1->x0 || bbox1->y1 <= bbox2->y0 || bbox2->y1 <= bbox1->y0); } int scene_render(struct scene *scn, bool dirty_only) { struct expo *exp = scn->expo; struct scene_obj *obj; struct vid_bbox dirty_bbox; int ret; /* Get bounding box of dirty objects and add to expo damage */ ret = scene_get_dirty_bbox(scn, &dirty_bbox); if (!ret) expo_damage_add(exp, &dirty_bbox); list_for_each_entry(obj, &scn->obj_head, sibling) { bool render = true; if (obj->flags & SCENEOF_HIDE) continue; /* render objects that intersect with dirty bbox */ if (dirty_only && !ret) render = bbox_intersects(&obj->bbox, &dirty_bbox); if (render) { 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 * @obj: Object 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 *cur, *obj; int ret; event->type = EXPOACT_NONE; /* * In 'popup' mode, arrow keys move betwen objects, unless a menu or * textline is opened */ cur = NULL; if (scn->highlight_id) cur = scene_obj_find(scn, scn->highlight_id, SCENEOBJT_NONE); if (scn->expo->popup) { if (!cur) return 0; if (!(cur->flags & SCENEOF_OPEN)) { send_key_obj(scn, cur, key, event); return 0; } switch (cur->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)cur, 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 *)cur, ret = scene_textline_send_key(scn, tline, key, event); if (ret) return log_msg_ret("key", ret); break; } case SCENEOBJT_TEXTEDIT: /* TODO(sjg@chromium.org): Implement this */ break; } return 0; } if (cur && cur->type == SCENEOBJT_TEXTLINE) { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)cur; ret = scene_textline_send_key(scn, tline, key, event); if (ret) return log_msg_ret("key", ret); 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; } /** * is_within() - check if a point is considered within an object ID * * @scn: Scene to check * @obj: object to check * @x: X coordinate of the point * @y: Y coordinate of the point * Return: true if the point is considered within the object, false if not */ static bool is_within(const struct scene_obj *obj, int x, int y) { /* Check if point (x, y) is within object's bounding box */ return (x >= obj->bbox.x0 && x <= obj->bbox.x1 && y >= obj->bbox.y0 && y <= obj->bbox.y1); } bool scene_within(const struct scene *scn, uint id, int x, int y) { struct scene_obj *obj; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) { log_debug("Cannot find id %d\n", id); return false; } log_debug("- id %d: '%s' bbox x0 %d y0 %d x1 %d y1 %d\n", id, obj->name, obj->bbox.x0, obj->bbox.y0, obj->bbox.x1, obj->bbox.x1); return is_within(obj, x, y); } bool scene_obj_within(const struct scene *scn, struct scene_obj *obj, int x, int y) { bool within = false; switch (obj->type) { case SCENEOBJT_NONE: break; case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: within = is_within(obj, x, y); break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, within = scene_menu_within(scn, menu, x, y); break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj, within = scene_textline_within(scn, tline, x, y); break; } case SCENEOBJT_TEXTEDIT: /* TODO(sjg@chromium.org): Implement this */ break; } return within; } struct scene_obj *scene_find_obj_within(const struct scene *scn, int x, int y, bool reverse, bool allow_any) { struct scene_obj *obj; log_debug("within: x %d y %d reverse %d allow_any %d\n", x, y, reverse, allow_any); if (reverse) { list_for_each_entry_reverse(obj, &scn->obj_head, sibling) { log_debug(" - obj %d '%s' can_highlight %d within %d\n", obj->id, obj->name, scene_obj_can_highlight(obj), scene_obj_within(scn, obj, x, y)); if ((allow_any || scene_obj_can_highlight(obj)) && scene_obj_within(scn, obj, x, y)) { log_debug("- returning obj %d '%s'\n", obj->id, obj->name); return obj; } } } else { list_for_each_entry(obj, &scn->obj_head, sibling) { log_debug(" - obj %d '%s' can_highlight %d within %d\n", obj->id, obj->name, scene_obj_can_highlight(obj), scene_obj_within(scn, obj, x, y)); if ((allow_any || scene_obj_can_highlight(obj)) && scene_obj_within(scn, obj, x, y)) { log_debug("- returning obj %d '%s'\n", obj->id, obj->name); return obj; } } } log_debug("- no object\n"); return NULL; } /** * send_click_obj() - Handle a click for moving between objects * * On entry, scn->highlight_id is set to a menu/textline, but the object is not * open. * * @scn: Scene to receive the click * @obj: Object to receive the click * @x: X coordinate of the click * @y: Y coordinate of the click * @event: Returns resulting event from this keypress * Returns: 0 if OK, -ve on error */ static void send_click_obj(struct scene *scn, struct scene_obj *obj, int x, int y, struct expo_action *event) { if (scene_obj_can_highlight(obj) && scene_obj_within(scn, obj, x, y)) { event->type = EXPOACT_OPEN; event->select.id = obj->id; log_debug("open obj %d\n", event->select.id); return; } log_debug("no object; finding...\n"); obj = scene_find_obj_within(scn, x, y, false, false); if (obj) { event->type = EXPOACT_POINT_OPEN; event->select.id = obj->id; } } static int scene_click_popup(struct scene *scn, int x, int y, struct expo_action *event) { struct scene_obj *obj, *chk; 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_click_obj(scn, obj, x, y, event); return 0; } /* check that the click is within our object */ chk = scene_find_obj_within(scn, x, y, false, false); log_debug("chk %d '%s' (obj %d '%s')\n", chk ? chk->id : -1, chk ? chk->name : "(none)", obj->id, obj->name); if (!chk) { /* click into space */ event->type = EXPOACT_CLOSE; event->select.id = obj->id; return 0; } else if (chk != obj) { send_click_obj(scn, chk, x, y, event); if (event->type == EXPOACT_OPEN) { event->type = EXPOACT_REPOINT_OPEN; event->select.prev_id = obj->id; } return 0; } /* click within the open object */ switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, scene_menu_send_click(scn, menu, x, y, event); break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj; // ret = scene_textline_send_click(scn, tline, x, y, event); // if (ret) // return log_msg_ret("key", ret); break; } case SCENEOBJT_TEXTEDIT: /* TODO(sjg@chromium.org): Implement this */ break; } return 0; } int scene_send_click(struct scene *scn, int x, int y, struct expo_action *event) { struct scene_obj *obj; event->type = EXPOACT_NONE; if (scn->expo->popup) { scene_click_popup(scn, x, y, event); return 0; } obj = scene_find_obj_within(scn, x, y, false, false); log_debug("non-popup obj %d '%s'\n", obj ? obj->id : -1, obj ? obj->name : "(none)"); if (!obj) { obj = scene_find_obj_within(scn, x, y, true, true); log_debug("non-popup any obj %d '%s'\n", obj ? obj->id : -1, obj ? obj->name : "(none)"); if (!obj) return 0; } switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: event->type = EXPOACT_CLICK; event->select.id = obj->id; break; case SCENEOBJT_MENU: { struct scene_obj_menu *menu; menu = (struct scene_obj_menu *)obj, scene_menu_send_click(scn, menu, x, y, event); break; } case SCENEOBJT_TEXTLINE: { /* For now, just highlight the textline */ scn->highlight_id = obj->id; break; } case SCENEOBJT_TEXTEDIT: /* For now, just highlight the textedit */ scn->highlight_id = obj->id; break; } return 0; } int scene_obj_calc_bbox(struct scene_obj *obj, struct vidconsole_bbox bbox[]) { switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_IMAGE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: case SCENEOBJT_TEXTEDIT: return -ENOSYS; case SCENEOBJT_MENU: { struct scene_obj_menu *menu = (struct scene_obj_menu *)obj; scene_menu_calc_bbox(menu, bbox); break; } case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj; scene_textline_calc_bbox(tline, &bbox[SCENEBB_all], &bbox[SCENEBB_label]); break; } } return 0; } int scene_sync_bbox(struct scene *scn) { struct scene_obj *obj; list_for_each_entry(obj, &scn->obj_head, sibling) { int req_width = obj->req_bbox.x1 - obj->req_bbox.x0; int req_height = obj->req_bbox.y1 - obj->req_bbox.y0; struct vid_bbox old_bbox = obj->bbox; if (obj->flags & SCENEOF_SYNC_POS) { if (obj->flags & SCENEOF_SIZE_VALID) { int width = obj->bbox.x1 - obj->bbox.x0; int height = obj->bbox.y1 - obj->bbox.y0; obj->bbox.x1 = obj->req_bbox.x0 + width; obj->bbox.y1 = obj->req_bbox.y0 + height; } obj->bbox.x0 = obj->req_bbox.x0; obj->bbox.y0 = obj->req_bbox.y0; } if (obj->flags & SCENEOF_SYNC_SIZE) { obj->bbox.x1 = obj->bbox.x0 + req_width; obj->bbox.y1 = obj->bbox.y0 + req_height; } if (obj->flags & SCENEOF_SYNC_WIDTH) obj->bbox.x1 = obj->bbox.x0 + req_width; if (obj->flags & SCENEOF_SYNC_BBOX) obj->bbox = obj->req_bbox; /* Set dirty flag if bbox changed */ if (old_bbox.x0 != obj->bbox.x0 || old_bbox.y0 != obj->bbox.y0 || old_bbox.x1 != obj->bbox.x1 || old_bbox.y1 != obj->bbox.y1) obj->flags |= SCENEOF_DIRTY; obj->flags &= ~(SCENEOF_SYNC_POS | SCENEOF_SYNC_SIZE | SCENEOF_SYNC_WIDTH | SCENEOF_SYNC_BBOX); } return 0; } int scene_calc_dims(struct scene *scn) { struct scene_obj *obj; int ret, i; /* * Do the menus last so that all the menus' text objects * are dimensioned. Many objects are referenced by a menu and the size * and position is set by the menu */ for (i = 0; i < 2; i++) { bool do_menus = i; list_for_each_entry(obj, &scn->obj_head, sibling) { switch (obj->type) { case SCENEOBJT_NONE: case SCENEOBJT_TEXT: case SCENEOBJT_BOX: case SCENEOBJT_TEXTEDIT: 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->dims.x = width; obj->dims.y = ret; } break; } case SCENEOBJT_MENU: break; case SCENEOBJT_TEXTLINE: { struct scene_obj_textline *tline; tline = (struct scene_obj_textline *)obj; if (!scn->expo->cons) continue; ret = scene_textline_calc_dims(tline, scn->expo->cons); 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; /* 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_BOX: case SCENEOBJT_TEXTLINE: break; case SCENEOBJT_TEXTEDIT: scene_txted_set_font(scn, obj->id, NULL, theme->font_size); break; case SCENEOBJT_TEXT: scene_txt_set_font(scn, obj->id, NULL, theme->font_size); break; } } 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; } } } int scene_img_set_data(struct scene *scn, uint id, const void *data, int size) { struct scene_obj_img *img; img = scene_obj_find(scn, id, SCENEOBJT_IMAGE); if (!img) return log_msg_ret("sid", -ENOENT); img->data = data; /* TODO: Consider using an abuf for the image */ return 0; } 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: case SCENEOBJT_BOX: case SCENEOBJT_TEXTEDIT: 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_join(const struct vidconsole_bbox *src, int inset, struct vidconsole_bbox *dst) { if (dst->valid) { dst->x0 = min(dst->x0, src->x0 - inset); dst->y0 = min(dst->y0, src->y0); dst->x1 = max(dst->x1, src->x1 + inset); dst->y1 = max(dst->y1, src->y1); } else { dst->x0 = src->x0 - inset; dst->y0 = src->y0; dst->x1 = src->x1 + inset; dst->y1 = src->y1; dst->valid = true; } return 0; } int scene_bbox_union(struct scene *scn, uint id, int inset, struct vidconsole_bbox *bbox) { struct scene_obj *obj; struct vidconsole_bbox local; if (!id) return 0; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("obj", -ENOENT); local.x0 = obj->bbox.x0; local.y0 = obj->bbox.y0; local.x1 = obj->bbox.x1; local.y1 = obj->bbox.y1; local.valid = true; scene_bbox_join(&local, inset, bbox); return 0; } void scene_dims_join(struct scene_obj_dims *src, struct scene_obj_dims *dst) { dst->x = max(dst->x, src->x); dst->y = max(dst->y, src->y); } int scene_dims_union(struct scene *scn, uint id, struct scene_obj_dims *dims) { struct scene_obj *obj; if (!id) return 0; obj = scene_obj_find(scn, id, SCENEOBJT_NONE); if (!obj) return log_msg_ret("obj", -ENOENT); scene_dims_join(&obj->dims, dims); return 0; } const char *scene_flag_name(uint flag) { int bit; bit = ffs(flag) - 1; if (bit < 0 || bit >= ARRAY_SIZE(scene_flag_names)) return "(none)"; return scene_flag_names[bit]; } const char *scene_obj_type_name(enum scene_obj_t type) { if (type >= ARRAY_SIZE(scene_obj_type_names)) return "unknown"; return scene_obj_type_names[type]; }