git.lucas.co / cce-files
file manager
git clone https://git.lucas.co/cce-files.git

commitdbffa6acec23201da499435969df443f7654ba74
parent283e2f15ed
authorLucas Galante <[email protected]>
date2026-07-20 22:26
feat: adopt the SDF-lit plate system — window plate, band carves, control reliefs

The window backplate is now a Prim::Plate emitted first in display_list (a
lit slab with a rolled perimeter; band carves CSG-group into its draw). The
breadcrumb strip is carved one step down with a bottom-wall-only recess band,
and chooser mode carves the mirror band into the bottom for the action bar,
replacing the flat divider line.

Controls get the control_relief styling the flat-list pipeline was dropping:
buttons render as lit Bevel plates (Button::paint's raised branch), the view
dropdown / context menu / open-with dialog get edges-only Boss reliefs, and
the search, save-name, and open-with command boxes get recess wells.

Plumbing follows the ParametersBg::reliefs idiom for legacy-view hosts:
PageContent gains a reliefs channel (relief_raised / relief_recessed, no-ops
when control_relief is off) and AppWidget carries a WidgetFx tag so relief
prims keep z-order through the flat list, including viewport clipping.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/main.rs         | 141 ++++++++++++++++++++++++++++++++++++++++++++++------
 src/pages/browse.rs |  10 ++--
 src/pages/mod.rs    |  23 +++++++++
 3 files changed, 154 insertions(+), 20 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 5115f11..5e6505e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -113,8 +113,29 @@ fn occlude_against(
     Some(bounds)
 }
 
+/// Height of the chooser-mode bottom action bar (the band carved into the plate).
+const SELECT_BAR_H: f32 = 48.0;
+
+/// Bottom edge of the header strip (window pad + breadcrumb row + a breath) —
+/// the band carved one step down into the plate, recessed-MenuBar style.
+const HEADER_BAND_H: f32 = 46.0;
+
 // ── State ───────────────────────────────────────────────────────────
 
+/// How a flat quad renders under the SDF-lit plate system. `Flat` is the plain
+/// fill; the relief variants carry the roll/carve depth in px.
+#[derive(Clone, Copy, PartialEq)]
+enum WidgetFx {
+    Flat,
+    /// A lit Bevel plate: the quad's own fill plus a rolled, lit lip (raised
+    /// buttons with an opaque face).
+    Bevel(f32),
+    /// Edges-only raised plateau over whatever is painted below.
+    Boss(f32),
+    /// Edges-only carve into whatever is painted below (recessed wells).
+    Recess(f32),
+}
+
 struct AppWidget {
     x: f32,
     y: f32,
@@ -123,6 +144,7 @@ struct AppWidget {
     color: [f32; 4],
     radius: f32,
     corners: (bool, bool, bool, bool),
+    fx: WidgetFx,
 }
 
 #[derive(Clone)]
@@ -396,7 +418,7 @@ impl FilesystemApp {
         let usable_w = self.width as f32 - sidebar_w - (if has_sidebar { 1.0 } else { 0.0 }) - 32.0;
         let content_y = 16.0;
 
-        let select_bar_h = 48.0;
+        let select_bar_h = SELECT_BAR_H;
         let content_h = if self.select_mode {
             self.height as f32 - 32.0 - select_bar_h
         } else {
@@ -485,20 +507,21 @@ impl FilesystemApp {
                     cce_ui::layout::render_widget(&mut plain_pc, textbox, x, y, w, h, &mut self.ui_context);
                 }
 
-                // Replicate the legacy aggregate's GLOBAL order: every plain child quad,
-                // then the dissolved root plate (its translucent wash sat over the plain
-                // content), then every rounded child quad.
+                // Legacy aggregate order: plain child quads, then rounded child quads.
+                // The dissolved root plate that used to sit between them is now a
+                // Prim::Plate emitted FIRST in display_list — the lit window slab the
+                // rest of the frame sits on (and the surface the band carves CSG into).
                 let (plain, rounded): (Vec<_>, Vec<_>) = plain_pc.rects.into_iter().partition(|r| r.5 <= 0.1);
                 window_pc.rects.extend(plain);
-                let mut c = cce_ui::color::page_low_color();
-                if c[3] > 0.001 {
-                    c[3] = cce_ui::color::active_backplate_opacity();
-                }
-                let radius = cce_ui::color::backplate_corner_radius();
-                window_pc.rects.push((c, 0.0, 0.0, self.width as f32, self.height as f32, radius.max(0.0), (radius > 0.1, radius > 0.1, radius > 0.1, radius > 0.1)));
                 window_pc.rects.extend(rounded);
                 window_pc.texts.extend(plain_pc.texts);
                 window_pc.buttons.extend(plain_pc.buttons);
+                window_pc.reliefs.extend(plain_pc.reliefs);
+
+                // The view dropdown's raised plate (control_relief) lives in its
+                // modern paint(); the flat view loses it — restore it edges-only.
+                let (dx, dy, dw, dh) = (*self_ptr).view_dropdown.rect();
+                window_pc.relief_raised(dx, dy, dw, dh, cce_ui::layout::dropdown_corner_radius());
             }
         }
 
@@ -528,8 +551,11 @@ impl FilesystemApp {
         // to the viewport and would swallow the bar entirely.
         if self.select_mode {
             let bar_y = self.height as f32 - select_bar_h - 16.0;
-            // Divider line
-            window_pc.rect([0.15, 0.20, 0.16, 1.0], browse_x, bar_y, usable_w, 1.0);
+            // Divider line — under control_relief the bar is a band carved into the
+            // plate (see display_list), so the flat line is the fallback only.
+            if !cce_ui::layout::control_relief() {
+                window_pc.rect([0.15, 0.20, 0.16, 1.0], browse_x, bar_y, usable_w, 1.0);
+            }
 
             let accent = [0.36, 0.56, 0.38, 1.0];
             let text_fg = [0.83, 0.83, 0.83, 1.0];
@@ -588,6 +614,8 @@ impl FilesystemApp {
                 let iy = cy + h_idx as f32 * ROW_H;
                 context_menu_pc.rect([0.20, 0.40, 0.65, 0.6], cx + 2.0, iy + 2.0, cw - 4.0, 20.0);
             }
+            // Raised menu plate (control_relief styling).
+            context_menu_pc.relief_raised(cx, cy, cw, ch, 0.0);
             // Text options
             for (idx, (opt, _)) in self.context_menu.options.iter().enumerate() {
                 let iy = cy + idx as f32 * ROW_H + (ROW_H - 12.0) / 2.0;
@@ -625,6 +653,9 @@ impl FilesystemApp {
 
             // Set textbox position dynamically using configured textbox height
             textbox.set_rect(tb_x, tb_y, tb_w, tb_h);
+            // Raised dialog plate + recessed command well (control_relief styling).
+            dialog_pc.relief_raised(dialog_x, dialog_y, dialog_w, dialog_h, 0.0);
+            dialog_pc.relief_recessed(tb_x, tb_y, tb_w, tb_h, cce_ui::layout::textbox_corner_radius());
 
             let cancel_hover = self.cursor_x >= btn_cancel_x && self.cursor_x <= btn_cancel_x + btn_cancel_w
                 && self.cursor_y >= btn_cancel_y && self.cursor_y <= btn_cancel_y + btn_cancel_h;
@@ -665,6 +696,26 @@ impl FilesystemApp {
                     color: *c,
                     radius: *r,
                     corners: *corners,
+                    fx: WidgetFx::Flat,
+                });
+            }
+            for (rx, ry, rw, rh, rr, rd, raised) in &pc_part.reliefs {
+                let (mut wy, mut wh) = (*ry, *rh);
+                if is_page_content {
+                    match clip_to_viewport(wy, wh, content_y, content_y + content_h) {
+                        Some((cy, ch)) => { wy = cy; wh = ch; }
+                        None => continue,
+                    }
+                }
+                widgets.push(AppWidget {
+                    x: *rx,
+                    y: wy,
+                    w: *rw,
+                    h: wh,
+                    color: [0.0; 4],
+                    radius: *rr,
+                    corners: (true, true, true, true),
+                    fx: if *raised { WidgetFx::Boss(*rd) } else { WidgetFx::Recess(*rd) },
                 });
             }
             for (btn, action) in &pc_part.buttons {
@@ -691,6 +742,15 @@ impl FilesystemApp {
                     && self.cursor_y >= wy && self.cursor_y <= wy + wh;
                 let col = if hovering { hover_bg } else { bg };
 
+                // Raised button plate (control_relief): the fill becomes a lit
+                // Bevel — rolled lip owns the edge — mirroring Button::paint's
+                // raised branch. Transparent fills degrade to edges-only Boss.
+                let fx = if cce_ui::layout::control_relief() {
+                    let depth = cce_ui::layout::bevel_width().min(wh * 0.2);
+                    if col[3] > 0.001 { WidgetFx::Bevel(depth) } else { WidgetFx::Boss(depth) }
+                } else {
+                    WidgetFx::Flat
+                };
                 widgets.push(AppWidget {
                     x: wx,
                     y: wy,
@@ -699,6 +759,7 @@ impl FilesystemApp {
                     color: col,
                     radius: 4.0, // standard button radius
                     corners: (true, true, true, true),
+                    fx,
                 });
 
                 let text_x = if btn.justify == cce_ui::widget::Justification::Left {
@@ -1147,12 +1208,60 @@ impl Application for FilesystemApp {
         }
         use cce_ui::scene::layout::Rect;
         let mut pc = cce_ui::scene::paint::PaintCtx::new();
+        let (fw, fh) = (self.width as f32, self.height as f32);
+
+        // The window plate — the dissolved root Backplate as a lit object: page-low
+        // color at the configured opacity, perimeter rolled over bevel_width so the
+        // surface reads as a physical plate rather than a flat fill.
+        let mut plate = cce_ui::color::page_low_color();
+        if plate[3] > 0.001 {
+            plate[3] = cce_ui::color::active_backplate_opacity();
+        }
+        let radius = cce_ui::color::backplate_corner_radius().max(0.0);
+        pc.plate(
+            Rect { x: 0.0, y: 0.0, width: fw, height: fh },
+            (radius, radius, radius, radius),
+            plate,
+            cce_ui::layout::bevel_width(),
+        );
+
+        // Band carves, emitted right after the plate so they CSG-group into its
+        // draw: the header strip (breadcrumb row) one step down, flush to the top
+        // and sides so its only wall is the bottom one; in chooser mode the action
+        // bar is the mirror band carved into the bottom.
+        if cce_ui::layout::control_relief() {
+            let wall = cce_ui::layout::bar_wall_width();
+            pc.recess_edges(
+                Rect { x: 0.0, y: 0.0, width: fw, height: HEADER_BAND_H },
+                (0.0, 0.0, 0.0, 0.0),
+                wall,
+                (false, false, true, false),
+            );
+            if self.select_mode {
+                let band_h = SELECT_BAR_H + 16.0;
+                pc.recess_edges(
+                    Rect { x: 0.0, y: fh - band_h, width: fw, height: band_h },
+                    (0.0, 0.0, 0.0, 0.0),
+                    wall,
+                    (true, false, false, false),
+                );
+            }
+        }
+
         for w in &self.widgets {
             let rect = Rect { x: w.x, y: w.y, width: w.w, height: w.h };
-            if w.radius > 0.1 {
-                pc.rounded_rect(rect, w.radius, w.corners, w.color);
-            } else {
-                pc.quad(rect, w.color);
+            let radii = (w.radius, w.radius, w.radius, w.radius);
+            match w.fx {
+                WidgetFx::Bevel(depth) => pc.bevel(rect, radii, w.color, depth),
+                WidgetFx::Boss(depth) => pc.boss(rect, radii, depth),
+                WidgetFx::Recess(depth) => pc.recess(rect, radii, depth),
+                WidgetFx::Flat => {
+                    if w.radius > 0.1 {
+                        pc.rounded_rect(rect, w.radius, w.corners, w.color);
+                    } else {
+                        pc.quad(rect, w.color);
+                    }
+                }
             }
         }
         for (text, font_size, x, y, col, font, bounds) in &self.texts {
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index ee5a71b..127ce96 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -274,15 +274,16 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
 
     state.list.push_prims(&mut pc);
     if state.search_visible {
-        cce_ui::layout::render_widget(
-            &mut pc,
-            &mut state.search_box,
+        let (sx, sy, sw, sh) = (
             list_x + 8.0,
             list_y + list_h - search_offset + search_margin_y,
             list_w - 16.0,
             search_h,
-            ctx,
         );
+        cce_ui::layout::render_widget(&mut pc, &mut state.search_box, sx, sy, sw, sh, ctx);
+        // The TextBox's recessed well lives in its modern paint(); the flat view
+        // this host renders through loses it, so carve it here.
+        pc.relief_recessed(sx, sy, sw, sh, cce_ui::layout::textbox_corner_radius());
     }
 
     // Count label in the bottom right corner of the scrolling list
@@ -301,6 +302,7 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
         let (tx, ty, tw, th) = layout.allocate(client_w, textbox_h);
         state.save_name_box.set_row_rect(tx, tw);
         cce_ui::layout::render_widget(&mut pc, &mut state.save_name_box, tx, ty, tw, th, ctx);
+        pc.relief_recessed(tx, ty, tw, th, cce_ui::layout::textbox_corner_radius());
     }
 
     pc
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index e70ce10..ed4b483 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -35,6 +35,10 @@ pub struct PageContent {
     pub rects: Vec<([f32; 4], f32, f32, f32, f32, f32, (bool, bool, bool, bool))>,
     pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>)>,
     pub buttons: Vec<(cce_ui::widget::Adapted<cce_ui::widget::Button>, crate::Message)>,
+    /// Relief steps for the control_relief styling — (x, y, w, h, radius, depth,
+    /// raised). The flat rects own the faces; these are the edges-only boss/recess
+    /// walls emitted over them (the ParametersBg::reliefs idiom for flat-view hosts).
+    pub reliefs: Vec<(f32, f32, f32, f32, f32, f32, bool)>,
 }
 
 impl PageContent {
@@ -43,6 +47,7 @@ impl PageContent {
             rects: Vec::new(),
             texts: Vec::new(),
             buttons: Vec::new(),
+            reliefs: Vec::new(),
         }
     }
 
@@ -50,6 +55,24 @@ impl PageContent {
         self.rects.push((color, x, y, w, h, 0.0, (true, true, true, true)));
     }
 
+    /// A recessed well carved over the control at (x, y, w, h) — no-op when the
+    /// DE's control_relief styling is off.
+    pub fn relief_recessed(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
+        if cce_ui::layout::control_relief() {
+            let depth = cce_ui::layout::bevel_width().min(h * 0.2);
+            self.reliefs.push((x, y, w, h, radius, depth, false));
+        }
+    }
+
+    /// A raised plateau over the control at (x, y, w, h) — no-op when the DE's
+    /// control_relief styling is off.
+    pub fn relief_raised(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
+        if cce_ui::layout::control_relief() {
+            let depth = cce_ui::layout::bevel_width().min(h * 0.2);
+            self.reliefs.push((x, y, w, h, radius, depth, true));
+        }
+    }
+
     pub fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
         self.texts.push((content.to_string(), size, x, y, color, None, None));
     }