system settings
git clone https://git.lucas.co/cce-system-interface.git
feat: sections render as sunken wells with carved title tabs (the designer look)
PageContent claims section frames via the new section_relief hatch;
display_list carves each well with the designer union shape — bottom-open
title tab, concave-filleted throat, one piece owning the right run, a
bridge for the left wall — clipped to the page viewport and scroll-
shifted. Focused sections keep the green outline for ctrl-nav feedback;
relief-off styling keeps the legacy outline everywhere.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/app.rs | 23 ++++++++++++-
src/main.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/renderer.rs | 1 +
3 files changed, 126 insertions(+), 1 deletion(-)
diff --git a/src/app.rs b/src/app.rs
index 104df59..88bb081 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -98,6 +98,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>, AppAction)>,
+ /// Section wells claimed via `RenderTarget::section_relief` — the body box
+ /// plus the title tab box, carved into the window plate by display_list as
+ /// recess prims (page coordinates, pre-scroll).
+ pub reliefs: Vec<((f32, f32, f32, f32), Option<(f32, f32, f32, f32)>)>,
pub clip_stack: Vec<[f32; 4]>,
pub measure_only: bool,
}
@@ -108,6 +112,7 @@ impl Default for PageContent {
rects: Vec::new(),
texts: Vec::new(),
buttons: Vec::new(),
+ reliefs: Vec::new(),
clip_stack: Vec::new(),
measure_only: true,
}
@@ -116,7 +121,7 @@ impl Default for PageContent {
impl PageContent {
pub fn new() -> Self {
- Self { rects: Vec::new(), texts: Vec::new(), buttons: Vec::new(), clip_stack: Vec::new(), measure_only: false }
+ Self { rects: Vec::new(), texts: Vec::new(), buttons: Vec::new(), reliefs: Vec::new(), clip_stack: Vec::new(), measure_only: false }
}
fn get_clipped_rect(&self, x: f32, y: f32, w: f32, h: f32) -> Option<(f32, f32, f32, f32)> {
@@ -246,6 +251,22 @@ impl RenderTarget for PageContent {
self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), cb));
}
+ fn section_relief_style(&self) -> bool {
+ cce_ui::layout::control_relief()
+ }
+
+ fn section_relief(&mut self, f: &cce_ui::layout::SectionFrame) -> bool {
+ // Focused sections keep the legacy green outline (the ctrl-nav feedback);
+ // relief-off styling keeps the outline everywhere.
+ if f.focused || !cce_ui::layout::control_relief() {
+ return false;
+ }
+ if !self.measure_only {
+ self.reliefs.push(((f.x, f.y, f.w, f.h), f.tab));
+ }
+ true
+ }
+
fn push_clip_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
if self.measure_only { return; }
let clip = if let Some(&parent_clip) = self.clip_stack.last() {
diff --git a/src/main.rs b/src/main.rs
index 0b41210..9eabae3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -90,6 +90,9 @@ struct SystemInterface {
// window pass reads last frame's value, exactly as the legacy Page did.
page_scroll_bar: cce_ui::widget::Adapted<crate::scroll_bar::ScrollBar>,
content_h: f32,
+ // Section wells: body box + title tab (page coords, pre-scroll) — carved by
+ // display_list.
+ page_reliefs: Vec<((f32, f32, f32, f32), Option<(f32, f32, f32, f32)>)>,
// Root Backplate + StatusBar DISSOLVED (Phase 6s): the window plate and the status
// bar are emitted as tuples in rebuild_layout.
sans_serif_family: String,
@@ -178,6 +181,7 @@ impl cce_ui::engine::Application for SystemInterface {
page_dropdown,
page_scroll_bar: crate::scroll_bar::ScrollBar::new(),
content_h: 0.0,
+ page_reliefs: Vec::new(),
sans_serif_family: sans_family,
serif_family,
monospace_family,
@@ -281,6 +285,105 @@ impl cce_ui::engine::Application for SystemInterface {
pc.quad(rect, color);
}
}
+ // The section wells, carved after the page's flat quads so the walls shade
+ // the fills they cross (the designer relief order), clipped to the page
+ // viewport so a scrolled-off well can't shade the status bar or search row.
+ if !self.page_reliefs.is_empty() {
+ let view = Rect {
+ x: self.sidebar_width,
+ y: self.header_height,
+ width: width - self.sidebar_width,
+ height: (height - self.header_height - self.status_height
+ - if self.search_open { 42.0 } else { 0.0 }).max(0.0),
+ };
+ let r = 13.0f32;
+ let scroll_y = self.scroll_y;
+ pc.clip(view, |pc| {
+ for &((cx, cy, cw, ch), tab) in &self.page_reliefs {
+ let cy = cy - scroll_y;
+ let depth = cce_ui::layout::bevel_width().min(ch * 0.2);
+ match tab {
+ Some((tx, ty, tw, th)) => {
+ // The designer union carve: the title tab bottom-open, one
+ // piece owning the whole right run so its corners are real
+ // turns, a left piece carrying the left wall — pieces
+ // extend past their interior seam by `depth` so the walls
+ // crossfade there instead of notching — and the throat's
+ // inside corner rounded by a concave fillet.
+ let ty = ty - scroll_y;
+ let rt = r.min(th * 0.45);
+ let throat_r = tx + tw;
+ let rho = 10.0f32; // designer SECTION_FILLET_R
+ let body_lr = |x_run: f32, pc: &mut cce_ui::scene::paint::PaintCtx| {
+ pc.recess_edges(
+ Rect { x: x_run, y: cy, width: cx + cw - x_run, height: ch },
+ (0.0, r, r, 0.0),
+ depth,
+ (true, true, true, false),
+ );
+ pc.recess_edges(
+ Rect { x: cx, y: cy, width: x_run + depth - cx, height: ch },
+ (0.0, 0.0, 0.0, r),
+ depth,
+ (false, false, true, true),
+ );
+ };
+ if cx + cw > throat_r + 2.0 * rho {
+ // Filleted throat: the tab's right wall ends at the
+ // fillet's vertical tangent, a left-only bridge
+ // carries the left wall across the fillet span.
+ pc.recess_edges(
+ Rect { x: tx, y: ty, width: tw, height: (cy - rho) - ty + depth },
+ (rt, rt, 0.0, 0.0),
+ depth,
+ (true, true, false, true),
+ );
+ pc.recess_edges(
+ Rect { x: tx, y: cy - rho, width: tw, height: rho + depth },
+ (0.0, 0.0, 0.0, 0.0),
+ depth,
+ (false, false, false, true),
+ );
+ body_lr(throat_r + rho - depth, pc);
+ pc.concave_fillet(throat_r + rho, cy - rho, rho, depth, std::f32::consts::FRAC_PI_2, false);
+ } else if cx + cw > throat_r + 0.5 {
+ // Too narrow for the fillet: the plain square throat.
+ pc.recess_edges(
+ Rect { x: tx, y: ty, width: tw, height: (cy - ty) + depth },
+ (rt, rt, 0.0, 0.0),
+ depth,
+ (true, true, false, true),
+ );
+ body_lr(throat_r - depth, pc);
+ } else {
+ // The tab spans the body: no top wall at all.
+ pc.recess_edges(
+ Rect { x: tx, y: ty, width: tw, height: (cy - ty) + depth },
+ (rt, rt, 0.0, 0.0),
+ depth,
+ (true, true, false, true),
+ );
+ pc.recess_edges(
+ Rect { x: cx, y: cy, width: cw, height: ch },
+ (0.0, 0.0, r, r),
+ depth,
+ (false, true, true, true),
+ );
+ }
+ }
+ None => {
+ pc.recess_edges(
+ Rect { x: cx, y: cy, width: cw, height: ch },
+ (r, r, r, r),
+ depth,
+ (true, true, true, true),
+ );
+ }
+ }
+ }
+ });
+ }
+
cce_ui::widget::hover_animation::post_render_check();
if let Some((qx, qy, qw, qh, qc)) = cce_ui::widget::hover_animation::get_quad() {
pc.quad(Rect { x: qx, y: qy - self.scroll_y, width: qw, height: qh }, qc);
diff --git a/src/renderer.rs b/src/renderer.rs
index e9cbca0..53c4e1e 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -257,6 +257,7 @@ impl SystemInterface {
// Page content in LOGICAL coordinates, then scale to physical
let pc = self.render_page_content(lcx, lcy, lcw, lch);
+ self.page_reliefs = pc.reliefs.clone();