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

commitcf72ce390bddf65801fc5903f148e73793710572
parentac54035376
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-06 10:21
fix(layout): the breadcrumb sits in the same place on every page

Browse drew it at the pane's top-left, full width; Network and Space
4px in, 6px down and 16px narrower, an inset their own TODOs called a
leftover, and started their content 28px and 34px down against
Browse's 24 + gap. pages::breadcrumb_header now lays out, renders and
carves it for all three and returns the top of the content under it,
so the graph pane and the treemap start where the list does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/pages/browse.rs  | 17 ++++-------------
 src/pages/mod.rs     | 24 ++++++++++++++++++++++++
 src/pages/network.rs | 24 ++++++++----------------
 src/pages/space.rs   | 13 ++-----------
 4 files changed, 38 insertions(+), 40 deletions(-)

diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index 6b8fa8e..8f11e79 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -229,21 +229,12 @@ pub fn view(state: &mut BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, select_
     let client_h = ch - 2.0 * margin;
     layout.init(client_x, client_y, client_w, client_h);
 
-    let breadcrumb_h = 24.0;
+    let breadcrumb_h = crate::pages::BREADCRUMB_H;
     let textbox_h = cce_ui::layout::textbox_height();
 
-    // 1. Allocate and render the Breadcrumb, full width: the view switch that
-    // used to sit beside it is in its context menu now.
-    let (bx, by, bw, bh) = layout.allocate(client_w, breadcrumb_h);
-    cce_ui::layout::render_widget(&mut pc, &mut state.breadcrumb, bx, by, bw, bh, ctx);
-    // 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 rect = cce_ui::scene::layout::Rect { x: bx, y: by, width: bw, height: bh };
-        crate::pages::breadcrumb_relief(&mut pc, &state.breadcrumb, rect);
-    }
+    // 1. Allocate and render the Breadcrumb, where every page puts it.
+    let (bx, by, bw, _) = layout.allocate(client_w, breadcrumb_h);
+    crate::pages::breadcrumb_header(&mut pc, &mut state.breadcrumb, bx, by, bw, ctx);
 
     // 2. Allocate and render List (ScrollBox)
     // The scrolling list height occupies the remaining vertical space:
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 4500b33..c787961 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -38,6 +38,30 @@ impl Page {
     }
 }
 
+/// The breadcrumb's height on every page.
+pub const BREADCRUMB_H: f32 = 24.0;
+
+/// Lay out, render and carve a page's breadcrumb: the pane's full width along
+/// its top edge, the same on every page, so switching views leaves it where it
+/// was. Returns the top of the content under it, one plate gap below.
+///
+/// Network and Space used to place theirs 4px in, 6px down and 16px narrower
+/// than Browse, an inset left over from before the pane rect carried it, and
+/// started their content 28px and 34px down against Browse's 24 + gap.
+pub fn breadcrumb_header(
+    pc: &mut PageContent,
+    breadcrumb: &mut cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>,
+    cx: f32,
+    cy: f32,
+    cw: f32,
+    ctx: &mut cce_ui::context::UiContext,
+) -> f32 {
+    cce_ui::layout::render_widget(pc, breadcrumb, cx, cy, cw, BREADCRUMB_H, ctx);
+    let rect = cce_ui::scene::layout::Rect { x: cx, y: cy, width: cw, height: BREADCRUMB_H };
+    breadcrumb_relief(pc, breadcrumb, rect);
+    cy + BREADCRUMB_H + cce_ui::layout::root_plate_gap()
+}
+
 /// 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.
diff --git a/src/pages/network.rs b/src/pages/network.rs
index ffc0033..c9af9bc 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -204,18 +204,10 @@ impl NetworkState {
 pub fn view(state: &mut NetworkState, browse: &BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, ctx: &mut cce_ui::context::UiContext) -> PageContent {
     let mut pc = PageContent::new();
 
-    // Render the Breadcrumb, full width (the view switch is in its context menu).
-    // TODO(style): the 4/6/16 offsets are a leftover inset from the pane rect
-    // that browse.rs has already dropped.
-    let breadcrumb_w = cw - 16.0;
-    cce_ui::layout::render_widget(&mut pc, &mut state.breadcrumb, cx + 4.0, cy + 6.0, breadcrumb_w, 24.0, ctx);
-    {
-        let rect = cce_ui::scene::layout::Rect { x: cx + 4.0, y: cy + 6.0, width: breadcrumb_w, height: 24.0 };
-        crate::pages::breadcrumb_relief(&mut pc, &state.breadcrumb, rect);
-    }
+    let top = crate::pages::breadcrumb_header(&mut pc, &mut state.breadcrumb, cx, cy, cw, ctx);
 
     // The pane the graph is laid out in, under the breadcrumb.
-    let pane = (cx, cy + 28.0, cw, (ch - 28.0).max(0.0));
+    let pane = (cx, top, cw, (cy + ch - top).max(0.0));
     let moved = (pane.0 - state.laid_out_for.0).abs() > 0.5
         || (pane.1 - state.laid_out_for.1).abs() > 0.5
         || (pane.2 - state.laid_out_for.2).abs() > 0.5
@@ -270,17 +262,17 @@ pub fn view(state: &mut NetworkState, browse: &BrowseState, cx: f32, cy: f32, cw
         }
     }
 
-    // Render the Graph widget into PageContent, shifted down by 28.0 to leave
-    // room for the breadcrumb — cut to its pane, since a graph panned or
-    // zoomed past its edge would otherwise draw over the preview beside it.
+    // Render the Graph widget into its pane under the breadcrumb — cut to
+    // it, since a graph panned or zoomed past its edge would otherwise draw
+    // over the preview beside it.
     let mut graph_pc = PageContent::new();
-    cce_ui::layout::render_widget(&mut graph_pc, &mut state.graph, cx, cy + 28.0, cw, ch - 28.0, ctx);
-    pc.absorb(graph_pc.clipped_to([cx, cy + 28.0, cx + cw, cy + ch]));
+    cce_ui::layout::render_widget(&mut graph_pc, &mut state.graph, pane.0, pane.1, pane.2, pane.3, ctx);
+    pc.absorb(graph_pc.clipped_to([cx, top, cx + cw, cy + ch]));
 
     // Each node's glyph on the left of its body, at the size and inset the
     // graph's geometry toggle has on the right (hidden here): scaled with
     // the zoom. Cut to the graph's rect, as its labels are.
-    let canvas = [cx, cy + 28.0, cx + cw, cy + ch];
+    let canvas = [cx, top, cx + cw, cy + ch];
     let glyph_color = [0xcc as f32 / 255.0, 0xcc as f32 / 255.0, 0xd4 as f32 / 255.0, 1.0];
     for (idx, glyph) in state.node_glyphs.iter().enumerate() {
         let Some((nx, ny, nw, nh)) = state.graph.node_rect(idx) else { continue };
diff --git a/src/pages/space.rs b/src/pages/space.rs
index 1cc8d16..a229ab4 100644
--- a/src/pages/space.rs
+++ b/src/pages/space.rs
@@ -467,16 +467,7 @@ pub fn view(
 ) -> PageContent {
     let mut pc = PageContent::new();
 
-    // The breadcrumb, mirroring the Network page's header so the two views
-    // line up when you switch between them.
-    // TODO(style): the 4/6/16 offsets are a leftover inset from the pane rect
-    // that browse.rs has already dropped.
-    let breadcrumb_w = cw - 16.0;
-    cce_ui::layout::render_widget(&mut pc, &mut state.breadcrumb, cx + 4.0, cy + 6.0, breadcrumb_w, 24.0, ctx);
-    {
-        let rect = cce_ui::scene::layout::Rect { x: cx + 4.0, y: cy + 6.0, width: breadcrumb_w, height: 24.0 };
-        crate::pages::breadcrumb_relief(&mut pc, &state.breadcrumb, rect);
-    }
+    let top = crate::pages::breadcrumb_header(&mut pc, &mut state.breadcrumb, cx, cy, cw, ctx);
 
     let mut segments = Vec::new();
     for component in browse.current_dir.components() {
@@ -488,7 +479,7 @@ pub fn view(
     state.breadcrumb.set_path(&segments);
 
     // The map occupies everything below the header, less the footer readout.
-    let map = (cx, cy + 34.0, cw, (ch - 34.0 - FOOTER_H).max(0.0));
+    let map = (cx, top, cw, (cy + ch - top - FOOTER_H).max(0.0));
     state.map_rect = map;
     // Fill and well share one rect AND one radius — `RowList::push_prims`'s
     // pairing, since this pane is the Browse list's opposite number across the