git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitc8277504bef81c940ae0cd91ca19a8148938a02d
parent080c6d4eb2
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-02 14:07
feat(layout): Rect::contains, a half-open point test

cce-browser and cce-calendar each define `hit`, cce-notes and cce-graph
each define `contains`, all the same four comparisons on this Rect. They
disagree on the far edges (browser and calendar exclude them, notes and
graph include them); this one excludes them, so rects sharing an edge (a
tab row, list rows) give a point on it to exactly one of them whatever
order they are tested in. cce-ui's own widgets keep their inline tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/scene/layout.rs | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/src/scene/layout.rs b/src/scene/layout.rs
index 3634de2..c56d197 100644
--- a/src/scene/layout.rs
+++ b/src/scene/layout.rs
@@ -50,6 +50,17 @@ pub struct Rect {
 
 impl Rect {
     pub const ZERO: Rect = Rect { x: 0.0, y: 0.0, width: 0.0, height: 0.0 };
+
+    /// Whether the point `(x, y)` lies in the rect: the left and top edges
+    /// are inside, the right and bottom edges are not.
+    ///
+    /// Half-open on purpose, so rects that share an edge — a row of tabs, a
+    /// list's rows — partition the space between them: a point on the
+    /// shared edge belongs to exactly one, whichever order they are tested
+    /// in. A rect with no width or height contains nothing.
+    pub fn contains(&self, x: f32, y: f32) -> bool {
+        x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
+    }
 }
 
 /// How an image maps into a bounding box — see [`fit_rect`].
@@ -665,6 +676,20 @@ fn arrange_grid(
 mod tests {
     use super::*;
 
+    #[test]
+    fn contains_is_half_open() {
+        let r = Rect { x: 10.0, y: 20.0, width: 100.0, height: 50.0 };
+        assert!(r.contains(10.0, 20.0), "the top-left corner is inside");
+        assert!(r.contains(109.9, 69.9));
+        assert!(!r.contains(110.0, 40.0), "the right edge is outside");
+        assert!(!r.contains(50.0, 70.0), "the bottom edge is outside");
+        assert!(!r.contains(9.9, 40.0) && !r.contains(50.0, 19.9));
+        // Neighbours sharing an edge split it: the point goes to one only.
+        let next = Rect { x: 110.0, ..r };
+        assert!(!r.contains(110.0, 40.0) && next.contains(110.0, 40.0));
+        assert!(!Rect::ZERO.contains(0.0, 0.0), "an empty rect contains nothing");
+    }
+
     #[test]
     fn fit_contain_letterboxes_and_centers() {
         let b = Rect { x: 10.0, y: 20.0, width: 100.0, height: 50.0 };