graphic design tool
git clone https://git.lucas.co/cce-designer.git
refactor: the dialog plate is the menu plate — one config block, no override
The command palette and the Add Node list wore the parameter plate's
fill under a backdrop compression of their own (DIALOG_COMPRESSION 0.8,
style.surface.dialog.compression), an in-app override the context menus
did not share; the menus wore the root plate colour at menu.opacity with
menu.compression. Two plates that could not be configured to match.
The dialog's plate is now cce_ui::widget::context_menu::paint_menu_plate,
the one function the context menus draw theirs with: Material::menu —
style.surface.menu's color, opacity and compression — on the menu corner
radius with the relief-width roll. The compression field, its constant,
its config key and plate_material are gone; Dialog::color is the menu
fill, its corners the menu radius, its border none.
the_dialog_plate_is_the_menu_plate checks the material and scans the
source for the override coming back. The designer's per-app config.kdl
sets the menu block to the look the dialog had.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CLAUDE.md | 28 +++++++++++++++++++---------
src/app.rs | 19 -------------------
src/dialog.rs | 37 ++++++++++++-------------------------
src/main.rs | 36 ++++++++++++++++++++----------------
src/render.rs | 29 ++++++++++-------------------
5 files changed, 61 insertions(+), 88 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index f5860b3..6dcb5fa 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1908,15 +1908,25 @@ sees one (`input.kdl`'s `cce-window-manager` domain has `super+d` on the app
launcher), and Super held is the DE's window-adjust modifier besides. Alt is the
app's own — the `move_*` family already lives there.
-**The dialog plate compresses its backdrop harder than anything else**
-(`State::dialog_compression`, `style.surface.dialog.compression` in
-config.kdl, default `DIALOG_COMPRESSION` 0.8 against a menu's 0.6). The
-plate's own compression can be 0 — a config that keeps the panes clear —
-and at that value the rows sat on the full-contrast scene and network. The
-render arm paints the plate itself with `dialog::plate_material` rather
-than through `append_widget_plate`, whose material comes from the fill alone,
-exactly as the node bodies do for `node_compression`. Opaque plates (blur
-off) have no backdrop and ignore it.
+**The dialog plate IS the menu plate** (since 2026-09-28). The render arm
+draws it with `cce_ui::widget::context_menu::paint_menu_plate`, the one
+function the context menus draw theirs with: `Material::menu` — the
+`style.surface.menu` block's `color` (a cce-ui key added the same day;
+absent, the root plate colour as menus always wore), `opacity` and
+`compression` — on `menu.corner_radius` with the relief-width roll. So
+the command palette, the Add Node list and every right-click menu are
+configured in ONE block and cannot be configured apart; the designer's
+per-app config.kdl sets that block to the look the dialog had
+(`#101018` at 0.06, compression 0.8, radius 24). Until then the dialog
+was the parameter plate's fill under a compression of its own
+(`DIALOG_COMPRESSION` 0.8, `style.surface.dialog.compression`) — an
+in-app override the menus did not share, and the reason the two plates
+looked nothing alike. `the_dialog_plate_is_the_menu_plate` scans the
+source for that override coming back. The one difference left is
+mechanical: the menus are hosted in the runner's popup surface, where the
+compositor's blur cannot compress and the helper folds `compression` into
+opacity, while the dialog is in-window and the in-app frost pass
+compresses as configured.
**The dialog is painted after the overlay passes, not in the widget walk.** A
high `z_order` is not enough: `append_frame_text`, `append_scale_readout` and the
diff --git a/src/app.rs b/src/app.rs
index eecf20b..1076b81 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -2095,13 +2095,6 @@ pub struct State {
/// pull the view toward the tint's key and read as solid. None = the
/// pane's.
pub node_compression: Option<f32>,
- /// The Alt+D dialog's backdrop compression (`style.surface.dialog.
- /// compression`, 0..1; [`crate::dialog::DIALOG_COMPRESSION`] when
- /// unset). The dialog is a modal read over whatever the scene and the
- /// network are doing, so it pulls its backdrop toward the tint harder
- /// than a pane or even a menu does — at the plate's own compression the
- /// rows sat on a busy, full-contrast picture.
- pub dialog_compression: f32,
pub last_design_mod_time: Option<std::time::SystemTime>,
pub last_config_mod_time: Option<std::time::SystemTime>,
pub floating_network_layout: (f32, f32, f32, f32),
@@ -5982,7 +5975,6 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
network_opacity: 0.95,
node_opacity: 1.0,
node_compression: None,
- dialog_compression: crate::dialog::DIALOG_COMPRESSION,
last_design_mod_time: {
let design_path = DesignSettings::file_path();
std::fs::metadata(&design_path).and_then(|m| m.modified()).ok()
@@ -6400,23 +6392,12 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
.and_then(|v| v.as_f64())
.map(|k| (k as f32).clamp(0.0, 1.0));
- let dialog_compression = cce_ui::config::cached_config()
- .pointer("/style/surface/dialog/compression")
- .and_then(|v| v.as_f64())
- .map(|k| (k as f32).clamp(0.0, 1.0))
- .unwrap_or(crate::dialog::DIALOG_COMPRESSION);
-
let mut changed = false;
if self.node_compression != node_compression {
self.node_compression = node_compression;
changed = true;
}
- if self.dialog_compression != dialog_compression {
- self.dialog_compression = dialog_compression;
- changed = true;
- }
-
if (self.network_opacity - opacity).abs() > 0.001 {
self.network_opacity = opacity;
changed = true;
diff --git a/src/dialog.rs b/src/dialog.rs
index 21c4405..a973908 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -187,25 +187,6 @@ const READOUT_GAP: f32 = 8.0;
/// Gap between the query line and the list.
const GAP: f32 = 8.0;
-/// The dialog plate's backdrop compression when `style.surface.dialog.
-/// compression` is unset — above a menu's 0.6, since a modal is read over
-/// the whole busy window rather than beside the one control that opened it.
-/// Compression pulls what shows through the frost toward the tint's key, so
-/// the rows read against an even ground whatever is behind them.
-pub const DIALOG_COMPRESSION: f32 = 0.8;
-
-/// The dialog plate's material: the param plate's fill, frosted as the
-/// plates are, with the compression raised to `compression`. An opaque
-/// plate (blur off in the config) has no backdrop to compress and is
-/// returned as it is.
-pub fn plate_material(compression: f32) -> cce_ui::scene::Material {
- let mut m = cce_ui::scene::Material::from_fill(colors::param_plate_fill());
- if let cce_ui::scene::Frost::Frosted { compression: c, .. } = &mut m.frost {
- *c = compression.clamp(0.0, 1.0);
- }
- m
-}
-
/// The dialog's rect inside a `width` x `height` window: centered
/// horizontally, and a little above centre vertically so the list grows into
/// the window's roomier half rather than down over the status bar.
@@ -707,19 +688,25 @@ impl Paint for Dialog {
true
}
- /// The dialog IS its own plate, the contract every floating surface in
- /// this app wears: the parameter plate's fill, so it tracks the configured
- /// tint, opacity and blur-behind marker with the panes.
+ /// The dialog IS a menu plate: `Material::menu` — `style.surface.menu`'s
+ /// colour, opacity and compression — the material the context menus
+ /// wear, so the palette and a right-click menu are configured in one
+ /// block and cannot drift apart. Until 2026-09-28 it was the parameter
+ /// plate's fill with a backdrop compression of its own (a constant, and
+ /// a `surface.dialog` config key), an in-app override the menus did not
+ /// share.
fn color(&self) -> [f32; 4] {
- colors::param_plate_fill()
+ cce_ui::scene::Material::menu().fill(cce_ui::scene::PlateRole::Nested)
}
+ /// A menu has no border: its edge is the rolled perimeter
+ /// `context_menu::paint_menu_plate` draws.
fn solid_border(&self) -> Option<([f32; 4], f32)> {
- colors::plate_border_color().map(|bc| (bc, colors::plate_border_thickness()))
+ None
}
fn corner_style(&self, _rect: Rect) -> Option<(f32, (bool, bool, bool, bool))> {
- let r = cce_ui::layout::plate_corner_radius();
+ let r = cce_ui::layout::menu_corner_radius();
(r > 0.0).then_some((r, (true, true, true, true)))
}
diff --git a/src/main.rs b/src/main.rs
index 6d71737..262dc9f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2384,23 +2384,27 @@ mod tests {
context_menu::hide();
}
- /// The dialog plate carries its own backdrop compression, above a
- /// menu's: whatever the plates' own is (0 in a config that keeps the
- /// panes clear), the modal pulls its backdrop toward the tint, and a
- /// value out of range is clamped rather than overshooting.
- #[test]
- fn the_dialog_plate_compresses_its_backdrop_harder_than_a_menu() {
- use cce_ui::scene::Frost;
- assert!(crate::dialog::DIALOG_COMPRESSION > cce_ui::color::menu_compression().min(0.6));
- for (asked, want) in [(0.8, 0.8), (1.7, 1.0), (-0.2, 0.0)] {
- match crate::dialog::plate_material(asked).frost {
- Frost::Frosted { compression, .. } => assert!((compression - want).abs() < 1e-6, "{asked} -> {compression}"),
- // Blur off: nothing behind the plate to compress.
- Frost::Opaque => {}
- }
+ /// The dialog plate IS the menu plate: its fill is `Material::menu`'s
+ /// and its corners are the menu radius, so the command palette and a
+ /// right-click menu read one config block (`style.surface.menu`) and
+ /// the app carries no compression, colour or radius of its own for it.
+ /// The source scan is the backstop: a `dialog/compression` reader or a
+ /// `DIALOG_COMPRESSION` constant coming back would be an override the
+ /// menus do not share.
+ #[test]
+ fn the_dialog_plate_is_the_menu_plate() {
+ use cce_ui::widget::model::Paint;
+ let dialog = crate::dialog::Dialog::new();
+ let menu = cce_ui::scene::Material::menu().fill(cce_ui::scene::PlateRole::Nested);
+ assert_eq!(dialog.color(), menu);
+ assert!(dialog.solid_border().is_none(), "a menu has no border");
+ let r = cce_ui::layout::menu_corner_radius();
+ let rect = cce_ui::scene::layout::Rect { x: 0.0, y: 0.0, width: 100.0, height: 100.0 };
+ assert_eq!(dialog.corner_style(rect).map(|(r, _)| r), (r > 0.0).then_some(r));
+ for file in ["src/dialog.rs", "src/render.rs", "src/app.rs"] {
+ let src = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join(file)).unwrap();
+ assert!(!src.contains("dialog/compression") && !src.contains("DIALOG_COMPRESSION"), "{file} overrides the menu material");
}
- let state = State::new(false);
- assert!((0.0..=1.0).contains(&state.dialog_compression));
}
/// Ctrl+Up rewinds: a moving timeline stops and the playhead lands on
diff --git a/src/render.rs b/src/render.rs
index 416bf60..3dcb1e9 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -284,26 +284,17 @@ impl State {
// rows. A subtree painter, so append_frame_text skips the slot and
// the chord column keeps its own font and bounds.
//
- // The plate is `append_widget_plate`'s, drawn here rather than by
- // it because that helper builds its material from the fill alone
- // and the dialog wants its own backdrop compression
- // (`State::dialog_compression`) — the node bodies' override, for
- // the same reason: the one knob that differs from the panes.
+ // The plate is THE MENU PLATE — `context_menu::paint_menu_plate`,
+ // the one function the context menus draw theirs with: the
+ // `style.surface.menu` material, radius and roll. Drawn here
+ // rather than through `append_widget_plate` because that helper
+ // builds a pane's material from its fill, and the dialog is not
+ // a pane; it is a menu that happens to have a query line. Not in
+ // a popup surface, so the in-app frost pass compresses its
+ // backdrop as the config says rather than folding that into
+ // opacity as the hosted menus must.
let (x, y, ww, h) = w.rect();
- let r = rect(x, y, ww, h);
- let cr = w.corner_radii();
- let radii = (cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left);
- let mat = crate::dialog::plate_material(self.dialog_compression);
- match w.solid_border() {
- Some(_) if cce_ui::layout::control_relief() => {
- pc.bevel(r, radii, &mat, cce_ui::colors::plate_bevel_width());
- }
- Some((border, thickness)) => {
- pc.fill_material(r, radii, &mat);
- pc.border(r, radii, [0.0; 4], border, thickness);
- }
- None => pc.fill_material(r, radii, &mat),
- }
+ cce_ui::widget::context_menu::paint_menu_plate(pc, rect(x, y, ww, h), false);
w.paint_self(&self.ui_context, pc);
} else if idx == PLAYBAR_IDX {
// Modern-paint pane: the plate from the legacy views like the other