Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
feat(handles): pinch the band at the seams
The band's thickness minimum sat at the corner apex — a leftover from
before the pads, and invisible now that a pad covers the apex. The visible
consequence was a band that only thinned in one direction: away from each
gap toward the corner, while the gap itself cut through a partly-swollen
band.
The minimum now lands exactly on the seams. The profile swells away from
each gap in both directions — an edge bar to full band at its side's
midpoint, a corner arm back toward the apex, where the pad's smooth union
takes over — so every join is a waist between two thickening pieces, and
the pad reads as continuous with its two arms rather than sitting on a
thin ribbon. The corner run (cl, per-side clamped) is now shared by the
profile and the zone cut, which is what pins the minimum to the seam.
Measured along the top side: 12px at both seams (37%/63%), swelling to
27px into the corner arm and 31px at the midpoint.
Co-Authored-By: Claude Fable 5 <[email protected]>
CLAUDE.md | 14 +++++----
scenefx/render/fx_renderer/shaders/frame.frag | 43 +++++++++++++++++----------
2 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index b2e001a..10bc5b6 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -322,12 +322,14 @@ where the old band sat outside them.
to be spent on moving the way the outside band's did.
- The ring is drawn by **one scenefx node**, `wlr_scene_frame`
(`scenefx/render/fx_renderer/shaders/frame.frag`), not by rects. Its
- two elements make the frame. The BAND runs thin at the corners and swells
- to `band` at each side's midpoint; a round PAD (`bulge`, screen px, 0
- disables) sits on each corner — a disc centred on the corner arc's centre,
- smooth-unioned onto the ring so its inner boundary bows inward with a
- fillet where it meets the moulding. The band deliberately thins toward the
- corner so the pad reads as a bead on a slender moulding, not as more band.
+ two elements make the frame. The BAND is thinnest at the SEAMS — the gaps
+ between corner and edge pieces — and swells away from them both ways: an
+ edge bar to `band` at its side's midpoint, a corner arm back toward the
+ apex, where it flows into the round PAD (`bulge`, screen px, 0 disables) —
+ a disc centred on the corner arc's centre, smooth-unioned onto the ring so
+ its inner boundary bows inward with a fillet where it meets the moulding.
+ Each gap therefore separates two thin tips: the frame pinches at every
+ join and thickens toward every piece's middle.
The gap notches sever only the band; a groove across a pad would read as
damage. Both `get_border_zone` and the drawing treat a pad as its corner's
zone — its tip reaches past the band, so the hit test carries a matching
diff --git a/scenefx/render/fx_renderer/shaders/frame.frag b/scenefx/render/fx_renderer/shaders/frame.frag
index 40edfdb..94f70e5 100644
--- a/scenefx/render/fx_renderer/shaders/frame.frag
+++ b/scenefx/render/fx_renderer/shaders/frame.frag
@@ -2,12 +2,14 @@
// swells from the corners toward the middle of each side, like the moulding
// of a picture frame.
//
-// Two elements make the frame. The BAND runs `band_min` at the corners,
-// easing to `band` at each side's midpoint — the moulding. And a round BULGE
-// sits on each corner: a disc smooth-unioned onto the ring, so the inner
-// boundary bows inward there like a bead. The band deliberately thins toward
-// the corner so the bulge reads as its own rounded pad on a slender moulding,
-// not as more band. `corner_len` places the gaps and the corner zones.
+// Two elements make the frame. The BAND is thinnest at the SEAMS — the gaps
+// between corner and edge pieces — and swells away from them in both
+// directions: an edge bar rises to `band` at its side's midpoint, a corner
+// arm rises back toward the apex, where it flows into the round BULGE that
+// sits on each corner (a disc smooth-unioned onto the ring, its inner
+// boundary bowing inward like a bead). So each gap separates two thin tips,
+// and every piece thickens toward its own middle. `corner_len` places the
+// seams and the corner zones.
//
// Why a shader rather than rects: the swell is continuous along a side, and
// a scene rect can only approximate it with a clipped region whose corner
@@ -97,11 +99,24 @@ void main() {
// Distance from the nearer end of that side.
float u = min(along, len - along);
- // The band: thin at the corners, swelling to the side's midpoint, with
- // swell_curve pulling the gain early (below 1) or late (above 1).
+ // The corner run, clamped against ITS OWN side (see the zone comment
+ // below); the band profile and the zone cut share it so the thickness
+ // minimum lands exactly on the seam.
+ float cl = min(corner_len, 0.45 * len);
+
+ // The band: band_min at the seams, swelling away from them both ways —
+ // an edge bar to `band` at the side's midpoint, a corner arm back toward
+ // the apex, where the pad's union takes over. v is 0 at a seam and 1 at
+ // a piece's peak; swell_curve pulls the gain early (below 1) or late.
float half_side = max(0.5 * len, 1e-3);
- float t = clamp(u / half_side, 0.0, 1.0);
- float s = pow(smoothstep(0.0, 1.0, t), max(swell_curve, 0.01));
+ float v;
+ if (u <= cl) {
+ v = 1.0 - u / max(cl, 1e-3);
+ } else {
+ float run = max(half_side - cl - gap, 1e-3);
+ v = clamp((u - cl - gap) / run, 0.0, 1.0);
+ }
+ float s = pow(smoothstep(0.0, 1.0, v), max(swell_curve, 0.01));
float thickness = mix(band_min, band, s);
float f_ring = thickness - depth;
@@ -123,11 +138,9 @@ void main() {
// Zones: anything on the pad is its corner's, then the band cut as
// before. The gap only severs the BAND — a groove across the pad would
// read as damage, so it skips fragments the disc owns.
- // The corner run clamps against ITS OWN side: two corner zones on one
- // side must never meet, or the side's midpoint would resize diagonally —
- // and clamping per side rather than to the window's short side lets a
- // long side carry the full configured run while a short one shortens.
- float cl = min(corner_len, 0.45 * len);
+ // The corner run clamps per side because two corner zones on one side
+ // must never meet — past that, the side's midpoint would resize
+ // diagonally.
float zone;
if (u <= cl || f_disc > 0.0) {
zone = top ? (left ? ZONE_TL : ZONE_TR) : (left ? ZONE_BL : ZONE_BR);