GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
No widget writes the frost sentinel by hand (RFC material, step 2d)
The context menu, the Dropdown popover and the menubar panel shared one
recipe spelled three times — the page or dropdown colour at menu_opacity
with the alpha negated — and the Graph negated its blur value into an
alpha. Material::popover is that recipe, and the Graph's plate is an
opaque or frosted Material; each site encodes through fill(Nested)
where the colour-typed flat path needs a colour. Material::fill_tint is
now the only place in the toolkit that writes a negative alpha.
Prim-identical against the pre-2a golden; 379 lib tests.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/scene/material.rs | 21 +++++++++++++++++++++
src/widget/container/menu.rs | 10 +++++-----
src/widget/core.rs | 9 ++++-----
src/widget/display/graph.rs | 17 ++++++++++++-----
src/widget/input/dropdown.rs | 17 ++++++-----------
5 files changed, 48 insertions(+), 26 deletions(-)
diff --git a/src/scene/material.rs b/src/scene/material.rs
index 74aef9b..49ebd46 100644
--- a/src/scene/material.rs
+++ b/src/scene/material.rs
@@ -213,6 +213,16 @@ impl Material {
}
}
+ /// The popover material: a menu, a context menu, a dropdown's open
+ /// surface. `base`'s colour at `style.surface.menu.opacity`
+ /// (`color::menu_opacity`) — not the colour's own alpha, since a page
+ /// colour is typically opaque and would resolve the frost to a solid
+ /// tint — and frosted at the DE recipe, so a menu shows the content
+ /// beneath it blurred and tinted rather than covering it.
+ pub fn popover(base: [f32; 4]) -> Self {
+ Self::opaque([base[0], base[1], base[2], crate::color::menu_opacity()]).with_frost(Frost::from_style())
+ }
+
// ---- derived materials -------------------------------------------------
/// The well floor cut into this plate: the same material with the tint
@@ -379,6 +389,17 @@ mod tests {
assert_eq!(Material::face([0.3, 0.3, 0.3, 0.7]).map(|m| m.tint), Some([0.3, 0.3, 0.3, 0.7]));
}
+ /// A popover is the base colour at menu opacity, frosted — the bytes the
+ /// three menu sites used to write by negating an alpha.
+ #[test]
+ fn popover_is_the_menu_recipe() {
+ let m = Material::popover([0.1, 0.2, 0.3, 1.0]);
+ let mut old = [0.1, 0.2, 0.3, 1.0];
+ old[3] = -crate::color::menu_opacity();
+ assert_eq!(m.fill(PlateRole::Nested), old);
+ assert!(m.frost.is_frosted());
+ }
+
/// The control-face rule: opaque or nothing.
#[test]
fn control_face_is_opaque_or_none() {
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 1b925e4..2934b7f 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -713,11 +713,11 @@ impl Paint for MenuBar {
pc.rect([0.02, 0.02, 0.05, 0.08], dx + 3.0, dy + 3.0, dw, dh);
pc.rect([0.02, 0.02, 0.05, 0.04], dx + 5.0, dy + 5.0, dw, dh);
pc.rect(theme.surface_border, dx, dy, dw, dh);
- // Frosted, like the Dropdown popover and the context menu (the
- // negative-alpha blur-behind sentinel): menus show what is
- // beneath them blurred and tinted, not covered.
- let mut bg = theme.surface_bg;
- bg[3] = -crate::color::menu_opacity();
+ // Frosted, like the Dropdown popover and the context menu: the
+ // popover material (`Material::popover`), encoded for the
+ // colour-typed flat path — menus show what is beneath them
+ // blurred and tinted, not covered.
+ let bg = crate::scene::Material::popover(theme.surface_bg).fill(crate::scene::PlateRole::Nested);
pc.rect(bg, dx + 1.0, dy + 1.0, dw - 2.0, dh - 2.0);
if let Some(di) = hovered {
let iy = dy + di as f32 * DROPDOWN_ITEM_H;
diff --git a/src/widget/core.rs b/src/widget/core.rs
index 297d855..d455ce5 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -518,11 +518,10 @@ pub mod context_menu {
let depth = crate::layout::bevel_width().min(self.h * 0.2);
let face = crate::color::page_low_color();
if face[3] > 0.001 {
- let mut frosted = face;
- // menu_opacity, not the color's own alpha: an opaque page
- // color resolved the frost to a solid tint (invisible).
- frosted[3] = -crate::color::menu_opacity();
- ctx.plate(rect, (r, r, r, r), &crate::scene::material::Material::from_fill(frosted), depth);
+ // The popover material: the page colour at menu_opacity,
+ // frosted (an opaque page colour would resolve the frost
+ // to a solid tint, invisible).
+ ctx.plate(rect, (r, r, r, r), &crate::scene::material::Material::popover(face), depth);
} else {
let (plateau, radii) = crate::layout::carve_inside(rect, (r, r, r, r), depth);
ctx.boss(plateau, radii, depth);
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index 3bbfbe7..06d53ca 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -340,17 +340,24 @@ impl Graph {
}
}
+ /// The graph's plate, as the colour-typed host paints it: the cell
+ /// colour (or a near-transparent black) at the network opacity, and
+ /// under `graph_blur` a frosted material whose tint alpha IS the blur
+ /// value — the knob doubles as the frost's opacity.
fn bg_color(&self) -> [f32; 4] {
- let mut c = if self.uniform_background {
+ use crate::scene::{Frost, Material, PlateRole};
+ let c = if self.uniform_background {
[self.cell_color[0], self.cell_color[1], self.cell_color[2], self.network_opacity]
} else {
[0.0, 0.0, 0.0, 0.01 * self.network_opacity]
};
let blur_val = crate::layout::graph_blur();
- if blur_val > 0.0 {
- c[3] = -blur_val.abs() * self.network_opacity;
- }
- c
+ let m = if blur_val > 0.0 {
+ Material::opaque([c[0], c[1], c[2], blur_val.abs() * self.network_opacity]).with_frost(Frost::from_style())
+ } else {
+ Material::opaque(c)
+ };
+ m.fill(PlateRole::Nested)
}
/// The grid cells' rounded-corner radius at the current zoom: the desktop
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 95f518f..ab1bd36 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -941,20 +941,15 @@ impl Paint for Dropdown {
// PaintCtx-backed target; collector hosts degrade to a rounded fill).
// The trigger's configured fill is usually transparent — the window
// plate IS its face — so the expansion substitutes the plate color,
- // FROSTED: the negative-alpha blur-behind sentinel, the same
- // material the context menu wears (`ContextMenuState::paint`), so
- // the menu shows the content beneath it blurred and tinted rather
- // than covering it. The magnitude is the color's own alpha — an
- // app styled fully opaque stays opaque, matching its windows.
+ // FROSTED: the popover material (`Material::popover`), the same the
+ // context menu wears (`ContextMenuState::paint`), so the menu shows
+ // the content beneath it blurred and tinted rather than covering it.
+ // Encoded here because the flat-path `RenderTarget` speaks colours.
let radius = crate::layout::dropdown_corner_radius();
let raw_bg = colors::dropdown_background_color();
let face = {
- let mut c = if raw_bg[3] > 0.001 { raw_bg } else { crate::color::page_low_color() };
- // Magnitude from menu_opacity, not the color's own alpha: page
- // colors are typically fully opaque, which would resolve the
- // frost to a solid tint and hide it entirely.
- c[3] = -crate::color::menu_opacity();
- c
+ let base = if raw_bg[3] > 0.001 { raw_bg } else { crate::color::page_low_color() };
+ crate::scene::Material::popover(base).fill(crate::scene::PlateRole::Nested)
};
if self.raised() {
let depth = crate::layout::bevel_width().min(rect.height * 0.2);