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

commit7fd65e5f0463df582441d5b97925c80daabd9ec5
parent9caa807312
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-08 13:54
refactor(browse): the page is a scene::layout column; the preview's frames are outlines

The browse page placed its breadcrumb, list and save-name box with the legacy
ColumnLayout cursor; it is a scene::layout column now (the list grows into what
is left). The preview pane's two SectionContexts only ever drew a 1px outline
round each well with control relief off; well_outline draws it. Pixel-identical
before and after, relief on and off, and in save mode.

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

 src/pages/browse.rs | 51 +++++++++++++++++++++++++++---------------------
 src/preview_pane.rs | 56 +++++++++++++++++++++++------------------------------
 2 files changed, 53 insertions(+), 54 deletions(-)

diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index 083abae..d21258b 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -3,7 +3,8 @@ use std::path::{Path, PathBuf};
 use crate::pages::PageContent;
 use cce_ui::widget::Owned;
 use cce_ui::widget::{Adapted, WidgetHost, Breadcrumb, PathController};
-use cce_ui::layout::{ColumnLayout, LayoutStrategy};
+use cce_ui::scene::arena::Arena;
+use cce_ui::scene::layout::{compute_layout, CrossAlign, LayoutBox, Length, Size, Style};
 
 // ── Data ────────────────────────────────────────────────────────────
 
@@ -221,30 +222,36 @@ pub fn view(state: &mut BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, select_
     // The pane rect already sits root_plate_inset off the window edge — no
     // second inset here, or the list lands 16+12 from the edge while apps
     // that place content at the pane rect (cce-data-editor's tree) sit at 16.
-    let gap = cce_ui::layout::root_plate_gap();
-    let margin = 0.0;
-    let mut layout = ColumnLayout::new(gap);
-    let client_x = cx + margin;
-    let client_y = cy + margin;
-    let client_w = cw - 2.0 * margin;
-    let client_h = ch - 2.0 * margin;
-    layout.init(client_x, client_y, client_w, client_h);
-
+    // The page is a column on the root plate: the breadcrumb, the list taking what is
+    // left, and in select mode the name box under it, a root gap between each.
     let breadcrumb_h = crate::pages::breadcrumb_h();
     let textbox_h = cce_ui::layout::textbox_height();
+    let mut arena: Arena<LayoutBox> = Arena::new();
+    let column = arena.insert(LayoutBox::container(
+        Style::column().gap(cce_ui::layout::root_plate_gap()).cross_align(CrossAlign::Stretch),
+    ));
+    let fixed = |h: f32| LayoutBox::leaf(Style { height: Length::Fixed(h), ..Style::default() }, Size::new(0.0, h));
+    let crumb = arena.insert(fixed(breadcrumb_h));
+    let list = arena.insert(LayoutBox::leaf(Style::default().grow(1.0), Size::ZERO));
+    arena.append_child(column, crumb);
+    arena.append_child(column, list);
+    let name_box = select_mode.then(|| {
+        let b = arena.insert(fixed(textbox_h));
+        arena.append_child(column, b);
+        b
+    });
+    compute_layout(&mut arena, column, Size::new(cw, ch));
+    let at = |id| {
+        let r = arena.value(id).expect("laid out").rect;
+        (cx + r.x, cy + r.y, r.width, r.height)
+    };
 
-    // 1. Allocate and render the Breadcrumb, where every page puts it.
-    let (bx, by, bw, _) = layout.allocate(client_w, breadcrumb_h);
+    // 1. The Breadcrumb, where every page puts it.
+    let (bx, by, bw, _) = at(crumb);
     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:
-    let list_h_val = if select_mode {
-        client_h - breadcrumb_h - textbox_h - 2.0 * gap
-    } else {
-        client_h - breadcrumb_h - gap
-    };
-    let (list_x, list_y, list_w, list_h) = layout.allocate(client_w, list_h_val);
+    // 2. The list (ScrollBox).
+    let (list_x, list_y, list_w, list_h) = at(list);
 
     // Update List columns dynamically based on list width
     let in_trash = crate::services::trash::is_trash_files_dir(&state.current_dir);
@@ -385,8 +392,8 @@ pub fn view(state: &mut BrowseState, cx: f32, cy: f32, cw: f32, ch: f32, select_
     pc.text(&count_str, count_x, count_y, 11.0, text_dim);
 
     // 3. Allocate and render Textbox(es)
-    if select_mode {
-        let (tx, ty, tw, th) = layout.allocate(client_w, textbox_h);
+    if let Some(name_box) = name_box {
+        let (tx, ty, tw, th) = at(name_box);
         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());
diff --git a/src/preview_pane.rs b/src/preview_pane.rs
index aa5701f..e2b43a0 100644
--- a/src/preview_pane.rs
+++ b/src/preview_pane.rs
@@ -1,5 +1,6 @@
 //! The file-preview pane — Preview (text lines / image pixels) over Details
-//! (name + metadata rows), drawn in cce-ui `SectionContext` titled frames.
+//! (name + metadata rows), each in its own well (a 1px outline
+//! with control relief off).
 //!
 //! Moved in-crate from cce-ui's `PreviewState` (its only consumer was this app)
 //! and flattened to the RowList idiom: a plain struct whose `push_prims` emits
@@ -11,7 +12,6 @@
 
 use std::path::PathBuf;
 
-use cce_ui::layout::SectionContext;
 use cce_ui::scene::layout::{fit_rect, FitMode, Rect};
 use cce_ui::widget::display::{measure_text_width, truncate_tail};
 
@@ -266,30 +266,18 @@ impl PreviewPane {
         let pad = cce_ui::layout::section_padding();
         // Inside each well the content stands off the rim by the pane rung.
         let inset = cce_ui::layout::plate_padding();
-        // Under control_relief the section frames are recessed wells carved
-        // into the plate (the list's treatment); the SectionContext 1px line
-        // frame is the flat fallback. Titles come from SectionContext::new
-        // either way. Frame geometry mirrors SectionContext::finish: the well
-        // spans top+7 down to content_y + pad + 12.
+        // Under control_relief each well is recessed into the plate (the
+        // list's treatment); with it off, the well is its 1px outline
+        // (`well_outline`).
         let relief = cce_ui::layout::control_relief();
         let radius = cce_ui::layout::list_corner_radius();
 
-        // 1. Top pane: File Preview Section. The section frame spans the FULL
-        // pane rect (left = cx - pad cancels SectionContext's inner pad), so
-        // the well edge sits at the pane edge like the list across the split —
-        // the visible split gap is exactly root_plate_gap on both sides.
-        // The well's top edge sits AT the pane top, aligned with the
-        // breadcrumb across the split (finish() draws from top+7, so the
-        // fallback frame gets top-7 to land on the same edge).
-        {
-            // style: deliberate — the 7 and 12 mirror SectionContext::finish's
-            // own frame geometry (top+7 down to content_y+pad+12), so the flat
-            // fallback frame lands on the well's edges.
-            let mut preview_sec = SectionContext::new(pc, cx - pad, cy - 7.0, cw + 2.0 * pad, "", false, false);
-            preview_sec.content_y = cy + half_h - pad - 12.0;
-            if !relief {
-                preview_sec.finish();
-            }
+        // 1. Top pane: File Preview. The well spans the FULL pane rect, so
+        // its edge sits at the pane edge like the list across the split — the
+        // visible split gap is exactly root_plate_gap on both sides — and its
+        // top edge at the pane top, aligned with the breadcrumb.
+        if !relief {
+            well_outline(pc, cx, cy, cw, half_h);
         }
         if relief {
             if self.focused_well == Some(PreviewWell::Top) {
@@ -365,15 +353,9 @@ impl PreviewPane {
 
         // The well's bottom edge sits AT the pane bottom, aligned with the
         // list across the split (content-sized before; short panes just show
-        // empty well below the rows). finish() draws from top+7, so the
-        // fallback frame gets top-7 to land on the same edge.
-        {
-            // style: deliberate — SectionContext::finish's frame geometry, as above.
-            let mut details_sec = SectionContext::new(pc, cx - pad, details_top - 7.0, cw + 2.0 * pad, "", false, false);
-            details_sec.content_y = cy + ch - pad - 12.0;
-            if !relief {
-                details_sec.finish();
-            }
+        // empty well below the rows).
+        if !relief {
+            well_outline(pc, cx, details_top, cw, (cy + ch) - details_top);
         }
         if relief {
             if self.focused_well == Some(PreviewWell::Bottom) {
@@ -415,3 +397,13 @@ impl PreviewPane {
         }
     }
 }
+
+/// A well's frame with control relief off: a 1px outline round its rect, in
+/// the section border colour.
+fn well_outline(pc: &mut PageContent, x: f32, y: f32, w: f32, h: f32) {
+    const BORDER: [f32; 4] = [0.25, 0.25, 0.35, 1.0];
+    pc.rect(BORDER, x, y, w, 1.0);
+    pc.rect(BORDER, x, y + h, w, 1.0);
+    pc.rect(BORDER, x, y, 1.0, h);
+    pc.rect(BORDER, x + w - 1.0, y, 1.0, h);
+}