graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: the Alt+D dialog compresses its backdrop harder than the panes
The dialog plate drew through append_widget_plate, whose material is the
fill's own, so it carried the plates' compression — 0 in a config that
keeps the panes clear — and its rows sat on the full-contrast scene. It now
paints its plate with its own material at State::dialog_compression:
style.surface.dialog.compression in config.kdl, 0.8 when unset, above a
menu's 0.6. Opaque plates ignore it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
CLAUDE.md | 10 ++++++++++
src/app.rs | 18 ++++++++++++++++++
src/dialog.rs | 19 +++++++++++++++++++
src/main.rs | 19 +++++++++++++++++++
src/render.rs | 28 ++++++++++++++++++++++++----
5 files changed, 90 insertions(+), 4 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 47a644d..b6af66b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1592,6 +1592,16 @@ 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 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
point-number overlay all run AFTER the whole walk, so the graph's node labels drew
diff --git a/src/app.rs b/src/app.rs
index 90bf050..ff33f00 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1910,6 +1910,13 @@ 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),
@@ -5107,6 +5114,7 @@ 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()
@@ -5505,12 +5513,22 @@ 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;
diff --git a/src/dialog.rs b/src/dialog.rs
index 48a32fb..401d414 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -188,6 +188,25 @@ 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.
diff --git a/src/main.rs b/src/main.rs
index 05edda4..5fa0f33 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1384,6 +1384,25 @@ mod tests {
assert_eq!(m.match_command(&plain, &Key::Named(NamedKey::ArrowDown)), Some("play_pause_reverse"));
}
+ /// 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 => {}
+ }
+ }
+ 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
/// the start frame, from wherever it was and in either direction; from a
/// stop it is simply a jump to the start.
diff --git a/src/render.rs b/src/render.rs
index d3bbf2f..2523076 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -280,10 +280,30 @@ impl State {
// Modern-paint surface, the playbar's contract: the designer
// authors the plate (the dialog floats, so the pane radii and the
// focus tint do not apply — it is never a pane and never the
- // focused one), then Dialog::paint emits the tab strip, the query
- // line and the rows. A subtree painter, so append_frame_text skips
- // the slot and the chord column keeps its own font and bounds.
- append_widget_plate(w, pc);
+ // focused one), then Dialog::paint emits the query line and the
+ // 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.
+ 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),
+ }
w.paint_self(&self.ui_context, pc);
} else if idx == PLAYBAR_IDX {
// Modern-paint pane: the plate from the legacy views like the other