GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix: the well's bevel is the far side of nested-control seams
The textpick picker and the spinbox -/+ run had their trough rings
encapsulated within the well's bevel, doubling the valley on every
adjoining side. Now the face reaches exactly to the well wall's base
(inset = the carve depth) and those trough edges are suppressed — the
WELL'S OWN WALL is the seam's far side; only edges facing open floor
(each control's interior left, and the -/+ divider groove) carve their
own. troughs() carries per-edge flags; relief_parts returns them.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/widget/container/parameters_bg.rs | 54 ++++++++++++++++++---------------
src/widget/input/spinbox.rs | 57 +++++++++++++++++++++--------------
2 files changed, 65 insertions(+), 46 deletions(-)
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 596e50b..8b7d581 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -605,15 +605,13 @@ impl ParametersBg {
if self.display_params[i].2.starts_with("textpick") {
// The picker button nests INSIDE the text box's recessed
// well (the box spans the full row): below the detached
- // label band, inset from the well's walls by HALF the
- // well's carve depth — its trough ring
- // ([`Self::picker_troughs`]) straddles this outline by
- // ±depth/2, so the ring's outer edge lands exactly on the
- // well outline and the seam hugs the box's edges the way
- // the dropdown's groove hugs its face. (The full-depth
- // inset was the boss era's: a raised island needed a
- // strip of floor for its wall to abut the recess wall,
- // which read as a too-small button in a too-wide ring.)
+ // label band, its face reaching exactly to the base of
+ // the well's wall (inset = the carve depth) on the sides
+ // it adjoins — there the WELL'S OWN BEVEL is the seam's
+ // far side, and the trough ([`Self::troughs`]) carves
+ // only the interior left edge. A ring encapsulated
+ // within the bevel doubled the valley on the adjoining
+ // sides.
let label_top = if crate::layout::control_label_layout() == "side" {
0.0
} else {
@@ -621,7 +619,7 @@ impl ParametersBg {
+ crate::layout::control_label_margin()
};
let band_h = r.3 - label_top;
- let inset = 0.5 * crate::layout::bevel_width().min(band_h * 0.2);
+ let inset = crate::layout::bevel_width().min(band_h * 0.2);
let by = r.1 + label_top + inset;
let bh = (band_h - 2.0 * inset).max(8.0);
d.set_rect(r.0 + r.2 - PICK_W - inset, by, PICK_W, bh);
@@ -1246,15 +1244,20 @@ impl ParametersBg {
}
/// The textpick picker buttons' trough rings — `(x, y, w, h, radii, depth)`
- /// for [`crate::scene::paint::PaintCtx::trough`], drawn by the host AFTER
- /// [`Self::reliefs`]. These are the rows' FLUSH inset controls — the
+ /// for [`crate::scene::paint::PaintCtx::trough_edges`], drawn by the host
+ /// AFTER [`Self::reliefs`]. These are the rows' FLUSH inset controls — the
/// textpick picker button, and the spinbox's -/+ run: faces level with
- /// their well floor, a valley seam straddling the outline — the dropdown
- /// trigger's (and the breadcrumb run's) inset language. They cannot ride
- /// in [`Self::reliefs`], whose tuple only speaks boss/recess. Radii are
- /// the well radius's parallel curve at each control's inset; depths match
- /// the well's carve, so seam and wall read as one family.
- pub fn troughs(&self) -> Vec<(f32, f32, f32, f32, (f32, f32, f32, f32), f32)> {
+ /// their well floor. On the sides a control adjoins its well, the WELL'S
+ /// OWN WALL is the seam's far side (the face reaches the wall's base and
+ /// that trough edge is suppressed — a lip of its own there doubles the
+ /// valley); only edges facing open floor carve their own wall. They
+ /// cannot ride in [`Self::reliefs`], whose tuple only speaks boss/recess.
+ /// Radii are the well radius's parallel curve at each control's inset;
+ /// depths match the well's carve, so seam and wall read as one family.
+ #[allow(clippy::type_complexity)]
+ pub fn troughs(
+ &self,
+ ) -> Vec<(f32, f32, f32, f32, (f32, f32, f32, f32), f32, (bool, bool, bool, bool))> {
if !self.visible || !crate::layout::control_relief() {
return Vec::new();
}
@@ -1272,7 +1275,8 @@ impl ParametersBg {
if bw > 0.0 && bh > 0.0 {
let depth = crate::layout::bevel_width().min((th - ty) * 0.2);
let r = (crate::layout::textbox_corner_radius() - depth).max(2.0);
- out.push((bx, by, bw, bh, (r, r, r, r), depth));
+ // Left edge only: top, right and bottom adjoin the well.
+ out.push((bx, by, bw, bh, (r, r, r, r), depth, (false, false, false, true)));
}
}
} else if p.2.starts_with("spinbox") {
@@ -1280,8 +1284,10 @@ impl ParametersBg {
let (x, y, w, h) = sb.rect();
let ty = crate::widget::label_offset(sb);
let band = Rect { x, y: y + ty, width: w, height: h - ty };
- if let Some((_, Some(((run, radii, rd), _)))) = sb.inner().relief_parts(band) {
- out.push((run.x, run.y, run.width, run.height, radii, rd));
+ if let Some((_, Some(((run, radii, rd, edges), _)))) =
+ sb.inner().relief_parts(band)
+ {
+ out.push((run.x, run.y, run.width, run.height, radii, rd, edges));
}
}
}
@@ -1309,7 +1315,7 @@ impl ParametersBg {
let (x, y, w, h) = sb.rect();
let ty = crate::widget::label_offset(sb);
let band = Rect { x, y: y + ty, width: w, height: h - ty };
- if let Some((_, Some(((_, _, rd), (sa, sb2, sw, host))))) =
+ if let Some((_, Some(((_, _, rd, _), (sa, sb2, sw, host))))) =
sb.inner().relief_parts(band)
{
out.push((sa, sb2, sw, rd, host));
@@ -1465,8 +1471,8 @@ impl Paint for ParametersBg {
ctx.recess_edges(Rect { x: rx, y: ry, width: rw, height: rh }, radii, rd, edges);
}
}
- for (tx2, ty2, tw2, th2, radii, td) in self.troughs() {
- ctx.trough(Rect { x: tx2, y: ty2, width: tw2, height: th2 }, radii, td);
+ for (tx2, ty2, tw2, th2, radii, td, tedges) in self.troughs() {
+ ctx.trough_edges(Rect { x: tx2, y: ty2, width: tw2, height: th2 }, radii, td, tedges);
}
for (ga, gb, gw, gd, ghost) in self.grooves() {
ctx.groove(ga, gb, gw, gd, ghost);
diff --git a/src/widget/input/spinbox.rs b/src/widget/input/spinbox.rs
index c724fef..3f8bf1b 100644
--- a/src/widget/input/spinbox.rs
+++ b/src/widget/input/spinbox.rs
@@ -105,15 +105,19 @@ impl Spinbox {
/// relief IS the chrome:
/// - the whole control is a recessed well (the TextBox language — the
/// value sits on the well floor),
- /// - the -/+ pair is ONE flush inset run inside the well's right end,
- /// trough-ringed like the textpick picker and the dropdown trigger,
+ /// - the -/+ pair is ONE flush inset run in the well's right end. Where
+ /// the run adjoins the well (top, right, bottom) the WELL'S OWN WALL is
+ /// the seam's far side — the face reaches exactly to the wall's base
+ /// (inset = the carve depth) and those trough edges are suppressed; a
+ /// second lip inside the bevel would double the valley. Only the left
+ /// edge, which faces open floor, carries its own trough wall,
/// - the two buttons divide by an engraved seam, not a wall pair — the
/// breadcrumb run's segment language at miniature scale.
///
/// Returns the well `(rect, radius, depth)`, plus the run
- /// `(rect, radii, depth)` and seam `(top, bottom, width, host)` when the
- /// button zone is non-degenerate. `None` when the control has no area.
- /// The caller gates on `control_relief`.
+ /// `(rect, radii, depth, trough edges)` and seam `(top, bottom, width,
+ /// host)` when the button zone is non-degenerate. `None` when the control
+ /// has no area. The caller gates on `control_relief`.
#[allow(clippy::type_complexity)]
pub fn relief_parts(
&self,
@@ -121,7 +125,7 @@ impl Spinbox {
) -> Option<(
(Rect, f32, f32),
Option<(
- (Rect, (f32, f32, f32, f32), f32),
+ (Rect, (f32, f32, f32, f32), f32, (bool, bool, bool, bool)),
((f32, f32), (f32, f32), f32, Rect),
)>,
)> {
@@ -135,17 +139,22 @@ impl Spinbox {
if g.btn_w <= 0.0 || g.btn_h <= 0.0 {
return Some((well, None));
}
- // The run rides the existing button hit zones (inset `pad` from the
- // control edges — within a hair of the trough's depth/2 straddle, so
- // the ring's outer edge hugs the well outline; the picker lesson).
- // Right corners follow the well radius's parallel curve at that
- // inset; left corners stay tight — the run's left edge is interior.
- let run_rect = Rect { x: g.split_dec + g.pad, y: g.btn_y, width: 2.0 * g.btn_w, height: g.btn_h };
- let rr = (radius - g.pad).max(2.0);
- let run = (run_rect, (2.0, rr, rr, 2.0), depth);
+ // Face flush against the wall base on the adjoining sides; the left
+ // edge starts at the button hit zone. Right corners follow the well
+ // radius's parallel curve at the wall base; left corners stay tight —
+ // the run's left edge is interior.
+ let run_rect = Rect {
+ x: g.split_dec + g.pad,
+ y: g.y + depth,
+ width: (g.x + g.w - depth) - (g.split_dec + g.pad),
+ height: g.h - 2.0 * depth,
+ };
+ let rr = (radius - depth).max(2.0);
+ let run = (run_rect, (2.0, rr, rr, 2.0), depth, (false, false, false, true));
// Seam floor width: the breadcrumb's SEAM_WIDTH — a hair of flat
- // floor so the crease doesn't alias into a dotted line.
- let sx = run_rect.x + g.btn_w;
+ // floor so the crease doesn't alias into a dotted line. It sits on
+ // the -/+ hit boundary, not the painted run's midpoint.
+ let sx = g.split_dec + g.pad + g.btn_w;
let seam = ((sx, run_rect.y), (sx, run_rect.y + run_rect.height), 0.75, run_rect);
Some((well, Some((run, seam))))
}
@@ -277,15 +286,19 @@ impl Paint for Spinbox {
// washes and the editing cue survive a flat host's rounded-quad
// bridge, the carves are re-emitted host-side.
if let Some((well, buttons)) = self.relief_parts(rect) {
- if let Some(((run, radii, _), _)) = buttons {
+ if let Some(((run, radii, _, _), (seam_top, _, _, _))) = buttons {
let wash = [1.0, 1.0, 1.0, 0.06];
- let half = Rect { x: run.x, y: run.y, width: run.width * 0.5, height: run.height };
if self.hover_dec {
- ctx.rounded_rect(half, radii.0, (true, false, false, true), wash);
+ ctx.rounded_rect(
+ Rect { x: run.x, y: run.y, width: seam_top.0 - run.x, height: run.height },
+ radii.0,
+ (true, false, false, true),
+ wash,
+ );
}
if self.hover_inc {
ctx.rounded_rect(
- Rect { x: run.x + run.width * 0.5, ..half },
+ Rect { x: seam_top.0, y: run.y, width: run.x + run.width - seam_top.0, height: run.height },
radii.1,
(false, true, true, false),
wash,
@@ -309,8 +322,8 @@ impl Paint for Spinbox {
}
let (wr, wrad, wd) = well;
ctx.recess(wr, (wrad, wrad, wrad, wrad), wd);
- if let Some(((run, radii, rd), (sa, sb, sw, host))) = buttons {
- ctx.trough(run, radii, rd);
+ if let Some(((run, radii, rd, edges), (sa, sb, sw, host))) = buttons {
+ ctx.trough_edges(run, radii, rd, edges);
ctx.groove(sa, sb, sw, rd, host);
}
}