git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit562cb24a752579c8bc497c53e2fa896dc09f3958
parent9b2a114695
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-28 11:52
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