GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
7b-1: PlateSpec — plates carry their role; DemoApp migrates
RFC Phase 7b, first cut, with the blur-regime design note written into the
RFC first per the phase invariant. PlateSpec (scene/paint.rs): rect,
positive-alpha color, blur flag, per-corner window_corners role flags, and
perimeter depth. window_corner_flags derives the flags from window geometry
(the designer's pane_plate_radii derivation, moved in); radii_for gives
window-flagged corners the SHARED silhouette curve (window_corner_radius x
corner_span_factor) and interior corners the nominal plate radius; fill()
applies the role-correct frost encoding — root stays positive-alpha (the
compositor frosts behind the window), nested+blur takes the in-app frost
pass's negative-alpha sentinel. PaintCtx::plate_spec emits it.
DemoApp's hand-rolled root plate migrates to the spec — a deliberate
visual correction: its un-spanned root_plate_corner_radius detached the
perimeter shading from the compositor's span-widened clip under squircle
corner_shape; the spec snaps it to the silhouette.
Also two 7a stragglers the getter census could not see (raw JSON-pointer
reads): layout::window_corner_radius now reads the canonical
plate/root/corner_radius pointer first (get_i64_shared_opt added), legacy
backplate second. Role tests cover flag derivation, radii selection, and
both frost encodings; designer A/B AE=0, demo corner change verified
intended.
docs/rfc-core-rebuild.md | 37 ++++++++++++++
src/config.rs | 6 +++
src/layout.rs | 7 ++-
src/main.rs | 15 ++++--
src/scene/paint.rs | 126 +++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 187 insertions(+), 4 deletions(-)
diff --git a/docs/rfc-core-rebuild.md b/docs/rfc-core-rebuild.md
index 0474c5c..29167b6 100644
--- a/docs/rfc-core-rebuild.md
+++ b/docs/rfc-core-rebuild.md
@@ -2450,6 +2450,43 @@ Constraint respected: **each crate still builds standalone** — the new core is
`pane_plate_radii` in, and give the engine a root-plate paint path fed by a spec instead of
each app's hand-rolled quads (DemoApp first, then the clients). The designer's per-pane
plates convert to specs with computed role flags. A/B: AE=0 per app.
+
+ **Blur-regime design note (required before code; written 2026-08-25).** Two frost
+ regimes; the role selects between them:
+ - *Root plate* (all four corners on the window silhouette): the fill stays
+ POSITIVE-alpha translucent; the COMPOSITOR frosts what lies behind the window
+ (`plate.root.blur` in the shared config drives cce-fx's blur-behind). The app
+ draws no frost of its own.
+ - *Nested plate* (any interior corner): frost is the NEGATIVE-ALPHA sentinel on
+ the fill — the in-app vk frost pass blurs app content drawn BEFORE the plate
+ (draw-order-dependent by design; see `param_plate_fill`). A pane touching some
+ window edges is still nested for blur purposes: it frosts app content.
+ `PlateSpec` therefore stores `color` with positive alpha plus `blur: bool`, and
+ `fill()` applies the role-correct encoding: root → alpha forced non-negative,
+ nested+blur → alpha negated. DETACH is exactly a role flip: interior corners become
+ window corners, `fill()` flips regimes, and the formerly-frosted app content beneath
+ simply does not exist in the new window. No other app-side blur change is needed.
+
+ **Radii rule.** A window-flagged corner wears
+ `window_corner_radius() * corner_span_factor()` (the SHARED silhouette curve — the
+ invariant); an interior corner wears `plate_corner_radius()` (the app-overridable
+ pane value). This is `pane_plate_radii` verbatim, moved in.
+
+ - **7b-1 DONE (2026-08-25).** `PlateSpec` in `scene/paint.rs` (rect, positive-alpha
+ color, `blur`, per-corner `window_corners`, perimeter `depth`) with
+ `window_corner_flags(rect, win_w, win_h)`, `radii()`/`radii_for()`, role-aware
+ `fill()`, and `PaintCtx::plate_spec`. DemoApp's hand-rolled root plate migrated —
+ DELIBERATE visual correction: its radius was the un-spanned
+ `root_plate_corner_radius`, so under squircle `corner_shape` its perimeter shading
+ detached from the compositor's span-widened clip; the spec snaps it to the
+ silhouette (demo AE≠0 expected and intended). The designer's `pane_plate_radii`
+ delegates to the toolkit (A/B AE=0). Two 7a stragglers the getter census could not
+ see (raw JSON-pointer reads, not getter calls) gained the canonical-first chain:
+ `layout::window_corner_radius`'s shared-config read, and cce-grid's silhouette
+ read.
+ - **7b-2 — pending.** Migrate the remaining clients' hand-rolled root plates onto
+ `PlateSpec` (per-repo, AE=0 each) and convert the designer's pane plate EMISSION
+ (not just radii) to specs. 7c builds on the role flip.
- **7c — Detach/dock generalization.** Lift the designer's plate-corner control, collapse,
and dock-drag onto `PlateSpec` so any app can offer them. The detached-window PROCESS model
and sync channel (`default_project.json` polling) remain app policy — the toolkit provides
diff --git a/src/config.rs b/src/config.rs
index 96875f3..c18a935 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -614,6 +614,12 @@ pub fn get_i64_shared(pointer: &str, default: i64) -> i64 {
cached_shared_config().pointer(pointer).and_then(|v| v.as_i64()).unwrap_or(default)
}
+/// [`get_i64_shared`] without a default — for canonical-first alias chains
+/// (RFC Phase 7a) where absence must fall through to the next spelling.
+pub fn get_i64_shared_opt(pointer: &str) -> Option<i64> {
+ cached_shared_config().pointer(pointer).and_then(|v| v.as_i64())
+}
+
// ── Typed accessors over the cached config ──────────────────────────────────
// Each reads the mtime-cached config and extracts a value at a JSON pointer
// (e.g. "/notifications/enable"), returning the default when absent or mistyped.
diff --git a/src/layout.rs b/src/layout.rs
index c250f14..6740ee2 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1596,7 +1596,12 @@ pub fn corner_shape() -> f32 {
/// override file — or its corners detach from the silhouette (and from the
/// desktop grid's cells, which share the same knob).
pub fn window_corner_radius() -> f32 {
- crate::config::get_i64_shared("/style/surface/backplate/corner_radius", 12) as f32
+ // Canonical-first (RFC Phase 7a): the shared config may spell the
+ // silhouette radius either way; both feed the one value.
+ crate::config::get_i64_shared_opt("/style/surface/plate/root/corner_radius")
+ .unwrap_or_else(|| {
+ crate::config::get_i64_shared("/style/surface/backplate/corner_radius", 12)
+ }) as f32
}
/// The curvature-matched corner-span factor for window-scale squircle corners.
diff --git a/src/main.rs b/src/main.rs
index 2d97f77..00b09c0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -363,10 +363,19 @@ impl Application for DemoApp {
if plate[3] > 0.001 {
plate[3] = cce_ui::color::root_plate_opacity();
}
- let radius = cce_ui::colors::root_plate_corner_radius();
+ // The root plate as a PlateSpec (RFC Phase 7b): all four corners are
+ // window corners, so the radii come from the SHARED silhouette curve
+ // — under squircle corner_shape this widens the perimeter roll to
+ // match the compositor's clip, which the old hand-rolled
+ // root_plate_corner_radius did not.
let frame = Rect { x: 0.0, y: 0.0, width: w, height: h };
- let bevel = cce_ui::layout::bevel_width();
- pc.plate(frame, (radius, radius, radius, radius), plate, bevel);
+ pc.plate_spec(&cce_ui::scene::paint::PlateSpec {
+ rect: frame,
+ color: plate,
+ blur: false,
+ window_corners: (true, true, true, true),
+ depth: cce_ui::layout::bevel_width(),
+ });
// Header band: the title strip carved one step down into the plate. Flush to the
// window's top and sides, so its only real wall is the bottom one facing the
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index dda2714..71513d8 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -33,6 +33,83 @@ pub enum Cap {
/// `CornerRadii` order.
pub type Radii = (f32, f32, f32, f32);
+/// RFC Phase 7b: the ONE description of a lit base surface — a window's root
+/// plate or a nested pane plate — distinguished only by ROLE data, never by
+/// type. A window root is a plate whose four corners are all window corners;
+/// detaching a pane into its own window is a role flip, nothing more.
+///
+/// `color` always carries POSITIVE alpha; the frost encoding is applied by
+/// [`Self::fill`] per the role (see the Phase 7b blur-regime note in
+/// `docs/rfc-core-rebuild.md`): a root plate stays positive-alpha (the
+/// COMPOSITOR frosts behind the window), a nested plate with `blur` encodes
+/// the in-app frost pass's negative-alpha sentinel.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct PlateSpec {
+ pub rect: Rect,
+ /// Fill, linear RGBA, alpha positive — the role encoding is `fill()`'s.
+ pub color: [f32; 4],
+ /// Frost the surface (encoding per role; see `fill`).
+ pub blur: bool,
+ /// Which corners lie ON the window silhouette (TL, TR, BR, BL).
+ pub window_corners: (bool, bool, bool, bool),
+ /// Transition-band width of the rolled perimeter.
+ pub depth: f32,
+}
+
+impl PlateSpec {
+ /// All four corners on the silhouette: this plate IS the window's base
+ /// surface.
+ pub fn is_root(&self) -> bool {
+ let (tl, tr, br, bl) = self.window_corners;
+ tl && tr && br && bl
+ }
+
+ /// Which of `rect`'s corners lie on a `win_w` x `win_h` window's
+ /// silhouette (edge tolerance 1.5px) — the designer's `pane_plate_radii`
+ /// derivation, toolkit-side.
+ pub fn window_corner_flags(rect: Rect, win_w: f32, win_h: f32) -> (bool, bool, bool, bool) {
+ let e = 1.5;
+ let left = rect.x <= e;
+ let top = rect.y <= e;
+ let right = rect.x + rect.width >= win_w - e;
+ let bottom = rect.y + rect.height >= win_h - e;
+ (top && left, top && right, bottom && right, bottom && left)
+ }
+
+ /// Per-corner radii for `flags`: a window corner wears the SHARED
+ /// silhouette curve (`window_corner_radius * corner_span_factor` — the
+ /// compositor clips the window and the desktop grid draws its cells from
+ /// the same value, so window-corner arcs must follow it, never a per-app
+ /// plate override); an interior corner wears the nominal
+ /// `plate_corner_radius`.
+ pub fn radii_for(flags: (bool, bool, bool, bool)) -> Radii {
+ let nominal = crate::layout::plate_corner_radius();
+ let window_r =
+ crate::layout::window_corner_radius() * crate::layout::corner_span_factor();
+ let (tl, tr, br, bl) = flags;
+ let pick = |on: bool| if on { window_r } else { nominal };
+ (pick(tl), pick(tr), pick(br), pick(bl))
+ }
+
+ /// [`Self::radii_for`] over this spec's flags.
+ pub fn radii(&self) -> Radii {
+ Self::radii_for(self.window_corners)
+ }
+
+ /// The fill with the role-correct frost encoding: root → alpha forced
+ /// non-negative (the compositor's frost, not ours), nested + `blur` →
+ /// the in-app frost pass's negative-alpha sentinel.
+ pub fn fill(&self) -> [f32; 4] {
+ let mut c = self.color;
+ if self.is_root() {
+ c[3] = c[3].abs();
+ } else if self.blur {
+ c[3] = -c[3].abs();
+ }
+ c
+ }
+}
+
/// The **relief primitives** are the members of this enum that describe a lit
/// surface rather than a flat fill: [`Prim::Bevel`], [`Prim::Plate`],
/// [`Prim::Recess`], [`Prim::Boss`], [`Prim::Ridge`], [`Prim::ConcaveFillet`],
@@ -923,6 +1000,12 @@ impl PaintCtx {
self.push(Prim::Plate { rect, radii, color, depth });
}
+ /// Emit the plate a [`PlateSpec`] describes: role-resolved per-corner
+ /// radii and role-encoded frost (RFC Phase 7b).
+ pub fn plate_spec(&mut self, spec: &PlateSpec) {
+ self.plate(spec.rect, spec.radii(), spec.fill(), spec.depth);
+ }
+
pub fn arc(&mut self, cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, color: [f32; 4]) {
let (ox, oy) = self.offset;
self.push(Prim::Arc { cx: cx + ox, cy: cy + oy, radius, thickness, start, end, color });
@@ -1124,6 +1207,49 @@ impl crate::layout::RenderTarget for PaintCtx {
mod tests {
use super::*;
+ /// RFC Phase 7b: PlateSpec role mechanics — flag derivation from window
+ /// geometry, silhouette-vs-nominal radii selection, and the role-encoded
+ /// frost (root positive-alpha, nested negative-alpha sentinel).
+ #[test]
+ fn plate_spec_roles() {
+ // Flags: a full-window rect is root; an inset pane has none; a pane
+ // flush to the window's right edge owns the two right corners.
+ let root_flags = PlateSpec::window_corner_flags(
+ Rect { x: 0.0, y: 0.0, width: 800.0, height: 600.0 }, 800.0, 600.0);
+ assert_eq!(root_flags, (true, true, true, true));
+ let inset = PlateSpec::window_corner_flags(
+ Rect { x: 20.0, y: 20.0, width: 100.0, height: 100.0 }, 800.0, 600.0);
+ assert_eq!(inset, (false, false, false, false));
+ let right_pane = PlateSpec::window_corner_flags(
+ Rect { x: 500.0, y: 0.0, width: 300.0, height: 600.0 }, 800.0, 600.0);
+ assert_eq!(right_pane, (false, true, true, false));
+
+ // Radii: flagged corners wear the shared silhouette curve, interior
+ // ones the nominal plate radius (compared against the same getters,
+ // so the assertion holds for any configured values).
+ let window_r =
+ crate::layout::window_corner_radius() * crate::layout::corner_span_factor();
+ let nominal = crate::layout::plate_corner_radius();
+ let r = PlateSpec::radii_for((false, true, true, false));
+ assert_eq!(r, (nominal, window_r, window_r, nominal));
+
+ // Frost encoding by role.
+ let mut spec = PlateSpec {
+ rect: Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 },
+ color: [0.1, 0.2, 0.3, 0.8],
+ blur: true,
+ window_corners: (true, true, true, true),
+ depth: 3.0,
+ };
+ assert!(spec.is_root());
+ assert!(spec.fill()[3] > 0.0, "root frost is the compositor's; alpha stays positive");
+ spec.window_corners = (false, true, true, false);
+ assert!(!spec.is_root());
+ assert!(spec.fill()[3] < 0.0, "nested frost = negative-alpha sentinel");
+ spec.blur = false;
+ assert_eq!(spec.fill()[3], 0.8, "no frost, no encoding");
+ }
+
fn r(x: f32, y: f32, w: f32, h: f32) -> Rect {
Rect { x, y, width: w, height: h }
}