GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix: composed carve pieces crossfade at interior seams — walls notched
The SDF recess fades every wall out over the carve width as it approaches
a suppressed edge (the tessellator's host fade, meant for carves flush
with a real plate edge). In the composed tab+body/ring pieces the
suppressed edges are INTERIOR seams, so both neighbors' walls died AT the
seam: a notch in the section wells' left wall at the tab joint, and the
throat's top wall stopping short of the tab's right wall (the pinched
inside corner).
Every piece now extends past its interior seam by the carve depth, putting
its fade-out exactly over the neighbor's fade-in — the two linear fades
sum to full strength and the wall runs flush through the joint. Applied to
the section reliefs, the labeled Dropdown tab, and the labeled Slider tab.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/widget/container/parameters_bg.rs | 12 +++++++++---
src/widget/input/dropdown.rs | 13 +++++++++----
src/widget/input/slider.rs | 7 +++++--
3 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 4f0fb54..66176b7 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1023,16 +1023,22 @@ impl ParametersBg {
let depth = crate::layout::bevel_width().min(ch * 0.2).min(CHANNEL);
// Tab strip: the title box itself, sitting flush on the body's top
// edge (the header row above it stays plain plate), bottom open.
- out.push((tx, ty, tw, th, (r, r, 0.0, 0.0), depth, false, (true, true, false, true)));
+ // A wall FADES OUT over the carve width approaching a suppressed
+ // edge (the tessellator's host fade — meant for carves flush with
+ // a real plate edge), so every piece extends past its interior
+ // seam by `depth`: its fade-out then crossfades with the
+ // neighbor's fade-in instead of both dying AT the seam (which
+ // notched the walls there; found the hard way).
+ out.push((tx, ty, tw, th + depth, (r, r, 0.0, 0.0), depth, false, (true, true, false, true)));
// Body: top open — its top wall comes from the segment beside
// the tab's throat.
out.push((cx, cy, cw, ch, (0.0, 0.0, r, r), depth, false, (false, true, true, true)));
let throat_r = tx + tw;
if cx + cw > throat_r + 0.5 {
out.push((
- throat_r,
+ throat_r - depth,
cy,
- cx + cw - throat_r,
+ cx + cw - throat_r + depth,
ch,
(0.0, r, 0.0, 0.0),
depth,
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 90bfe0d..28d4683 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -351,9 +351,12 @@ impl Dropdown {
.min(outer_r - outer_x);
let tab_r = outer_x + tab_w;
- // The tab: bottom open into the ring.
+ // The tab: bottom open into the ring. Extended `depth` past the
+ // seam so its walls' fade-out (the tessellator's host fade at a
+ // suppressed edge) crossfades with the ring's fade-in instead
+ // of both dying at the seam.
ctx.recess_edges(
- Rect { x: outer_x, y: tab_top, width: tab_w, height: ring_top - tab_top },
+ Rect { x: outer_x, y: tab_top, width: tab_w, height: ring_top - tab_top + depth },
(orad.0, orad.1.min(strip * 0.5), 0.0, 0.0),
depth,
(true, true, false, true),
@@ -366,10 +369,12 @@ impl Dropdown {
depth,
(false, true, true, true),
);
- // Ring top wall, right of the tab (starts at the tab's throat).
+ // Ring top wall, right of the tab (starts at the tab's throat;
+ // extended `depth` left so it crossfades against the tab's
+ // right wall instead of fading short of it).
if outer_r - tab_r > 0.5 {
ctx.recess_edges(
- Rect { x: tab_r, y: ring_top, width: outer_r - tab_r, height: visual_h + 2.0 * g },
+ Rect { x: tab_r - depth, y: ring_top, width: outer_r - tab_r + depth, height: visual_h + 2.0 * g },
(0.0, orad.1, 0.0, 0.0),
depth,
(true, false, false, false),
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index 92f12ce..d3c3581 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -360,8 +360,11 @@ impl Paint for Slider {
let inset = 4.0; // Layout::detached_label_inset — the label's x offset
let tab_w = (text_w + 2.0 * inset).max(2.0 * radius + 8.0).min(g.track_w);
let tab_r = g.track_x + tab_w;
+ // Pieces extend `recess_t` past their interior seams so the
+ // tessellator's host fade crossfades there instead of notching
+ // the walls (the labeled-Dropdown convention).
ctx.recess_edges(
- Rect { x: g.track_x, y: g.y - strip, width: tab_w, height: strip },
+ Rect { x: g.track_x, y: g.y - strip, width: tab_w, height: strip + recess_t },
(radius, radius.min(strip * 0.5), 0.0, 0.0),
recess_t,
(true, true, false, true),
@@ -369,7 +372,7 @@ impl Paint for Slider {
ctx.recess_edges(track_rect, (0.0, 0.0, radius, radius), recess_t, (false, true, true, true));
if g.track_x + g.track_w - tab_r > 0.5 {
ctx.recess_edges(
- Rect { x: tab_r, y: g.y, width: g.track_x + g.track_w - tab_r, height: g.h },
+ Rect { x: tab_r - recess_t, y: g.y, width: g.track_x + g.track_w - tab_r + recess_t, height: g.h },
(0.0, radius, 0.0, 0.0),
recess_t,
(true, false, false, false),