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

commit605fbdb2a4001281ebb69dfbb3e4c9b4179fb247
parente0ac4dac0c
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-30 14:47
feat: a graph wire may be thinner than a pixel

The stroke was clamped to one logical pixel, two device pixels at 2x, so
wire_size below 1 did nothing. The floor is one DEVICE pixel now; a wire
thinner than that draws as the pixel at the share of it the wire covers.

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

 CLAUDE.md                   |  8 ++++++++
 src/widget/display/graph.rs | 50 +++++++++++++++++++++++++++++++++++++++------
 2 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 772ba20..7711f42 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -780,6 +780,14 @@ NOTHING until this change, when the wires were a hard-coded cyan 3 px.
   so a translucent wire is one alpha throughout. A bezier is flat-capped
   pieces about 6 px of control net apiece, fine enough that no notch shows
   at the joins.
+- **A wire may be thinner than a pixel** (the same day). `wire_size` has no
+  floor; what is drawn does (`wire_stroke`): one DEVICE pixel, half a
+  logical one at 2x, since the 2D pass has no antialiasing and a narrower
+  axis-aligned quad covers a row of pixel centres or none. A wire under
+  that is the pixel at the share of it the wire covers, so it reads
+  thinner by reading fainter. Until then the stroke was clamped to one
+  LOGICAL pixel, two device pixels at 2x, and `wire_size` below 1 did
+  nothing.
 
 `every_wire_style_runs_from_port_to_port` and the splice test, run in all
 four styles, are the tests.
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index 95e5d91..bab77c3 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -112,6 +112,22 @@ enum WireSeg {
     Arc { c: (f32, f32), r: f32, a0: f32, a1: f32 },
 }
 
+/// One DEVICE pixel in logical px.
+fn device_px() -> f32 {
+    1.0 / crate::scale::scale_factor().max(1.0)
+}
+
+/// A wire of `size` logical px as it is drawn: (stroke width, alpha). The
+/// thinnest stroke is one device pixel (`px`), because the 2D pass has no
+/// antialiasing — an axis-aligned quad narrower than a pixel covers a row of
+/// pixel centres or none, and the wire would come and go as it moved. A
+/// thinner wire is drawn as that pixel at the share of it the wire would
+/// cover, so it reads thinner by reading fainter.
+fn wire_stroke(size: f32, px: f32) -> (f32, f32) {
+    let px = px.max(f32::EPSILON);
+    (size.max(px), (size / px).clamp(0.0, 1.0))
+}
+
 /// The path of a wire from `start` to `end` drawn `t` px thick. `bend` is
 /// the largest radius a Rounded bend takes and the least a Bezier lead
 /// runs straight down before curving — both scale with the node, so the
@@ -681,12 +697,22 @@ impl Graph {
         self.wire_style = style;
     }
 
-    /// A wire's stroke: `graph_wire_size` px at 100%, scaled with the node
-    /// body as the zoom scales it.
-    fn wire_thickness(&self) -> f32 {
+    /// A wire's width as asked for: `graph_wire_size` px at 100%, scaled
+    /// with the node body as the zoom scales it, and at most half a body.
+    fn wire_size_at_zoom(&self) -> f32 {
         let base = crate::layout::graph_node_width();
         let zoom = if base > 0.0 { self.node_w / base } else { 1.0 };
-        (crate::layout::graph_wire_size() * zoom).clamp(1.0, (self.node_h * 0.5).max(1.0))
+        (crate::layout::graph_wire_size() * zoom).clamp(0.0, (self.node_h * 0.5).max(1.0))
+    }
+
+    /// A wire's stroke as drawn (see [`wire_stroke`]).
+    fn wire_thickness(&self) -> f32 {
+        wire_stroke(self.wire_size_at_zoom(), device_px()).0
+    }
+
+    /// The alpha a wire's colour is drawn at (see [`wire_stroke`]).
+    fn wire_fade(&self) -> f32 {
+        wire_stroke(self.wire_size_at_zoom(), device_px()).1
     }
 
     /// What a Rounded bend's radius and a Bezier's straight lead are made of.
@@ -718,10 +744,11 @@ impl Graph {
                 }
             }
         };
+        let fade = self.node_opacity * self.wire_fade();
         let wc = crate::color::graph_wire_color();
-        let wire_color = [wc[0], wc[1], wc[2], wc[3] * self.node_opacity];
+        let wire_color = [wc[0], wc[1], wc[2], wc[3] * fade];
         let hl = crate::color::graph_wire_highlight_color();
-        let splice_color = [hl[0], hl[1], hl[2], hl[3] * self.node_opacity];
+        let splice_color = [hl[0], hl[1], hl[2], hl[3] * fade];
         pc.clip(rect, |pc| {
             for (src_idx, i, port) in self.wire_pairs() {
                 let Some(segs) = self.wire_segments(src_idx, i, port) else { continue };
@@ -1772,6 +1799,17 @@ mod tests {
     /// piece begins where the last ended. The orthogonal one is three runs
     /// meeting square, which touch without overlapping so a translucent wire
     /// is one alpha throughout — the across run filling the corners.
+    #[test]
+    fn a_wire_thinner_than_a_device_pixel_is_that_pixel_fainter() {
+        // At 2x a device pixel is half a logical one.
+        assert_eq!(wire_stroke(3.0, 0.5), (3.0, 1.0));
+        assert_eq!(wire_stroke(0.5, 0.5), (0.5, 1.0));
+        assert_eq!(wire_stroke(0.25, 0.5), (0.5, 0.5));
+        // At 1x the pixel is a whole one, and nothing draws narrower.
+        assert_eq!(wire_stroke(0.5, 1.0), (1.0, 0.5));
+        assert_eq!(wire_stroke(0.0, 1.0), (1.0, 0.0));
+    }
+
     #[test]
     fn every_wire_style_runs_from_port_to_port() {
         let t = 6.0;