GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
test: make the material finish and button centring tests hermetic
finish_defaults_and_layout failed whenever binding_semantics had run
first: that test reloads `relief spec=0.5 shininess=20 curvature=0.1`
into the process-wide finish, then "restored" it with set_finish_*,
which under cfg(test) writes only its own thread's overlay. The globals
stayed at 0.5/20/0.1, and the empty reload keeps an absent knob by
design. Reproducible with `--test-threads=1`.
- binding_semantics snapshots the DE finish before its reload, reloads
those values back (multi-line: one-line `a { b }` KDL parses to an
empty document), and asserts the finish is back.
- finish_defaults_and_layout checks the shipped literals as the
statics' initial values (FINISH_*_DEFAULT) and pins the getters on its
own thread, so neither the machine's config nor another test's reload
reaches it.
a_label_is_centred_on_its_drawn_width failed on a machine whose font
system holds only Noto Color Emoji: every glyph shapes ~15 px wide,
"Load Images" came to 164 px, and in the fixed 160 px button the label
overflows, which is left-aligned and clipped by design. The measure was
consistent; the test assumed the label fits. The button is now sized
from the measured label. Checked it still catches what it guards:
centring on the old inked measure fails it (87 px left vs -7 px right).
Full lib suite: 508/508, six parallel runs and one at --test-threads=1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NwZ8nBQYEej9ic3tVC5v7Z
src/color.rs | 11 ++++++++---
src/scene/material.rs | 39 ++++++++++++++++++++++++++++-----------
src/widget/input/button.rs | 12 +++++++++---
3 files changed, 45 insertions(+), 17 deletions(-)
diff --git a/src/color.rs b/src/color.rs
index 6e5ea87..c4af422 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -1951,9 +1951,14 @@ static PLATE_BLUR: RwLock<bool> = RwLock::new(false);
/// (0.4 / 24 / 0.2); the getters exist so the DE's plastic can be edited and
/// so a named material (RFC material, step 4) has somewhere to land. No config
/// path yet: the defaults ARE the shipped look.
-static FINISH_SPEC: RwLock<f32> = RwLock::new(0.4);
-static FINISH_SHININESS: RwLock<f32> = RwLock::new(24.0);
-static FINISH_CURVATURE: RwLock<f32> = RwLock::new(0.2);
+static FINISH_SPEC: RwLock<f32> = RwLock::new(FINISH_SPEC_DEFAULT);
+static FINISH_SHININESS: RwLock<f32> = RwLock::new(FINISH_SHININESS_DEFAULT);
+static FINISH_CURVATURE: RwLock<f32> = RwLock::new(FINISH_CURVATURE_DEFAULT);
+/// The finish a config without `relief spec` / `shininess` / `curvature`
+/// gets: the literals the shader shipped with.
+pub(crate) const FINISH_SPEC_DEFAULT: f32 = 0.4;
+pub(crate) const FINISH_SHININESS_DEFAULT: f32 = 24.0;
+pub(crate) const FINISH_CURVATURE_DEFAULT: f32 = 0.2;
/// The default material's blur radius (`style.surface.plate.frost.radius`,
/// the kernel sigma in logical px) — [`crate::scene::Frost::DEFAULT_RADIUS`]
diff --git a/src/scene/material.rs b/src/scene/material.rs
index 9398af9..f9cefb4 100644
--- a/src/scene/material.rs
+++ b/src/scene/material.rs
@@ -529,18 +529,26 @@ mod tests {
crate::color::set_plate_blur(false);
}
- /// The finish defaults are the literals the shader shipped with, and the
- /// push-constant layout is unchanged.
+ /// The finish defaults are the literals the shader shipped with, the DE
+ /// finish reads the three getters, and the push-constant layout is
+ /// unchanged.
+ ///
+ /// Read through `from_style` unpinned, the defaults were whatever the
+ /// process-wide values were: the machine's config, or what a test that
+ /// reloads the knobs had left there — `binding_semantics` left spec 0.5
+ /// until 2026-10-05, so this failed whenever it ran after that one. The
+ /// defaults are checked as the statics' initial values and the getters
+ /// pinned on this thread.
#[test]
fn finish_defaults_and_layout() {
+ use crate::color::{FINISH_CURVATURE_DEFAULT, FINISH_SHININESS_DEFAULT, FINISH_SPEC_DEFAULT};
+ assert_eq!((FINISH_SPEC_DEFAULT, FINISH_SHININESS_DEFAULT, FINISH_CURVATURE_DEFAULT), (0.4, 24.0, 0.2));
+ crate::color::set_finish_spec(0.9);
+ crate::color::set_finish_shininess(30.0);
+ crate::color::set_finish_curvature(0.3);
let f = Finish::from_style();
- assert_eq!(f.spec, 0.4);
- assert_eq!(f.shininess, 24.0);
- assert_eq!(f.curvature, 0.2);
+ assert_eq!((f.spec, f.shininess, f.curvature), (0.9, 30.0, 0.3));
assert_eq!(f.to_array(), [f.strength, f.spec, f.shininess, f.curvature]);
- crate::color::set_finish_spec(0.9);
- assert_eq!(Finish::from_style().spec, 0.9);
- crate::color::set_finish_spec(0.4);
}
/// The frost recipe reads the two plate-rung keys and carries the default
@@ -779,6 +787,11 @@ mod tests {
let _lock = crate::color::test_color_state_lock();
crate::layout::lazy_init_style_registry();
let _ = crate::color::plate_blur(); // fire the once-per-process load BEFORE the reload
+ // The DE finish this reload changes is process-wide, and an absent
+ // knob keeps its value through the empty reload below: put back
+ // what was there. (`set_finish_*` cannot — under `cfg(test)` a
+ // setter writes this thread's overlay, not the globals.)
+ let finish_before = Finish::from_style();
crate::color::reload_colors(r##"
style {
surface {
@@ -807,10 +820,14 @@ mod tests {
assert_eq!(Material::named("matte").map(|m| m.finish.strength), Some(0.3 / 0.15));
assert_eq!(Material::control(), Material::legacy(PlateRung::Control), "unknown name = unbound");
assert!(Frost::from_style().is_frosted(), "an opaque bound pane leaves the DE recipe to the keys");
- crate::color::set_finish_spec(0.4);
- crate::color::set_finish_shininess(24.0);
- crate::color::set_finish_curvature(0.2);
+ // Multi-line: `a { b }` on one line does not parse, and the loader
+ // reads that as an empty document.
+ crate::color::reload_colors(&format!(
+ "style {{\n surface {{\n relief spec=(f64){:?} shininess=(f64){:?} curvature=(f64){:?}\n }}\n}}\n",
+ finish_before.spec, finish_before.shininess, finish_before.curvature,
+ ));
crate::color::reload_colors("");
+ assert_eq!(Finish::from_style(), finish_before, "the DE finish is back for the tests after this one");
}
/// A popover is the base colour at menu opacity, frosted — the bytes the
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index 3d76745..064f693 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -724,6 +724,12 @@ mod tests {
/// the shaped label is equal. Measuring in a face the label is not drawn
/// in (first the inked button family, then the UI sans) left labels off
/// centre and, worse, under-measured for `intrinsic_size`.
+ ///
+ /// The button is sized from the label, so it fits in whatever face this
+ /// machine shapes it in. At a fixed 160 px it did not everywhere: a font
+ /// system holding only a colour-emoji face draws every glyph ~15 px wide,
+ /// "Load Images" came to 164 px, and an overflowing label is left-aligned
+ /// and clipped by design (`button_label_stays_inside_the_button`).
#[test]
fn a_label_is_centred_on_its_drawn_width() {
let b = Button::model(ButtonKind::Primary);
@@ -737,8 +743,9 @@ mod tests {
.map(|&(_, t)| t)
.unwrap()
};
- let (x, _) = painted_label(label, 160.0);
- let (left, right) = (x - 10.0, 170.0 - (x + drawn));
+ let w = drawn + 80.0;
+ let (x, _) = painted_label(label, w);
+ let (left, right) = (x - 10.0, 10.0 + w - (x + drawn));
assert!((left - right).abs() < 1.0, "{label:?}: {left:.1}px left vs {right:.1}px right");
}
}
@@ -900,4 +907,3 @@ mod focus_ring_tests {
assert_eq!(edges(&b), vec![None]);
}
}
-