file manager
git clone https://git.lucas.co/cce-files.git
feat: mirror the breadcrumb's slanted segment seams
render_widget drops the relief prims Breadcrumb::paint emits, so every page
showing a breadcrumb carves it app-side. That carve is now one plate for the
whole segment run plus a groove per seam, and it lives in one place —
pages::breadcrumb_relief — instead of the same block copied into all three
pages.
The seams did not appear at first: rebuild_layout merged each page's
PageContent field by hand, four sites of `pc.x.extend(other.x)`, and nothing
adds a line for a vec you just introduced. PageContent::absorb replaces those
runs and destructures the source, so the next new field is a compile error
rather than a mark missing from the screen.
Co-Authored-By: Claude <[email protected]>
src/main.rs | 56 +++++++++++++++++++++++++++++++++-------------------
src/pages/browse.rs | 14 +++++--------
src/pages/mod.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/pages/network.rs | 10 +---------
src/pages/space.rs | 6 +-----
5 files changed, 99 insertions(+), 43 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index f1c41d3..4c8781e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -136,6 +136,11 @@ enum WidgetFx {
/// A GPU-textured quad; the id comes from `cce_ui::vk::upload_rgba`
/// (the preview pane's image). `color` is unused.
Image { id: u32, alpha: f32 },
+ /// A line engraved from (ax, ay) to (bx, by) into the widget's rect, which
+ /// is the HOST surface here rather than the mark's own bounds — the shading
+ /// fades out across the host's rolled edge. The breadcrumb's slanted seams;
+ /// the only quad in this list that is not axis-aligned.
+ Groove { ax: f32, ay: f32, bx: f32, by: f32, width: f32, depth: f32 },
}
struct AppWidget {
@@ -523,13 +528,12 @@ impl FilesystemApp {
// 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);
+ let mut plain_pc = plain_pc;
+ let (plain, rounded): (Vec<_>, Vec<_>) =
+ std::mem::take(&mut plain_pc.rects).into_iter().partition(|r| r.5 <= 0.1);
window_pc.rects.extend(plain);
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);
- window_pc.images.extend(plain_pc.images);
+ window_pc.absorb(plain_pc);
// The view dropdown's flush inset plate (control_relief) lives
// in its modern paint(); the flat view loses it — restore it as
@@ -549,31 +553,19 @@ impl FilesystemApp {
let (bx, by, bw, bh) = self.browse_split.left_rect();
let browse_pc = pages::browse::view(&mut self.browse, &mut self.view_dropdown, bx, by, bw, bh, self.select_mode, &mut self.ui_context);
- pc.rects.extend(browse_pc.rects);
- pc.texts.extend(browse_pc.texts);
- pc.buttons.extend(browse_pc.buttons);
- pc.reliefs.extend(browse_pc.reliefs);
- pc.images.extend(browse_pc.images);
+ pc.absorb(browse_pc);
}
Page::Network => {
let (nx, ny, nw, nh) = self.network_split.left_rect();
let network_pc = pages::network::view(&mut self.network, &self.browse, &mut self.view_dropdown, nx, ny, nw, nh, &mut self.ui_context);
- pc.rects.extend(network_pc.rects);
- pc.texts.extend(network_pc.texts);
- pc.buttons.extend(network_pc.buttons);
- pc.reliefs.extend(network_pc.reliefs);
- pc.images.extend(network_pc.images);
+ pc.absorb(network_pc);
}
Page::Space => {
let (sx, sy, sw, sh) = self.space_split.left_rect();
let space_pc = pages::space::view(&mut self.space, &self.browse, &mut self.view_dropdown, sx, sy, sw, sh, &mut self.ui_context);
- pc.rects.extend(space_pc.rects);
- pc.texts.extend(space_pc.texts);
- pc.buttons.extend(space_pc.buttons);
- pc.reliefs.extend(space_pc.reliefs);
- pc.images.extend(space_pc.images);
+ pc.absorb(space_pc);
}
}
@@ -772,6 +764,27 @@ impl FilesystemApp {
},
});
}
+ for (ax, ay, bx, by, gw, gd, hx, hy, hw, hh) in &pc_part.grooves {
+ // Clipped by the HOST rect, not the seam's own span: a groove
+ // whose host is scrolled out has nothing left to engrave.
+ let (mut wy, mut wh) = (*hy, *hh);
+ 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: *hx,
+ y: wy,
+ w: *hw,
+ h: wh,
+ color: [0.0; 4],
+ radius: 0.0,
+ corners: (true, true, true, true),
+ fx: WidgetFx::Groove { ax: *ax, ay: *ay, bx: *bx, by: *by, width: *gw, depth: *gd },
+ });
+ }
for (btn, action) in &pc_part.buttons {
let base = btn.base();
let bg = btn.bg.unwrap_or([0.16, 0.16, 0.24, 1.0]);
@@ -1349,6 +1362,9 @@ impl Application for FilesystemApp {
WidgetFx::Recess(depth) => pc.recess(rect, radii, depth),
WidgetFx::Inset(depth) => pc.inset_plate(rect, radii, w.color, depth),
WidgetFx::Image { id, alpha } => pc.image(id, rect, alpha),
+ WidgetFx::Groove { ax, ay, bx, by, width, depth } => {
+ pc.groove((ax, ay), (bx, by), width, depth, rect)
+ }
WidgetFx::Flat => {
if w.radius > 0.1 {
pc.rounded_rect(rect, w.radius, w.corners, w.color);
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index 7c1c92c..f272863 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -198,17 +198,13 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
let breadcrumb_w = client_w - dropdown_w - gap;
let (bx, by, bw, bh) = layout.allocate(breadcrumb_w, breadcrumb_h);
cce_ui::layout::render_widget(&mut pc, &mut state.breadcrumb, bx, by, bw, bh, ctx);
- // The breadcrumb's well + per-segment button plates live in its modern
- // paint(); the flat view this host renders through loses them, so carve
- // here: the full-width recessed well, then each segment's raised button
- // within it (the dropdown-mirror pairing).
+ // The breadcrumb's well + segment plate live in its modern paint(); the flat
+ // view this host renders through loses them, so carve here: the full-width
+ // recessed well, then the run's raised plate within it (the dropdown-mirror
+ // pairing), divided by the slanted seams.
{
- let r = cce_ui::layout::breadcrumb_corner_radius();
- pc.relief_recessed(bx, by, bw, bh, r);
let rect = cce_ui::scene::layout::Rect { x: bx, y: by, width: bw, height: bh };
- for (sx, sy, sw, sh) in state.breadcrumb.segment_boxes(rect) {
- pc.relief_raised(sx, sy, sw, sh, r.min(sh * 0.5));
- }
+ crate::pages::breadcrumb_relief(&mut pc, &state.breadcrumb, rect);
}
cce_ui::layout::render_widget(&mut pc, view_dropdown, bx + bw + gap, by, dropdown_w, breadcrumb_h, ctx);
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index daad095..9ceec00 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -36,6 +36,28 @@ impl Page {
}
}
+/// Mirror the breadcrumb's relief into a flat-path [`PageContent`]: the
+/// full-width recessed well, the ONE raised plate the segment run shares, and a
+/// slanted seam engraved at each boundary between two segments.
+///
+/// `render_widget` drops the relief prims `Breadcrumb::paint` emits, so every
+/// page that shows a breadcrumb has to carve it app-side — this is that carve,
+/// in one place, since all three pages want it identically.
+pub fn breadcrumb_relief(
+ pc: &mut PageContent,
+ breadcrumb: &cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>,
+ rect: cce_ui::scene::layout::Rect,
+) {
+ let r = cce_ui::layout::breadcrumb_corner_radius();
+ pc.relief_recessed(rect.x, rect.y, rect.width, rect.height, r);
+ let Some(run) = breadcrumb.run_box(rect) else { return };
+ let (rx, ry, rw, rh) = run;
+ pc.relief_raised(rx, ry, rw, rh, r.min(rh * 0.5));
+ for (a, b) in breadcrumb.seams(rect) {
+ pc.groove(a, b, cce_ui::widget::Breadcrumb::SEAM_WIDTH, run);
+ }
+}
+
pub const RELIEF_RECESSED: u8 = 0;
pub const RELIEF_RAISED: u8 = 1;
pub const RELIEF_INSET: u8 = 2;
@@ -49,6 +71,13 @@ pub struct PageContent {
/// own the faces; these are the edges-only walls emitted over them (the
/// ParametersBg::reliefs idiom for flat-view hosts).
pub reliefs: Vec<(f32, f32, f32, f32, f32, f32, u8)>,
+ /// Engraved lines over the flat rects — (ax, ay, bx, by, width, depth) plus
+ /// the (x, y, w, h) of the surface being engraved, which the shading fades
+ /// out against. Unlike [`reliefs`] these are not axis-aligned: this is the
+ /// breadcrumb's slanted segment seams.
+ ///
+ /// [`reliefs`]: PageContent::reliefs
+ pub grooves: Vec<(f32, f32, f32, f32, f32, f32, f32, f32, f32, f32)>,
/// GPU-textured quads — (image id from `cce_ui::vk::upload_rgba`, x, y, w, h,
/// alpha). Drawn after the part's rects, so a fill emitted earlier is the floor
/// beneath the image and overlay parts still cover it.
@@ -62,10 +91,28 @@ impl PageContent {
texts: Vec::new(),
buttons: Vec::new(),
reliefs: Vec::new(),
+ grooves: Vec::new(),
images: Vec::new(),
}
}
+ /// Move every part of `other` into this content.
+ ///
+ /// Field-complete by construction: this replaces four hand-listed runs of
+ /// `self.x.extend(other.x)` in `rebuild_layout`, which silently dropped any
+ /// vec nobody remembered to add a line for — `grooves` was invisible for
+ /// exactly that reason. The destructuring below turns a new field into a
+ /// compile error instead of a missing mark on screen.
+ pub fn absorb(&mut self, other: PageContent) {
+ let PageContent { rects, texts, buttons, reliefs, grooves, images } = other;
+ self.rects.extend(rects);
+ self.texts.extend(texts);
+ self.buttons.extend(buttons);
+ self.reliefs.extend(reliefs);
+ self.grooves.extend(grooves);
+ self.images.extend(images);
+ }
+
/// A GPU-textured quad (id from `cce_ui::vk::upload_rgba`).
pub fn image(&mut self, id: u32, x: f32, y: f32, w: f32, h: f32, alpha: f32) {
self.images.push((id, x, y, w, h, alpha));
@@ -93,6 +140,15 @@ impl PageContent {
}
}
+ /// A line engraved from `a` to `b` into the surface `host` — no-op when the
+ /// DE's control_relief styling is off.
+ pub fn groove(&mut self, a: (f32, f32), b: (f32, f32), width: f32, host: (f32, f32, f32, f32)) {
+ if cce_ui::layout::control_relief() {
+ let depth = cce_ui::layout::bevel_width().min(host.3 * 0.2);
+ self.grooves.push((a.0, a.1, b.0, b.1, width, depth, host.0, host.1, host.2, host.3));
+ }
+ }
+
/// A flush inset button plate (groove ring + beveled lip, face level with the
/// surface — the Button treatment) over the control at (x, y, w, h) — no-op
/// when the DE's control_relief styling is off.
diff --git a/src/pages/network.rs b/src/pages/network.rs
index 926a13d..c389802 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -115,17 +115,9 @@ pub fn view(state: &mut NetworkState, browse: &BrowseState, view_dropdown: &mut
let dropdown_w = 120.0;
let breadcrumb_w = cw - 16.0 - dropdown_w - 12.0;
cce_ui::layout::render_widget(&mut pc, &mut state.breadcrumb, cx + 4.0, cy + 6.0, breadcrumb_w, 24.0, ctx);
- // The breadcrumb's well + per-segment button plates live in its modern
- // paint(); the flat view this host renders through loses them, so carve
- // here: the full-width recessed well, then each segment's raised button
- // within it (the dropdown-mirror pairing).
{
- let r = cce_ui::layout::breadcrumb_corner_radius();
- pc.relief_recessed(cx + 4.0, cy + 6.0, breadcrumb_w, 24.0, r);
let rect = cce_ui::scene::layout::Rect { x: cx + 4.0, y: cy + 6.0, width: breadcrumb_w, height: 24.0 };
- for (sx, sy, sw, sh) in state.breadcrumb.segment_boxes(rect) {
- pc.relief_raised(sx, sy, sw, sh, r.min(sh * 0.5));
- }
+ crate::pages::breadcrumb_relief(&mut pc, &state.breadcrumb, rect);
}
cce_ui::layout::render_widget(&mut pc, view_dropdown, cx + 4.0 + breadcrumb_w + 12.0, cy + 6.0, dropdown_w, 24.0, ctx);
diff --git a/src/pages/space.rs b/src/pages/space.rs
index 084e3df..e55a690 100644
--- a/src/pages/space.rs
+++ b/src/pages/space.rs
@@ -470,12 +470,8 @@ pub fn view(
let breadcrumb_w = cw - 16.0 - dropdown_w - 12.0;
cce_ui::layout::render_widget(&mut pc, &mut state.breadcrumb, cx + 4.0, cy + 6.0, breadcrumb_w, 24.0, ctx);
{
- let r = cce_ui::layout::breadcrumb_corner_radius();
- pc.relief_recessed(cx + 4.0, cy + 6.0, breadcrumb_w, 24.0, r);
let rect = cce_ui::scene::layout::Rect { x: cx + 4.0, y: cy + 6.0, width: breadcrumb_w, height: 24.0 };
- for (sx, sy, sw, sh) in state.breadcrumb.segment_boxes(rect) {
- pc.relief_raised(sx, sy, sw, sh, r.min(sh * 0.5));
- }
+ crate::pages::breadcrumb_relief(&mut pc, &state.breadcrumb, rect);
}
cce_ui::layout::render_widget(&mut pc, view_dropdown, cx + 4.0 + breadcrumb_w + 12.0, cy + 6.0, dropdown_w, 24.0, ctx);