git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit82940c2b562e40c70a36be86fe3f110e9ac35537
parentea3d35effd
authorLucas Galante <[email protected]>
date2026-08-15 22:36
refactor: stack cce-relief's sliders; size the window to its content

Three knobs to a row truncated both the label and the readout, and the
two-wide Depth/Width row underneath set a different rhythm again for no
reason. All five are one column now, full width, which is what a slider
with a label AND a readout actually wants.

`content_height(width)` is the height the window wants — every fixed row
plus the cutaway shrink-wrapped — and `settings()` now asks for exactly
that instead of a guessed constant, with min_size using the same function
rather than an unrelated pair of numbers. There is nothing here to drag to
a different shape: the rows are fixed and the cutaway shrink-wraps, so a
user-chosen height only adds dark space or squeezes the section's wall,
the proportional axes being height-bound.

The request is currently only a request. The compositor restores a saved
size per app_id over it — this window comes up at the 520x480 it was left
at while it wants ~896 — so the cutaway sits collapsed on its 90px floor.
That is the case for a Utility window mode (self-sizing, not user
resizable, no saved geometry), which TilingMode::Status already almost is;
proposal recorded, compositor untouched.

 src/bin/cce-relief.rs | 70 ++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 55 insertions(+), 15 deletions(-)

diff --git a/src/bin/cce-relief.rs b/src/bin/cce-relief.rs
index 2b71cef..89e019a 100644
--- a/src/bin/cce-relief.rs
+++ b/src/bin/cce-relief.rs
@@ -70,6 +70,41 @@ const UNDERSIDE: f32 = 18.0;
 /// the shape will actually LOOK like, as opposed to what it is.
 const SHADE_STRIP_H: f32 = 14.0;
 
+/// The height this window WANTS at a given width: every fixed row, plus the
+/// cutaway at its natural (shrink-wrapped) height.
+///
+/// The window is CONTENT-SIZED. There is nothing here to drag to a different
+/// shape — the rows are fixed and the cutaway shrink-wraps its section — so a
+/// user-chosen height only ever adds dark space or squeezes the section's wall
+/// to a sliver, because the proportional axes are height-bound. Kept as one
+/// function so `settings()` asks for the same number the layout will use.
+///
+/// (The compositor may still restore a saved size over this; see the Utility
+/// window-mode proposal. Until then the request is only a request.)
+fn content_height(width: f32) -> f32 {
+    let pad = cce_ui::layout::backplate_padding();
+    let w = (width - 2.0 * pad).max(0.0);
+    let gap = 14.0;
+    let strip = {
+        let (_, fsize) = cce_ui::layout::control_label_font_detached_parsed();
+        fsize + cce_ui::layout::control_label_margin()
+    };
+    let knob_h = 22.0 + strip;
+    let button_h = 26.0;
+    let status_h = HEADER_FONT_SIZE + 4.0;
+    let fixed = knob_h + gap
+        + 5.0 * (knob_h + gap)
+        + button_h + 8.0 + status_h + 2.0 * gap;
+    let natural = (w - 2.0 * CUT_MARGIN - GUTTER_L - 2.0 * MIN_BAND)
+        + 2.0 * CUT_MARGIN
+        + UNDERSIDE
+        + GUTTER_B
+        + 2.0 * SHADE_STRIP_H
+        + 6.0
+        + GUTTER_B;
+    2.0 * pad + fixed + natural
+}
+
 #[derive(Debug, Clone)]
 enum BevelMsg {
     Exit,
@@ -1166,13 +1201,13 @@ impl Application for BevelPopup {
             },
             app_id: "cce-relief".to_string(),
             width: 520,
-            // Taller than the old cce-bevel default: the opening now carries
-            // the section PLUS the predicted and real shading bands, and a
-            // short window squeezes the section's wall to a sliver because the
-            // proportional axes are height-bound.
-            height: 700,
+            // Asked for, not guessed: the exact height the content occupies at
+            // this width (see `content_height`).
+            height: content_height(520.0).round() as u32,
             fullscreen: false,
-            min_size: Some((440, 420)),
+            // The floor is the same content height — this window has no useful
+            // smaller shape, and shrinking it only eats the cutaway.
+            min_size: Some((440, content_height(440.0).round() as u32)),
         }
     }
 
@@ -1225,8 +1260,14 @@ impl Application for BevelPopup {
             let knob_h = 22.0 + strip;
             let button_h = 26.0;
             let status_h = HEADER_FONT_SIZE + 4.0;
-            let fixed = knob_h + gap + knob_h + gap + knob_h + gap + button_h + 8.0 + status_h
-                + 3.0 * gap;
+            // Rows above/below the cutaway: the selector row, then FIVE
+            // stacked sliders, then buttons and status. Stacked rather than
+            // gridded because a slider's label and readout want the full width
+            // — three to a row truncated both, and the two-wide Depth/Width row
+            // set a different rhythm again for no reason.
+            let fixed = knob_h + gap                      // selector row
+                + 5.0 * (knob_h + gap)                    // Shoulder..Width
+                + button_h + 8.0 + status_h + 2.0 * gap;  // buttons + status
             // The cutaway absorbs spare height — but only up to its NATURAL
             // height for this width (the proportional square domain plus
             // gutters), so the opening shrink-wraps the section instead of
@@ -1240,11 +1281,10 @@ impl Application for BevelPopup {
                 + GUTTER_B;
             let cut_h = (self.height as f32 - 2.0 * pad - fixed).min(natural).max(90.0);
 
-            let kw = (w - 2.0 * gap) / 3.0;
             let knob_row = |k: &mut ProfileKnobs, x: f32, y: f32| {
-                k.shoulder.set_rect(x, y, kw, knob_h);
-                k.base.set_rect(x + kw + gap, y, kw, knob_h);
-                k.bias.set_rect(x + 2.0 * (kw + gap), y, kw, knob_h);
+                k.shoulder.set_rect(x, y, w, knob_h);
+                k.base.set_rect(x, y + (knob_h + gap), w, knob_h);
+                k.bias.set_rect(x, y + 2.0 * (knob_h + gap), w, knob_h);
             };
             let park = |k: &mut ProfileKnobs| {
                 k.shoulder.set_rect(-1000.0, -1000.0, 0.0, 0.0);
@@ -1274,10 +1314,10 @@ impl Application for BevelPopup {
                 knob_row(&mut self.edge, x, y);
                 park(&mut self.wall);
             }
+            y += 3.0 * (knob_h + gap);
+            self.depth_slider.set_rect(x, y, w, knob_h);
             y += knob_h + gap;
-            let half = (w - gap) / 2.0;
-            self.depth_slider.set_rect(x, y, half, knob_h);
-            self.width_slider.set_rect(x + half + gap, y, half, knob_h);
+            self.width_slider.set_rect(x, y, w, knob_h);
             y += knob_h + gap;
             self.save_button.set_rect(x, y, 96.0, button_h);
             self.reset_button.set_rect(x + 96.0 + 12.0, y, 96.0, button_h);