expo: Set up the width and height of objects

Provide a way to set the full dimensions of objects, i.e. including the
width and height.

For menus, calculate the bounding box of all objects in the menu. Set all
labels to be the same size, so that highlighting works correct, once
implemented.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/boot/expo.c b/boot/expo.c
index be11cfd..67cae3c 100644
--- a/boot/expo.c
+++ b/boot/expo.c
@@ -114,6 +114,30 @@
 	return 0;
 }
 
+int expo_calc_dims(struct expo *exp)
+{
+	struct scene *scn;
+	int ret;
+
+	if (!exp->cons)
+		return log_msg_ret("dim", -ENOTSUPP);
+
+	list_for_each_entry(scn, &exp->scene_head, sibling) {
+		/*
+		 * Do the menus last so that all the menus' text objects
+		 * are dimensioned
+		 */
+		ret = scene_calc_dims(scn, false);
+		if (ret)
+			return log_msg_ret("scn", ret);
+		ret = scene_calc_dims(scn, true);
+		if (ret)
+			return log_msg_ret("scn", ret);
+	}
+
+	return 0;
+}
+
 void expo_set_text_mode(struct expo *exp, bool text_mode)
 {
 	exp->text_mode = text_mode;
diff --git a/boot/scene.c b/boot/scene.c
index 981a18b..6d5e3c1 100644
--- a/boot/scene.c
+++ b/boot/scene.c
@@ -207,6 +207,19 @@
 	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;
@@ -414,3 +427,42 @@
 
 	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;
+		}
+		}
+	}
+
+	return 0;
+}
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
index 24a2ba6..00085a2 100644
--- a/boot/scene_internal.h
+++ b/boot/scene_internal.h
@@ -66,6 +66,17 @@
 int scene_obj_flag_clrset(struct scene *scn, uint id, uint clr, uint set);
 
 /**
+ * scene_calc_dims() - Calculate the dimensions of the scene objects
+ *
+ * Updates the width and height of all objects based on their contents
+ *
+ * @scn: Scene to update
+ * @do_menus: true to calculate only menus, false to calculate everything else
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int scene_calc_dims(struct scene *scn, bool do_menus);
+
+/**
  * scene_menu_arrange() - Set the position of things in the menu
  *
  * This updates any items associated with a menu to make sure they are
@@ -133,4 +144,14 @@
  */
 int scene_send_key(struct scene *scn, int key, struct expo_action *event);
 
+/**
+ * scene_menu_calc_dims() - Calculate the dimensions of a menu
+ *
+ * Updates the width and height of the menu based on its contents
+ *
+ * @menu: Menu to update
+ * Returns 0 if OK, -ENOTSUPP if there is no graphical console
+ */
+int scene_menu_calc_dims(struct scene_obj_menu *menu);
+
 #endif /* __SCENE_INTERNAL_H */
diff --git a/boot/scene_menu.c b/boot/scene_menu.c
index eed7565..fa79cec 100644
--- a/boot/scene_menu.c
+++ b/boot/scene_menu.c
@@ -43,6 +43,85 @@
 	menu->cur_item_id = item_id;
 }
 
+static int scene_bbox_union(struct scene *scn, uint id,
+			    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);
+		bbox->y0 = min(bbox->y0, obj->dim.y);
+		bbox->x1 = max(bbox->x1, obj->dim.x + obj->dim.w);
+		bbox->y1 = max(bbox->y1, obj->dim.y + obj->dim.h);
+	} else {
+		bbox->x0 = obj->dim.x;
+		bbox->y0 = obj->dim.y;
+		bbox->x1 = obj->dim.x + obj->dim.w;
+		bbox->y1 = obj->dim.y + obj->dim.h;
+		bbox->valid = true;
+	}
+
+	return 0;
+}
+
+/**
+ * scene_menu_calc_bbox() - Calculate bounding boxes for the menu
+ *
+ * @menu: Menu to process
+ * @bbox: Returns bounding box of menu including prompts
+ * @label_bbox: Returns bounding box of labels
+ */
+static void scene_menu_calc_bbox(struct scene_obj_menu *menu,
+				 struct vidconsole_bbox *bbox,
+				 struct vidconsole_bbox *label_bbox)
+{
+	const struct scene_menitem *item;
+
+	bbox->valid = false;
+	scene_bbox_union(menu->obj.scene, menu->title_id, bbox);
+
+	label_bbox->valid = false;
+
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		scene_bbox_union(menu->obj.scene, item->label_id, bbox);
+		scene_bbox_union(menu->obj.scene, item->key_id, bbox);
+		scene_bbox_union(menu->obj.scene, item->desc_id, bbox);
+		scene_bbox_union(menu->obj.scene, item->preview_id, bbox);
+
+		/* Get the bounding box of all labels */
+		scene_bbox_union(menu->obj.scene, item->label_id, label_bbox);
+	}
+}
+
+int scene_menu_calc_dims(struct scene_obj_menu *menu)
+{
+	struct vidconsole_bbox bbox, label_bbox;
+	const struct scene_menitem *item;
+
+	scene_menu_calc_bbox(menu, &bbox, &label_bbox);
+
+	/* Make all labels the same size */
+	if (label_bbox.valid) {
+		list_for_each_entry(item, &menu->item_head, sibling) {
+			scene_obj_set_size(menu->obj.scene, item->label_id,
+					   label_bbox.x1 - label_bbox.x0,
+					   label_bbox.y1 - label_bbox.y0);
+		}
+	}
+
+	if (bbox.valid) {
+		menu->obj.dim.w = bbox.x1 - bbox.x0;
+		menu->obj.dim.h = bbox.y1 - bbox.y0;
+	}
+
+	return 0;
+}
+
 int scene_menu_arrange(struct scene *scn, struct scene_obj_menu *menu)
 {
 	struct scene_menitem *item;