file manager
git clone https://git.lucas.co/cce-files.git
Spacing: the ladder, not literals
The window-edge inset is root_plate_inset() (the roll plus the padding),
not root_plate_padding(): the padding alone left most of its run on the
roll, so the flat face at the edge read narrower than the gap between
the panes. Pane-level literals move onto the pane rung; per-control
offsets are marked.
main.rs
- layout(): pad = root_plate_padding() -> root_plate_inset() (the four
window edges; 2*pad follows)
- select bar: bar_y inset and the carved band's height ->
root_plate_inset(); the Cancel/Select gap 10 -> root_plate_gap()
- open_with_rects(): the dialog's 20px rim inset -> plate_padding()
(well x/w, buttons' right edge and bottom, title/prompt x, title y);
the 10px button gap -> plate_gap(); the 42/60 text drops stay,
TODO(style)
- context menu: label side-padding 24 and lead-in 8 stay, TODO(style)
- select-bar button: left-justified label lead-in 8 stays, TODO(style)
- collapsed preview stub: "Preview" label inset 10 -> plate_padding()
preview_pane.rs
- "Select a file" placeholder 12/12 -> plate_padding()
- content box: rect_y 12 -> plate_padding(), rect_h -24 -> 2x; text x
and first-line drop 12 -> plate_padding(); line budget cw-24 -> 2x;
"No preview" 12/12 -> plate_padding()
- details: content start +12, icon/label x 12 -> plate_padding(); the
8px drop before Target -> plate_gap(); header drop 6, columns 42/112,
row pitch 20 stay, TODO(style)
- SectionContext 7/12 frame mirrors stay: deliberate
pages/browse.rs
- search strip: margin 6 and x inset 8 / width 16 -> plate_padding();
count label 24/18 stays, TODO(style)
pages/space.rs, pages/network.rs
- breadcrumb|dropdown gap 12 -> root_plate_gap(); the 4/6/16 leftover
inset stays, TODO(style)
- space.rs: the map's message text inset 12/12 -> plate_padding()
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/main.rs | 45 ++++++++++++++++++++++++++++++++-------------
src/pages/browse.rs | 13 +++++++++----
src/pages/network.rs | 7 +++++--
src/pages/space.rs | 13 ++++++++-----
src/preview_pane.rs | 34 ++++++++++++++++++++++------------
5 files changed, 76 insertions(+), 36 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 9957c70..0443736 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -30,6 +30,8 @@ fn context_menu_size(options: &[(String, Option<Message>)]) -> (f32, f32) {
.iter()
.map(|(s, _)| cce_ui::widget::display::measure_text_width(s, &family, size))
.fold(0.0f32, f32::max);
+ // TODO(style): 24 is the menu's own label side-padding (12 a side), a
+ // control-level number the toolkit's context_menu should supply.
let w = (widest + 24.0).max(MENU_MIN_W);
let h = options.len() as f32 * ROW_H;
(w, h)
@@ -51,14 +53,24 @@ fn open_with_rects(win_w: f32, win_h: f32) -> OpenWithRects {
let y = (win_h - DIALOG_H) / 2.0;
let tb_h = cce_ui::layout::textbox_height();
let btn_h = cce_ui::layout::button_height();
+ // The dialog is a pane plate standing on the root: its content insets
+ // from the rim by the pane rung, and its two buttons sit one pane gap
+ // apart, right-aligned to that inset.
+ let pad = cce_ui::layout::plate_padding();
+ let gap = cce_ui::layout::plate_gap();
+ let (cancel_w, open_w) = (70.0, 80.0);
+ let btn_y = y + DIALOG_H - btn_h - pad;
+ let open_x = x + DIALOG_W - pad - open_w;
OpenWithRects {
x,
y,
w: DIALOG_W,
h: DIALOG_H,
- tb: (x + 20.0, y + 60.0, DIALOG_W - 40.0, tb_h),
- cancel: (x + DIALOG_W - 180.0, y + DIALOG_H - btn_h - 16.0, 70.0, btn_h),
- open: (x + DIALOG_W - 100.0, y + DIALOG_H - btn_h - 16.0, 80.0, btn_h),
+ // TODO(style): the 60px drop to the well (title, then the prompt at
+ // 42) is the dialog's own text rhythm, not a rung.
+ tb: (x + pad, y + 60.0, DIALOG_W - 2.0 * pad, tb_h),
+ cancel: (open_x - gap - cancel_w, btn_y, cancel_w, btn_h),
+ open: (open_x, btn_y, open_w, btn_h),
}
}
@@ -603,8 +615,10 @@ impl FilesystemApp {
let has_sidebar = false;
let sidebar_w = if has_sidebar { self.paginator.sidebar_w() } else { 0.0 };
- // DE-wide plate rim padding (style.surface.root plate.padding).
- let pad = cce_ui::layout::root_plate_padding();
+ // Inset from the WINDOW edge: the root plate's roll plus its padding
+ // (the padding alone left most of the run on the roll, so the edge
+ // read narrower than the gap between the panes).
+ let pad = cce_ui::layout::root_plate_inset();
let browse_x = if has_sidebar { sidebar_w + pad + 1.0 } else { pad };
let usable_w = self.width as f32 - sidebar_w - (if has_sidebar { 1.0 } else { 0.0 }) - 2.0 * pad;
let content_y = pad;
@@ -758,7 +772,7 @@ impl FilesystemApp {
// content region, so it goes into window_pc: page content (pc) is clipped
// to the viewport and would swallow the bar entirely.
if self.select_mode {
- let bar_y = self.height as f32 - select_bar_h - cce_ui::layout::root_plate_padding();
+ let bar_y = self.height as f32 - select_bar_h - cce_ui::layout::root_plate_inset();
// 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() {
@@ -772,7 +786,7 @@ impl FilesystemApp {
// the content region with the bar's own padding — and the
// toolkit's plain button face, not hand-picked red/green tints.
let btn_w = 84.0;
- let gap = 10.0;
+ let gap = cce_ui::layout::root_plate_gap();
// Flush with the panels' right edge — an extra inset here left the
// buttons hanging short of the column above them.
let confirm_x = browse_x + usable_w - btn_w;
@@ -850,6 +864,7 @@ impl FilesystemApp {
} else {
cce_ui::color::TEXT_FG
};
+ // TODO(style): the row label's 8px lead-in is a control-level inset.
context_menu_pc.text_with_font(opt, cx + 8.0, iy, menu_size, text_color, &menu_family);
}
}
@@ -870,10 +885,12 @@ impl FilesystemApp {
// Dialog panel background
dialog_pc.rect([0.08, 0.08, 0.12, 1.0], dialog_x + 1.0, dialog_y + 1.0, dialog_w - 2.0, dialog_h - 2.0);
- // Title text
- dialog_pc.text("Open with...", dialog_x + 20.0, dialog_y + 20.0, 14.0, [1.0, 1.0, 1.0, 1.0]);
- // Description
- dialog_pc.text("Enter command:", dialog_x + 20.0, dialog_y + 42.0, 11.0, [0.54, 0.54, 0.58, 1.0]);
+ // Title and prompt, inset from the plate rim by the pane rung
+ // (the same inset the well and buttons take in open_with_rects).
+ let dialog_pad = cce_ui::layout::plate_padding();
+ dialog_pc.text("Open with...", dialog_x + dialog_pad, dialog_y + dialog_pad, 14.0, [1.0, 1.0, 1.0, 1.0]);
+ // TODO(style): the prompt's 42px drop is the dialog's text rhythm.
+ dialog_pc.text("Enter command:", dialog_x + dialog_pad, dialog_y + 42.0, 11.0, [0.54, 0.54, 0.58, 1.0]);
// Set textbox position dynamically using configured textbox height
textbox.set_rect(tb_x, tb_y, tb_w, tb_h);
@@ -1068,6 +1085,8 @@ impl FilesystemApp {
// toolkit's convention for every other text-bearing control.
let text_w = cce_ui::widget::display::measure_text_width(label, &label_family, label_size);
let text_x = if btn.justify == cce_ui::widget::Justification::Left {
+ // TODO(style): a left-justified button label's lead-in is a
+ // control-level inset (Button's own, not a rung).
base.x + 8.0
} else {
base.x + (base.w - text_w) / 2.0
@@ -1650,7 +1669,7 @@ impl Application for FilesystemApp {
if cce_ui::layout::control_relief() {
let wall = cce_ui::layout::bar_wall_width();
if self.select_mode {
- let band_h = SELECT_BAR_H + cce_ui::layout::root_plate_padding();
+ let band_h = SELECT_BAR_H + cce_ui::layout::root_plate_inset();
pc.recess_edges(
Rect { x: 0.0, y: fh - band_h, width: fw, height: band_h },
(0.0, 0.0, 0.0, 0.0),
@@ -1725,7 +1744,7 @@ impl Application for FilesystemApp {
);
pc.text_with(
"Preview".to_string(),
- band.0 + 10.0,
+ band.0 + cce_ui::layout::plate_padding(),
band.1 + (band.3 - 13.0) / 2.0,
13.0,
[0xc8, 0xc8, 0xd4],
diff --git a/src/pages/browse.rs b/src/pages/browse.rs
index c5c1bce..8741ec5 100644
--- a/src/pages/browse.rs
+++ b/src/pages/browse.rs
@@ -185,7 +185,7 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
let text_dim = cce_ui::color::TEXT_DIM;
- // The pane rect already sits root_plate_padding off the plate rim — no
+ // 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();
@@ -316,7 +316,10 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
// When the search strip is open it reserves the bottom of the frame, exactly as
// the legacy List::set_rect carved its scroll frame.
let search_h = 26.0;
- let search_margin_y = 6.0;
+ // The strip stands off the well's rim (and the rows above it) by the
+ // pane rung, on both axes.
+ let search_pad = cce_ui::layout::plate_padding();
+ let search_margin_y = search_pad;
let search_offset = if state.search_visible { search_h + 2.0 * search_margin_y } else { 0.0 };
state.list.set_rect(list_x, list_y, list_w, list_h, search_offset);
state.list.update_bounds_from_rows();
@@ -334,9 +337,9 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
state.list.push_prims(&mut pc);
if state.search_visible {
let (sx, sy, sw, sh) = (
- list_x + 8.0,
+ list_x + search_pad,
list_y + list_h - search_offset + search_margin_y,
- list_w - 16.0,
+ list_w - 2.0 * search_pad,
search_h,
);
cce_ui::layout::render_widget(&mut pc, &mut state.search_box, sx, sy, sw, sh, ctx);
@@ -351,6 +354,8 @@ pub fn view(state: &mut BrowseState, view_dropdown: &mut cce_ui::widget::Adapted
state.entries.len(),
if state.show_hidden { " (.)" } else { "" }
);
+ // TODO(style): the 24 keeps the estimated-width label clear of the
+ // list's scrollbar as well as the rim; the 18 is a text baseline drop.
let count_text_w = count_str.len() as f32 * 6.0;
let count_x = list_x + list_w - count_text_w - 24.0;
let count_y = list_y + list_h - 18.0;
diff --git a/src/pages/network.rs b/src/pages/network.rs
index c389802..b288f83 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -113,13 +113,16 @@ pub fn view(state: &mut NetworkState, browse: &BrowseState, view_dropdown: &mut
// Render the Breadcrumb and Dropdown next to it
let dropdown_w = 120.0;
- let breadcrumb_w = cw - 16.0 - dropdown_w - 12.0;
+ // TODO(style): the 4/6/16 offsets are a leftover inset from the pane rect
+ // that browse.rs has already dropped; the gap to the dropdown is the rung.
+ let gap = cce_ui::layout::root_plate_gap();
+ let breadcrumb_w = cw - 16.0 - dropdown_w - gap;
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);
}
- cce_ui::layout::render_widget(&mut pc, view_dropdown, cx + 4.0 + breadcrumb_w + 12.0, cy + 6.0, dropdown_w, 24.0, ctx);
+ cce_ui::layout::render_widget(&mut pc, view_dropdown, cx + 4.0 + breadcrumb_w + gap, cy + 6.0, dropdown_w, 24.0, ctx);
// Check if directory changed, or if last_dir is empty, and repopulate
if state.last_dir != browse.current_dir || state.graph.get_nodes().is_empty() {
diff --git a/src/pages/space.rs b/src/pages/space.rs
index fe7e738..8978237 100644
--- a/src/pages/space.rs
+++ b/src/pages/space.rs
@@ -471,13 +471,16 @@ pub fn view(
// Breadcrumb + view dropdown, mirroring the Network page's header so the
// two views line up when you switch between them.
let dropdown_w = 120.0;
- let breadcrumb_w = cw - 16.0 - dropdown_w - 12.0;
+ // TODO(style): the 4/6/16 offsets are a leftover inset from the pane rect
+ // that browse.rs has already dropped; the gap to the dropdown is the rung.
+ let gap = cce_ui::layout::root_plate_gap();
+ let breadcrumb_w = cw - 16.0 - dropdown_w - gap;
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);
}
- cce_ui::layout::render_widget(&mut pc, view_dropdown, cx + 4.0 + breadcrumb_w + 12.0, cy + 6.0, dropdown_w, 24.0, ctx);
+ cce_ui::layout::render_widget(&mut pc, view_dropdown, cx + 4.0 + breadcrumb_w + gap, cy + 6.0, dropdown_w, 24.0, ctx);
let mut segments = Vec::new();
for component in browse.current_dir.components() {
@@ -519,7 +522,7 @@ pub fn view(
let text_fg = cce_ui::color::TEXT_FG;
if let Some(err) = &state.error {
- pc.text(err, map.0 + 12.0, map.1 + 12.0, 11.0, text_dim);
+ pc.text(err, map.0 + cce_ui::layout::plate_padding(), map.1 + cce_ui::layout::plate_padding(), 11.0, text_dim);
return pc;
}
@@ -530,7 +533,7 @@ pub fn view(
state.scan_files,
format_size(state.scan_bytes)
);
- pc.text(&msg, map.0 + 12.0, map.1 + 12.0, 11.0, text_dim);
+ pc.text(&msg, map.0 + cce_ui::layout::plate_padding(), map.1 + cce_ui::layout::plate_padding(), 11.0, text_dim);
return pc;
}
@@ -538,7 +541,7 @@ pub fn view(
state.relayout((map.0 + 1.0, map.1 + 1.0, (map.2 - 2.0).max(0.0), (map.3 - 2.0).max(0.0)));
if state.tiles.is_empty() {
- pc.text("Nothing to show — the directory is empty.", map.0 + 12.0, map.1 + 12.0, 11.0, text_dim);
+ pc.text("Nothing to show — the directory is empty.", map.0 + cce_ui::layout::plate_padding(), map.1 + cce_ui::layout::plate_padding(), 11.0, text_dim);
return pc;
}
diff --git a/src/preview_pane.rs b/src/preview_pane.rs
index 678eb24..ff52b4f 100644
--- a/src/preview_pane.rs
+++ b/src/preview_pane.rs
@@ -285,12 +285,15 @@ impl PreviewPane {
let label_fg = cce_ui::color::TEXT_ACCENT;
if self.path.is_none() {
- pc.text("Select a file to view details", cx + 12.0, cy + 12.0, 13.0, text_dim);
+ let pad = cce_ui::layout::plate_padding();
+ pc.text("Select a file to view details", cx + pad, cy + pad, 13.0, text_dim);
return;
}
let half_h = ch * 0.5;
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
@@ -307,6 +310,9 @@ impl PreviewPane {
// 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 {
@@ -320,8 +326,8 @@ impl PreviewPane {
pc.relief_recessed(cx, cy, cw, half_h, radius);
}
}
- let rect_y = cy + 12.0;
- let rect_h = half_h - pad - 24.0;
+ let rect_y = cy + inset;
+ let rect_h = half_h - pad - 2.0 * inset;
// Content fill spans the FULL well RECT, rounded to the well's radius —
// `RowList::push_prims` verbatim, so the two read as the same material
@@ -352,7 +358,7 @@ impl PreviewPane {
let clip_top = rect_y;
let clip_bottom = rect_y + rect_h - 8.0;
let clip = [cx, clip_top, cx + cw, clip_bottom];
- let mut text_y = rect_y + 12.0 - frac;
+ let mut text_y = rect_y + inset - frac;
for line in content.lines().skip(first_line as usize) {
if text_y >= clip_bottom {
break;
@@ -360,13 +366,13 @@ impl PreviewPane {
// Chars-per-width from one measured glyph (cached) instead of
// the old magic 6.8 px/char guess.
let char_w = measure_text_width("M", "monospace", 11.0).max(1.0);
- let limit = (((cw - 24.0) / char_w).floor() as usize).max(20);
+ let limit = (((cw - 2.0 * inset) / char_w).floor() as usize).max(20);
let line_truncated = truncate_tail(line, limit);
- pc.text_with_font_bounded(&line_truncated, cx + 12.0, text_y, 11.0, text_fg, "monospace", clip);
+ pc.text_with_font_bounded(&line_truncated, cx + inset, text_y, 11.0, text_fg, "monospace", clip);
text_y += PREVIEW_LINE_H;
}
} else {
- pc.text("No preview available", cx + 12.0, rect_y + 12.0, 11.0, text_dim);
+ pc.text("No preview available", cx + inset, rect_y + inset, 11.0, text_dim);
}
// 2. Bottom pane: Details Section. The visible gap between the wells
@@ -383,13 +389,14 @@ impl PreviewPane {
("Modified", &self.modified),
];
- let details_content_start_y = details_top + pad + 12.0;
+ let details_content_start_y = details_top + pad + inset;
// 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 {
@@ -404,8 +411,10 @@ impl PreviewPane {
}
}
+ // TODO(style): the header's 6px drop, the 42/112 label columns and
+ // the 20px row pitch are the details form's own rhythm.
let header_y = details_content_start_y + 6.0;
- pc.text(icon, cx + 12.0, header_y, 20.0, text_fg);
+ pc.text(icon, cx + inset, header_y, 20.0, text_fg);
// Pixel-measured budgets against the section frame's inner right edge
// (None-font text renders sans-serif — measure with the same family).
@@ -418,15 +427,16 @@ impl PreviewPane {
let mut y = details_content_start_y + 36.0;
for (label, val) in &details {
- pc.text(label, cx + 12.0, y, 12.0, label_fg);
+ pc.text(label, cx + inset, y, 12.0, label_fg);
let val_str = truncate_px(val, "sans-serif", 12.0, val_avail, true);
pc.text(&val_str, cx + 112.0, y, 12.0, text_dim);
y += 20.0;
}
if !self.target.is_empty() {
- y += 8.0;
- pc.text("Target", cx + 12.0, y, 12.0, label_fg);
+ // One pane gap sets the link target off from the rows above.
+ y += cce_ui::layout::plate_gap();
+ pc.text("Target", cx + inset, y, 12.0, label_fg);
let target_str = truncate_px(&self.target, "sans-serif", 12.0, val_avail, true);
pc.text(&target_str, cx + 112.0, y, 12.0, text_dim);
}