GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix: tab's right wall ends at the fillet tangent — it ghosted through the arc
The tab piece ran its full height for the left wall's seam crossfade, so
its RIGHT wall's straight run continued down through the fillet zone — a
ghost of the square corner behind the curve, breaking the border bevel's
continuity. The tab piece now stops at the fillet's vertical tangent (its
right wall crossfades out under the arc), and a dedicated left-only bridge
piece carries the left wall across the fillet span down to the well's own
fade-in. Sections, labeled Dropdown, and labeled Slider alike; sections
too narrow for the fillet keep the plain square-throat composition.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/widget/container/parameters_bg.rs | 39 +++++++++++++++++++++++++++--------
src/widget/input/dropdown.rs | 39 +++++++++++++++++++++++++++--------
src/widget/input/slider.rs | 33 ++++++++++++++++++++++-------
3 files changed, 85 insertions(+), 26 deletions(-)
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 3828eba..9a6ade0 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -1033,25 +1033,46 @@ impl ParametersBg {
// 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 {
- // Starts a fillet radius past the throat — the concave
- // fillet ([`Self::section_fillets`]) carries the corner.
+ let rho = SECTION_FILLET_R;
+ if cx + cw > throat_r + 2.0 * rho {
+ // Filleted throat ([`Self::section_fillets`]): the tab's
+ // right wall must END at the fillet's vertical tangent
+ // (crossfading out under the arc) or its straight run
+ // ghosts through the curve — so the tab piece stops there,
+ // and a left-only bridge carries the left wall across the
+ // fillet span down to the body's own fade-in.
+ out.push((tx, ty, tw, (cy - rho) - ty + depth, (r, r, 0.0, 0.0), depth, false, (true, true, false, true)));
+ out.push((tx, cy - rho, tw, rho + depth, (0.0, 0.0, 0.0, 0.0), depth, false, (false, false, false, true)));
out.push((
- throat_r + SECTION_FILLET_R - depth,
+ throat_r + rho - depth,
cy,
- cx + cw - throat_r - SECTION_FILLET_R + depth,
+ cx + cw - throat_r - rho + depth,
ch,
(0.0, r, 0.0, 0.0),
depth,
false,
(true, false, false, false),
));
+ } else {
+ // Too narrow for the fillet: the plain square throat.
+ out.push((tx, ty, tw, th + depth, (r, r, 0.0, 0.0), depth, false, (true, true, false, true)));
+ if cx + cw > throat_r + 0.5 {
+ out.push((
+ throat_r - depth,
+ cy,
+ cx + cw - throat_r + depth,
+ ch,
+ (0.0, r, 0.0, 0.0),
+ depth,
+ false,
+ (true, false, false, false),
+ ));
+ }
}
+ // 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)));
} else {
let depth = crate::layout::bevel_width().min(th * 0.2).min(CHANNEL);
out.push((tx, ty, tw, th, r4(SECTION_R), depth, false, all));
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index d069da8..c44545d 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -351,16 +351,31 @@ impl Dropdown {
.min(outer_r - outer_x);
let tab_r = outer_x + tab_w;
- // 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.
+ // The tab: bottom open into the ring, pieces extended `depth`
+ // past their interior seams so the tessellator's host fades
+ // crossfade there instead of notching the walls. With the
+ // fillet, the tab's right wall must END at the fillet's
+ // vertical tangent (crossfading out under the arc) or its
+ // straight run ghosts through the curve — the tab piece stops
+ // there and a left-only bridge carries the left wall across
+ // the fillet span down to the ring's own fade-in.
+ let fr = 6.0_f32.min(strip * 0.5);
+ let filleted = outer_r - tab_r > fr + 4.0;
+ let tab_bottom = if filleted { ring_top - fr } else { ring_top };
ctx.recess_edges(
- Rect { x: outer_x, y: tab_top, width: tab_w, height: ring_top - tab_top + depth },
+ Rect { x: outer_x, y: tab_top, width: tab_w, height: tab_bottom - tab_top + depth },
(orad.0, orad.1.min(strip * 0.5), 0.0, 0.0),
depth,
(true, true, false, true),
);
+ if filleted {
+ ctx.recess_edges(
+ Rect { x: outer_x, y: ring_top - fr, width: tab_w, height: fr + depth },
+ (0.0, 0.0, 0.0, 0.0),
+ depth,
+ (false, false, false, true),
+ );
+ }
// The ring proper: right + bottom + left walls, one prim so
// its corners blend internally.
ctx.recess_edges(
@@ -369,12 +384,11 @@ impl Dropdown {
depth,
(false, true, true, true),
);
- // Ring top wall, right of the tab. A concave fillet rounds the
- // throat; the straight run starts a fillet radius past it
+ // Ring top wall, right of the tab. The concave fillet rounds
+ // the throat; the straight run starts a fillet radius past it
// (extended `depth` left so its fade-in lands under the
// fillet's hard tangent cut instead of leaving a gap).
- let fr = 6.0_f32.min(strip * 0.5);
- if outer_r - tab_r > fr + 4.0 {
+ if filleted {
ctx.concave_fillet(
tab_r + fr,
ring_top - fr,
@@ -394,6 +408,13 @@ impl Dropdown {
depth,
(true, false, false, false),
);
+ } else if outer_r - tab_r > 0.5 {
+ ctx.recess_edges(
+ 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),
+ );
}
let rect = Rect { x, y, width: w, height: visual_h };
let rrad = (r4[0], r4[1], r4[2], r4[3]);
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index 6200c6b..f56af46 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -360,20 +360,30 @@ 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).
+ // The labeled-Dropdown composition: pieces extend `recess_t`
+ // past interior seams (host-fade crossfade), the tab's right
+ // wall ends at the fillet's vertical tangent (or it ghosts
+ // through the arc), and a left-only bridge carries the left
+ // wall across the fillet span.
+ let fr = 6.0_f32.min(strip * 0.5);
+ let filleted = g.track_x + g.track_w - tab_r > fr + 4.0;
+ let tab_bottom = if filleted { g.y - fr } else { g.y };
ctx.recess_edges(
- Rect { x: g.track_x, y: g.y - strip, width: tab_w, height: strip + recess_t },
+ Rect { x: g.track_x, y: g.y - strip, width: tab_w, height: tab_bottom - (g.y - strip) + recess_t },
(radius, radius.min(strip * 0.5), 0.0, 0.0),
recess_t,
(true, true, false, true),
);
+ if filleted {
+ ctx.recess_edges(
+ Rect { x: g.track_x, y: g.y - fr, width: tab_w, height: fr + recess_t },
+ (0.0, 0.0, 0.0, 0.0),
+ recess_t,
+ (false, false, false, true),
+ );
+ }
ctx.recess_edges(track_rect, (0.0, 0.0, radius, radius), recess_t, (false, true, true, true));
- // Concave fillet at the throat, the straight run starting a
- // fillet radius past it (the labeled-Dropdown convention).
- let fr = 6.0_f32.min(strip * 0.5);
- if g.track_x + g.track_w - tab_r > fr + 4.0 {
+ if filleted {
ctx.concave_fillet(
tab_r + fr,
g.y - fr,
@@ -393,6 +403,13 @@ impl Paint for Slider {
recess_t,
(true, false, false, false),
);
+ } else if g.track_x + g.track_w - tab_r > 0.5 {
+ ctx.recess_edges(
+ 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),
+ );
}
} else {
ctx.recess(track_rect, (radius, radius, radius, radius), recess_t);