[Concept,11/23] expo: Provide a way to check if a position is within a menu

Message ID 20250915122905.1217249-12-sjg@u-boot.org
State New
Headers
Series expo: Support interactions with a mouse or touchpad |

Commit Message

Simon Glass Sept. 15, 2025, 12:28 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

To implement mouse clicks we need a way to figure out what the user has
clicked on. As a starting point, create a scene_menu_within() function
which returns the item containing an x, y coordinate.

Provide a helper function in scene.c for use with this. Add a simple
for completeness.

Co-developed-by: Claude <noreply@anthropic.com>
Signed-off-by: Simon Glass <sjg@chromium.org>
---

 boot/scene.c          | 17 +++++++++++++++
 boot/scene_internal.h | 24 +++++++++++++++++++++
 boot/scene_menu.c     | 31 +++++++++++++++++++++++++++
 test/boot/expo.c      | 49 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 121 insertions(+)
  

Patch

diff --git a/boot/scene.c b/boot/scene.c
index 50a7dfb22a1..98716ef2337 100644
--- a/boot/scene.c
+++ b/boot/scene.c
@@ -1028,6 +1028,23 @@  int scene_send_key(struct scene *scn, int key, struct expo_action *event)
 	return 0;
 }
 
+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);
+
+	/* 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);
+}
+
 int scene_obj_calc_bbox(struct scene_obj *obj, struct vidconsole_bbox bbox[])
 {
 	switch (obj->type) {
diff --git a/boot/scene_internal.h b/boot/scene_internal.h
index c6f2615a2c5..15a96b1e31e 100644
--- a/boot/scene_internal.h
+++ b/boot/scene_internal.h
@@ -241,6 +241,30 @@  int scene_render(struct scene *scn);
  */
 int scene_send_key(struct scene *scn, int key, struct expo_action *event);
 
+/**
+ * scene_within() - check if a point is considered within an object ID
+ *
+ * @scn: Scene to check
+ * @id: ID of 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
+ */
+bool scene_within(const struct scene *scn, uint id, int x, int y);
+
+/**
+ * scene_menu_within() - check if a point is considered within a menu
+ *
+ * @scn: Scene to check
+ * @menu: Menu to check
+ * @x: X coordinate of the point
+ * @y: Y coordinate of the point
+ * Return: item the point is within, or NULL if none
+ */
+struct scene_menitem *scene_menu_within(const struct scene *scn,
+					struct scene_obj_menu *menu,
+					int x, int y);
+
 /**
  * scene_render_deps() - Render an object and its dependencies
  *
diff --git a/boot/scene_menu.c b/boot/scene_menu.c
index 0ed73cd12cc..758343fe909 100644
--- a/boot/scene_menu.c
+++ b/boot/scene_menu.c
@@ -497,6 +497,37 @@  int scene_menu_send_key(struct scene *scn, struct scene_obj_menu *menu, int key,
 	return 0;
 }
 
+struct scene_menitem *scene_menu_within(const struct scene *scn,
+					struct scene_obj_menu *menu,
+					int x, int y)
+{
+	struct scene_menitem *item;
+
+	list_for_each_entry(item, &menu->item_head, sibling) {
+		log_debug("  item %d: label %d\n", item->id, item->label_id);
+		bool within;
+
+		within = scene_within(scn, item->label_id, x, y);
+		log_debug("- item %d within %d\n", item->id, within);
+		if (!within && !scn->expo->popup) {
+			log_debug("- non-popup within key %d desc %d preview %d\n",
+				  scene_within(scn, item->key_id, x, y),
+				  scene_within(scn, item->desc_id, x, y),
+				  scene_within(scn, item->preview_id, x, y));
+			within |= scene_within(scn, item->key_id, x, y) ||
+				scene_within(scn, item->desc_id, x, y) ||
+				scene_within(scn, item->preview_id, x, y);
+			log_debug("- popup within %d\n", within);
+		}
+
+		log_debug("- final within %d\n", within);
+		if (within)
+			return item;
+	}
+
+	return NULL;
+}
+
 int scene_menuitem(struct scene *scn, uint menu_id, const char *name, uint id,
 		   uint key_id, uint label_id, uint desc_id, uint preview_id,
 		   uint flags, struct scene_menitem **itemp)
diff --git a/test/boot/expo.c b/test/boot/expo.c
index d407ad839f6..99c28cc77f6 100644
--- a/test/boot/expo.c
+++ b/test/boot/expo.c
@@ -962,6 +962,55 @@  static int expo_test_build(struct unit_test_state *uts)
 }
 BOOTSTD_TEST(expo_test_build, UTF_DM);
 
+/* test scene_menu_within() function */
+static int expo_menu_within(struct unit_test_state *uts)
+{
+	struct scene_obj_menu *menu;
+	struct scene_menitem *item;
+	struct scene_obj *obj;
+	struct udevice *dev;
+	struct scene *scn;
+	struct expo *exp;
+	ofnode node;
+
+	node = ofnode_path("/cedit");
+	ut_assert(ofnode_valid(node));
+	ut_assertok(expo_build(node, &exp));
+
+	scn = expo_lookup_scene_id(exp, ID_SCENE1);
+	ut_assertnonnull(scn);
+
+	menu = scene_obj_find(scn, ID_CPU_SPEED, SCENEOBJT_NONE);
+	ut_assertnonnull(menu);
+
+	ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev));
+	ut_assertok(expo_set_display(exp, dev));
+
+	ut_assertok(scene_arrange(scn));
+
+	/* get first menu item and test with its coordinates */
+	item = list_first_entry(&menu->item_head, struct scene_menitem,
+				sibling);
+	ut_assertnonnull(item);
+
+	/* get the label object to find coordinates */
+	obj = scene_obj_find(scn, item->label_id, SCENEOBJT_NONE);
+	ut_assertnonnull(obj);
+	ut_asserteq_ptr(item, scene_menu_within(scn, menu, obj->bbox.x0 + 1,
+						obj->bbox.y0 + 1));
+
+	/* test point outside menu bounds */
+	ut_assertnull(scene_menu_within(scn, menu, -1, -1));
+
+	/* test point far outside menu bounds */
+	ut_assertnull(scene_menu_within(scn, menu, 9999, 9999));
+
+	expo_destroy(exp);
+
+	return 0;
+}
+BOOTSTD_TEST(expo_menu_within, UTF_DM | UTF_SCAN_FDT);
+
 /* test expo_set_mouse_enable() */
 static int expo_mouse_enable(struct unit_test_state *uts)
 {