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

commit8948af0a49e09247b8ca501fa3d8be6774802c2b
parentabb3ae0ebf
authorLucas Galante <[email protected]>
date2026-07-24 15:38
feat: GraphController::node_at — topmost node under a point

node_at(px, py) hit-tests window-absolute coords against node_rect in
reverse draw order, for hosts that intercept a press before the graph
widget sees it (the designer's right-click node menu).

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/widget/container/content_bg.rs |  1 +
 src/widget/display/graph.rs        | 17 +++++++++++++++++
 src/widget/mod.rs                  |  4 ++++
 3 files changed, 22 insertions(+)

diff --git a/src/widget/container/content_bg.rs b/src/widget/container/content_bg.rs
index 1f61832..bc84800 100644
--- a/src/widget/container/content_bg.rs
+++ b/src/widget/container/content_bg.rs
@@ -269,4 +269,5 @@ impl GraphController for ContentBg {
     fn take_pending_connection(&mut self) -> Option<(String, String)> { None }
     fn cancel_connecting(&mut self) {}
     fn is_node_rect(&self, _qx: f32, _qy: f32, _qw: f32, _qh: f32) -> bool { false }
+    fn node_at(&self, _px: f32, _py: f32) -> Option<usize> { None }
 }
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index 079dcca..981e8ad 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -188,6 +188,20 @@ impl Graph {
         false
     }
 
+    /// The topmost node whose body contains (px, py), in the same
+    /// window-absolute space `node_rect` reports (grid_origin = pane + pan).
+    /// Reverse order so a later-drawn node wins where bodies overlap.
+    pub fn node_at(&self, px: f32, py: f32) -> Option<usize> {
+        for i in (0..self.nodes.len()).rev() {
+            if let Some((nx, ny, nw, nh)) = self.node_rect(i) {
+                if px >= nx && px < nx + nw && py >= ny && py < ny + nh {
+                    return Some(i);
+                }
+            }
+        }
+        None
+    }
+
     /// How far a port's center floats off its node edge: the connector's own
     /// radius plus a small gap, so the circle sits fully OUTSIDE the node's
     /// bounding box rather than straddling its border.
@@ -1012,6 +1026,9 @@ impl GraphController for Graph {
     fn is_node_rect(&self, qx: f32, qy: f32, qw: f32, qh: f32) -> bool {
         self.is_node_rect(qx, qy, qw, qh)
     }
+    fn node_at(&self, px: f32, py: f32) -> Option<usize> {
+        self.node_at(px, py)
+    }
 }
 
 #[cfg(test)]
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 1241ace..68c7153 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -92,6 +92,8 @@ pub enum ContextAction {
     CollapseNode,
     ExpandAll,
     CollapseAll,
+    /// Ramp: hide/show the bottom control strip, the graph claiming the space.
+    ToggleRampControls,
 }
 
 use crate::colors;
@@ -580,6 +582,8 @@ pub trait GraphController {
     fn take_pending_connection(&mut self) -> Option<(String, String)>;
     fn cancel_connecting(&mut self);
     fn is_node_rect(&self, qx: f32, qy: f32, qw: f32, qh: f32) -> bool;
+    /// The topmost node whose body contains (px, py), window-absolute coords.
+    fn node_at(&self, px: f32, py: f32) -> Option<usize>;
 }
 
 pub trait SpreadsheetController {