system settings
git clone https://git.lucas.co/cce-system-interface.git
Spacing: the ladder, not literals
The sections are wells carved straight into the root plate (no sidebar,
no pane between), so the page's edge is the window's edge and the grid's
gap is the root rung's sibling gap; inside a well the app's rects sit on
the toolkit's SectionContext geometry, which is named once.
Window level:
- renderer.rs render_page_content: `page_margin()` -> `root_plate_inset()`;
`AdaptiveGrid::new(260.0, 20.0)` -> the `grid_min_col_width()` /
`root_plate_gap()` getters its `init` reads anyway.
- main.rs: `set_grid_gap(page_margin())` -> `set_grid_gap(root_plate_gap())`
(kept: `AdaptiveGrid::init` reads the toolkit's grid_gap, not a ctor arg).
- renderer.rs search box: `+ 12.0` / `- 24.0` -> `root_plate_inset()`.
- renderer.rs left-aligned button label `+ 8.0` -> `CONTROL_TEXT_INSET`.
Well level (app.rs `section_margin()` = SectionContext::DEFAULT_MARGIN_X,
the number `add_widget` / `ax` / `finish` lay out on; TODO(style) to
become `plate_padding()` once SectionContext reads the rung):
- app.rs section_kv_row / section_divider `ax(12.0)`, `padding() + 12.0`.
- accounts.rs: `LIST_INSET` const deleted -> `m`; `padding() + 12.0`,
`rx + 12.0` (x11) -> `m`.
- services.rs, timers.rs, packages.rs: search width `- 24.0`, `left + 12.0`,
list box `left + 12.0` / `- 24.0` / `(cy+ch) - 12.0`, `padding() + 12.0`,
`ax(12.0)` -> `m`; the well-end line `+ 12.0 - (padding() + 12.0)`
simplified to `- sec.padding()` (the two margins cancel).
- processes.rs: `let inset = 12.0` -> `section_margin()`, same well-end.
- storage.rs: bar width `- 24.0`, `ax(12.0)` -> `section_margin()`.
- system_info.rs button_need `+ 24.0` -> `2 * CONTROL_TEXT_INSET + 8.0`.
Row / group gaps -> `plate_gap()`: every `vstack(8.0)` (13 pages),
`spacing(8.0)` before a list (services, timers), `add_row(n, 8.0, ..)` and
`add_row_for(.., 8.0, ..)` button groups (accounts, timers, packages,
services, system_info).
Left as they were:
- accounts.rs `LIST_GAP` (4) marked deliberate: list rows pack tighter
than the pane gap, like every ScrollRegion on these pages.
- TODO(style): the page scrollbar's 4px vertical stand-off (pairs with
`scrollbar_inset()`), the processes table's column offsets, the services
and timers list-row control runs, power's 14px dropdown row inset, and
the sub-row `spacing(4/6/10)` air.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/app.rs | 18 ++++++++++++++---
src/main.rs | 7 ++++---
src/pages/accounts.rs | 49 ++++++++++++++++++++++++----------------------
src/pages/audio.rs | 2 +-
src/pages/browser.rs | 2 +-
src/pages/default_apps.rs | 2 +-
src/pages/notifications.rs | 2 +-
src/pages/packages.rs | 31 ++++++++++++++++-------------
src/pages/power.rs | 10 ++++++----
src/pages/processes.rs | 16 +++++++++------
src/pages/services.rs | 28 ++++++++++++++------------
src/pages/storage.rs | 10 +++++-----
src/pages/system_info.rs | 8 ++++----
src/pages/timers.rs | 33 ++++++++++++++++++-------------
src/renderer.rs | 19 +++++++++++++-----
15 files changed, 140 insertions(+), 97 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index a47344c..fad2835 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -426,13 +426,25 @@ impl RenderTarget for PageContent {
}
+/// The inset from a section well's rim to the app's own content, on top of
+/// `section_padding()`. cce-ui's `SectionContext` lays its title tab, its
+/// grid and a VStack's widgets out `2 * padding + DEFAULT_MARGIN_X` in, and
+/// `finish()` lands the well's wall `padding + DEFAULT_MARGIN_X` past the
+/// content — a rect the app places inside a well has to match that number or
+/// sit off the toolkit's own geometry. A well is this app's pane, so this is
+/// where the pane rung belongs; TODO(style): become `plate_padding()` once
+/// SectionContext reads the rung instead of its literal.
+pub fn section_margin() -> f32 {
+ cce_ui::layout::SectionContext::<PageContent>::DEFAULT_MARGIN_X
+}
+
/// A dim label / bright value pair on one line (the shared details idiom).
pub fn section_kv_row(sc: &mut cce_ui::layout::SectionContext<'_, PageContent>, label: &str, value: &str, value_color: [f32; 4]) {
let mut y = sc.content_y;
if y > sc.content_start_y {
y += sc.row_gap;
}
- let lx = sc.ax(12.0);
+ let lx = sc.ax(section_margin());
sc.pc.text(label, lx, y, 12.0, [0.53, 0.53, 0.60, 1.0]);
sc.pc.text(value, lx + 130.0, y, 12.0, value_color);
sc.content_y = y + 18.0;
@@ -444,8 +456,8 @@ pub fn section_kv_row(sc: &mut cce_ui::layout::SectionContext<'_, PageContent>,
/// A hairline separating a well's zones.
pub fn section_divider(sc: &mut cce_ui::layout::SectionContext<'_, PageContent>) {
let y = sc.content_y + sc.row_gap + 4.0;
- let x = sc.ax(12.0);
- let w = sc.cw - 2.0 * (sc.padding() + 12.0);
+ let x = sc.ax(section_margin());
+ let w = sc.cw - 2.0 * (sc.padding() + section_margin());
sc.pc.rect([1.0, 1.0, 1.0, 0.06], x, y, w, 1.0);
sc.content_y = y + 5.0;
for h in &mut sc.grid.col_heights {
diff --git a/src/main.rs b/src/main.rs
index f163f16..d84d33e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -931,9 +931,10 @@ fn main() {
let _guard = rt.enter();
// One gap everywhere: the wells fill their grid allocations, so the grid
- // gap is the visual section gap — pin it to the page margin so
- // section-to-section and section-to-plate-edge spacing match.
- cce_ui::layout::set_grid_gap(cce_ui::layout::page_margin());
+ // gap IS the visual section gap. The sections stand on the root plate,
+ // so it is the root rung's sibling gap — `AdaptiveGrid::init` reads the
+ // toolkit's grid_gap, hence the set here rather than a constructor arg.
+ cce_ui::layout::set_grid_gap(cce_ui::layout::root_plate_gap());
let mut initial_page = Page::ALL[0];
diff --git a/src/pages/accounts.rs b/src/pages/accounts.rs
index f272f51..34f2fbb 100644
--- a/src/pages/accounts.rs
+++ b/src/pages/accounts.rs
@@ -452,9 +452,10 @@ const ACCENT_BG: [f32; 4] = [0.20, 0.40, 0.65, 0.35];
const TEXT_BTN: [f32; 4] = [0.90, 0.90, 0.95, 1.0];
const TEXT_DANGER: [f32; 4] = [0.95, 0.55, 0.55, 1.0];
-/// Gap between account rows, and the inset from the region's own edges.
+/// Gap between account rows. style: deliberate — list rows pack tighter
+/// than the pane gap, like every list on this app's pages (the inset from
+/// the region's edges is the well margin, `section_margin()`).
const LIST_GAP: f32 = 4.0;
-const LIST_INSET: f32 = 12.0;
/// Rows shown before the region starts scrolling. The list sits ABOVE the
/// actions and the edit form, so it cannot fill the page the way services'
/// does; it grows with the account count up to here and scrolls past it,
@@ -469,13 +470,15 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
let widget_h = cce_ui::layout::spinbox_height();
let btn_h = 26.0;
+ let m = crate::app::section_margin();
+ let gap = cce_ui::layout::plate_gap();
builder.add_section_spanned(&mut final_pc, "", 1, sec_focused.first().copied().unwrap_or(false), |sec| {
if !state.loaded {
sec.text("Loading online accounts...", 12.0, 0.0, 12.0, TEXT_DIM);
return;
}
- let item_w = sec.cw - 2.0 * (sec.padding() + 12.0);
+ let item_w = sec.cw - 2.0 * (sec.padding() + m);
let rx = sec.left;
// ── Account list: a scroll region, selection tinted, default marked ──
@@ -490,9 +493,9 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
// drawing at a different height than it virtualizes on would drift
// the rows out from under their own hit boxes.
let item_h = state.list.item_height;
- let list_x = sec.left + LIST_INSET;
+ let list_x = sec.left + m;
let list_y = sec.ay();
- let list_w = sec.cw - 2.0 * LIST_INSET;
+ let list_w = sec.cw - 2.0 * m;
let rows_shown = state.accounts.len().min(LIST_MAX_ROWS);
let list_h = rows_shown as f32 * (item_h + LIST_GAP) + 8.0;
@@ -501,7 +504,7 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
state.list.update_bounds(state.accounts.len(), list_y, list_h);
state.list.push_prims(sec.pc);
- let btn_w = list_w - 2.0 * LIST_INSET;
+ let btn_w = list_w - 2.0 * m;
sec.pc.push_clip_rect(list_x, list_y, list_w, list_h);
for (idx, acc) in state.accounts.iter().enumerate() {
// Same predicate the region virtualizes on — a row scrolled out
@@ -526,7 +529,7 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
};
sec.pc.button_left(
&label,
- list_x + LIST_INSET,
+ list_x + m,
draw_y,
btn_w,
item_h,
@@ -547,7 +550,7 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
sec.spacing(list_h + LIST_GAP);
}
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(gap);
if state.accounts.is_empty() {
stack.context.text("No accounts configured.", 12.0, 0.0, 12.0, TEXT_DIM);
}
@@ -585,7 +588,7 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
let sel = state.selected_idx.filter(|&i| i < state.accounts.len());
let icons_ok = cce_ui::upload_icon("plus", 32).is_some();
let (sq, bgap) = if icons_ok { (btn_h, 8.0) } else if narrow { (86.0, 6.0) } else { (110.0, 8.0) };
- stack.add_row(1, 8.0, btn_h, |c, _, x, _w| {
+ stack.add_row(1, gap, btn_h, |c, _, x, _w| {
// ONE y for the whole row. `c.ay()` reads the section's running
// content_y, and each button emitted advances it past its own
// bottom — normally right, because `add_row` resets content_y
@@ -613,17 +616,17 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
stack.context.text("Add New Account", 12.0, 0.0, 14.0, [0.35, 0.65, 0.90, 1.0]);
stack.context.text("Gmail signs in with Google below; iCloud requires an App Password.", 12.0, 0.0, 11.0, TEXT_DIM);
- state.email_box.set_row_rect(rx + 12.0, item_w);
+ state.email_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.email_box, item_w, widget_h, ctx);
- state.password_box.set_row_rect(rx + 12.0, item_w);
+ state.password_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.password_box, item_w, widget_h, ctx);
- state.imap_box.set_row_rect(rx + 12.0, item_w);
+ state.imap_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.imap_box, item_w, widget_h, ctx);
- state.smtp_box.set_row_rect(rx + 12.0, item_w);
+ state.smtp_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.smtp_box, item_w, widget_h, ctx);
stack.context.spacing(4.0);
- stack.add_row(4, 8.0, btn_h, |c, i, x, w| {
+ stack.add_row(4, gap, btn_h, |c, i, x, w| {
let (label, colors, text_col, action) = match i {
0 => ("Save", BTN_PRIMARY, TEXT_BTN, AccountsMessage::AddAccountSave),
1 => ("Cancel", BTN_NEUTRAL, TEXT_BTN, AccountsMessage::AddAccountCancel),
@@ -648,29 +651,29 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
// An OAuth account has no password to edit; a password one has no
// client credentials. Neither ever shows the other's fields.
if acc.is_oauth {
- state.imap_box.set_row_rect(rx + 12.0, item_w);
+ state.imap_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.imap_box, item_w, widget_h, ctx);
- state.smtp_box.set_row_rect(rx + 12.0, item_w);
+ state.smtp_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.smtp_box, item_w, widget_h, ctx);
stack.context.spacing(4.0);
stack.context.text("Credentials this account refreshes tokens with, taking effect", 12.0, 0.0, 11.0, TEXT_DIM);
stack.context.text("on the next refresh \u{2014} Re-login to re-issue the tokens now.", 12.0, 0.0, 11.0, TEXT_DIM);
- state.oauth_client_id_box.set_row_rect(rx + 12.0, item_w);
+ state.oauth_client_id_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.oauth_client_id_box, item_w, widget_h, ctx);
- state.oauth_client_secret_box.set_row_rect(rx + 12.0, item_w);
+ state.oauth_client_secret_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.oauth_client_secret_box, item_w, widget_h, ctx);
} else {
- state.password_box.set_row_rect(rx + 12.0, item_w);
+ state.password_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.password_box, item_w, widget_h, ctx);
- state.imap_box.set_row_rect(rx + 12.0, item_w);
+ state.imap_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.imap_box, item_w, widget_h, ctx);
- state.smtp_box.set_row_rect(rx + 12.0, item_w);
+ state.smtp_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.smtp_box, item_w, widget_h, ctx);
}
stack.context.spacing(4.0);
- stack.add_row(2, 8.0, btn_h, |c, i, x, w| {
+ stack.add_row(2, gap, btn_h, |c, i, x, w| {
let (label, colors, action) = match i {
0 => ("Save", BTN_PRIMARY, AccountsMessage::EditAccountSave),
_ => ("Cancel", BTN_NEUTRAL, AccountsMessage::EditAccountCancel),
@@ -719,7 +722,7 @@ pub fn view(state: &mut AccountsState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
actions.push((relogin, login_bg, TEXT_BTN, AccountsMessage::GoogleLoginInit));
}
if !actions.is_empty() {
- stack.add_row(actions.len(), 8.0, btn_h, |c, i, x, w| {
+ stack.add_row(actions.len(), gap, btn_h, |c, i, x, w| {
if let Some((label, colors, text_col, action)) = actions.get(i).cloned() {
c.button(label, x, c.ay(), w, btn_h, colors.0, colors.1, text_col, AppAction::Accounts(action));
}
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index 52c3f21..fe47b0a 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -302,7 +302,7 @@ pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
sec.text("Loading audio devices...", 12.0, 0.0, 12.0, TEXT_DIM);
return;
}
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
stack.context.text("Output", 12.0, 0.0, 14.0, HEADING);
if state.sinks.is_empty() {
diff --git a/src/pages/browser.rs b/src/pages/browser.rs
index 233aad3..088e3b6 100644
--- a/src/pages/browser.rs
+++ b/src/pages/browser.rs
@@ -247,7 +247,7 @@ impl AppPage for BrowserState {
let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(1);
builder.add_section(&mut final_pc, "Browser Settings", sec_focused.first().copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
let sec_w = stack.context.cw;
let row_w = sec_w - 28.0;
diff --git a/src/pages/default_apps.rs b/src/pages/default_apps.rs
index f8e0053..77fb85d 100644
--- a/src/pages/default_apps.rs
+++ b/src/pages/default_apps.rs
@@ -133,7 +133,7 @@ pub fn view(state: &mut DefaultAppsState, cx: f32, cy: f32, cw: f32, ch: f32, _r
if !state.loaded {
sec.text("Scanning installed applications...", 12.0, 0.0, 12.0, TEXT_DIM);
} else {
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
for entry in state.categories.iter_mut() {
entry.dropdown.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
stack.add_widget(&mut entry.dropdown, sec_w - 28.0, 44.0, ctx);
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index 473343e..7588595 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -209,7 +209,7 @@ impl AppPage for NotificationsState {
let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(1);
builder.add_section(&mut final_pc, "Notifications Settings", sec_focused.first().copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
let sec_w = stack.context.cw;
self.enable_toggle.set_toggled(self.enable);
stack.add_widget(&mut self.enable_toggle, sec_w - 28.0, cce_ui::layout::toggle_height(), ctx);
diff --git a/src/pages/packages.rs b/src/pages/packages.rs
index 648c5a7..aa57a76 100644
--- a/src/pages/packages.rs
+++ b/src/pages/packages.rs
@@ -325,6 +325,7 @@ pub fn view(
layout: &mut dyn LayoutStrategy,
ctx: &mut cce_ui::context::UiContext,
) -> PageContent {
+ let m = crate::app::section_margin();
let mut final_pc = PageContent::new();
let sec_w = 260.0f32;
let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(1);
@@ -337,7 +338,7 @@ pub fn view(
}
// ── Tabs (with counts), filter, compact update row ──
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
let tab_h = 28.0;
let active_bg = [0.20, 0.40, 0.65, 0.4];
let inactive_bg = [0.10, 0.10, 0.16, 0.3];
@@ -346,7 +347,7 @@ pub fn view(
let label1 = format!("Installed ({})", state.installed.len());
let label2 = format!("Updates ({})", state.updates.len());
- stack.add_row(2, 8.0, tab_h, |ctx, i, x, w| {
+ stack.add_row(2, cce_ui::layout::plate_gap(), tab_h, |ctx, i, x, w| {
if i == 0 {
ctx.button(
&label1,
@@ -376,9 +377,9 @@ pub fn view(
stack.context.spacing(4.0);
- let search_w = sec_w - 24.0;
+ let search_w = sec_w - 2.0 * m;
let search_h = 46.0;
- state.search_box.set_row_rect(stack.context.left + 12.0, search_w);
+ state.search_box.set_row_rect(stack.context.left + m, search_w);
stack.add_widget(&mut state.search_box, search_w, search_h, ctx);
stack.context.spacing(4.0);
@@ -396,7 +397,7 @@ pub fn view(
} else {
("Update System", [0.13, 0.18, 0.14, 1.0], [0.25, 0.30, 0.26, 1.0])
};
- stack.add_row(3, 8.0, 26.0, |c, i, x, w| {
+ stack.add_row(3, cce_ui::layout::plate_gap(), 26.0, |c, i, x, w| {
if i == 0 {
c.button(btn_lbl, x, c.ay(), w, 26.0, bg, hover, [0.90, 0.90, 0.95, 1.0],
AppAction::Packages(PackagesMessage::StartUpdate));
@@ -436,14 +437,14 @@ pub fn view(
if y > sc.content_start_y {
y += sc.row_gap;
}
- let lx = sc.ax(12.0);
+ let lx = sc.ax(m);
sc.pc.text(&parsed.name, lx, y, 14.0, [0.35, 0.65, 0.90, 1.0]);
sc.content_y = y + 20.0;
for h in &mut sc.grid.col_heights {
*h = sc.content_y;
}
if state.active_tab == PackageTab::Installed {
- let item_w = sc.cw - 2.0 * (sc.padding() + 12.0);
+ let item_w = sc.cw - 2.0 * (sc.padding() + m);
let bw = 100.0;
let bx = lx + item_w - bw;
let (btn_lbl, bg, hover, text_col) = if state.uninstalling {
@@ -462,9 +463,9 @@ pub fn view(
return;
}
let mut y = sc.content_y + 4.0;
- let lx = sc.ax(12.0);
+ let lx = sc.ax(m);
let vx = lx + 118.0;
- let usable_w = sc.cw - 118.0 - 2.0 * (sc.padding() + 12.0);
+ let usable_w = sc.cw - 118.0 - 2.0 * (sc.padding() + m);
let max_chars = ((usable_w / 6.0) as usize).max(15);
sc.pc.text(key, lx, y, 11.0, TEXT_DIM);
let lines = wrap_text(val, max_chars);
@@ -517,10 +518,10 @@ pub fn view(
section_divider(stack.context);
// ── List fills the rest of the page ──
- let list_box_x = sec.left + 12.0;
+ let list_box_x = sec.left + m;
let list_box_y = sec.ay();
- let list_box_w = sec_w - 24.0;
- let list_box_h = ((cy + ch) - 12.0 - list_box_y).max(120.0);
+ let list_box_w = sec_w - 2.0 * m;
+ let list_box_h = ((cy + ch) - m - list_box_y).max(120.0);
let query = if state.search_box.editing {
state.search_box.edit_buffer.to_lowercase()
@@ -601,8 +602,10 @@ pub fn view(
}
}
- // End the section so the well's bottom wall sits 12px below the list.
- sec.content_y = list_box_y + list_box_h + 12.0 - (sec.padding() + 12.0);
+ // End the section so the well's bottom wall sits one margin below
+ // the list: finish() places the wall at content_y + padding + margin,
+ // so the list's own bottom margin and that one cancel.
+ sec.content_y = list_box_y + list_box_h - sec.padding();
});
final_pc
diff --git a/src/pages/power.rs b/src/pages/power.rs
index d299cb8..dba65c2 100644
--- a/src/pages/power.rs
+++ b/src/pages/power.rs
@@ -676,7 +676,9 @@ pub fn view(state: &mut PowerState, cx: f32, cy: f32, cw: f32, ch: f32, _root_fo
if f.charge_limit.is_some() {
sec.spacing(10.0);
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
+ // TODO(style): the dropdown's 14px row inset is this page's own,
+ // two wider than the well margin its neighbours sit on.
dd_limit.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
stack.add_widget(dd_limit, sec_w - 28.0, 44.0, ctx);
}
@@ -712,7 +714,7 @@ pub fn view(state: &mut PowerState, cx: f32, cy: f32, cw: f32, ch: f32, _root_fo
builder.add_section(&mut final_pc, "Power Mode", focused(1), |sec| {
let sec_w = sec.cw;
{
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
dd_mode.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
stack.add_widget(dd_mode, sec_w - 28.0, 44.0, ctx);
}
@@ -726,7 +728,7 @@ pub fn view(state: &mut PowerState, cx: f32, cy: f32, cw: f32, ch: f32, _root_fo
}
sec.text("Not set leaves a lever alone.", 12.0, 0.0, 11.0, TEXT_DIM);
sec.spacing(6.0);
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
for i in 0..Lever::ALL.len() {
if levers.rows[i].is_empty() {
continue;
@@ -745,7 +747,7 @@ pub fn view(state: &mut PowerState, cx: f32, cy: f32, cw: f32, ch: f32, _root_fo
sec.text("Which mode runs in each adapter state.", 12.0, 0.0, 11.0, TEXT_DIM);
sec.text(&format!("{} right now.", f.source.label()), 12.0, 0.0, 11.0, GOOD);
sec.spacing(6.0);
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
for source in sources.iter().copied() {
let dd = &mut dd_assign[source_index(source)];
dd.set_row_rect(stack.context.left + 14.0, sec_w - 28.0);
diff --git a/src/pages/processes.rs b/src/pages/processes.rs
index 8ae1b8d..5ee5488 100644
--- a/src/pages/processes.rs
+++ b/src/pages/processes.rs
@@ -258,8 +258,9 @@ pub fn view(state: &mut ProcessesState, cx: f32, cy: f32, cw: f32, ch: f32, root
sec.text("Loading processes...", 12.0, 0.0, 12.0, TEXT_FG);
} else {
// Scrolling box configuration for process list: one even inset
- // between the list and the well's walls on all four sides.
- let inset = 12.0;
+ // between the list and the well's walls on all four sides — the
+ // well margin the toolkit's SectionContext lays out on.
+ let inset = crate::app::section_margin();
let list_box_x = rx + inset;
let list_box_w = sec.cw - 2.0 * inset;
// Power summary line above the list; the list starts below it.
@@ -285,6 +286,8 @@ pub fn view(state: &mut ProcessesState, cx: f32, cy: f32, cw: f32, ch: f32, root
const COL_KILL: f32 = 720.0;
const CONTENT_W: f32 = 745.0;
+ // TODO(style): the column offsets, header hit-target nudges and
+ // in-row text centring below are this table's own layout.
let header_h = 22.0;
state.cpu_list.set_rect(list_box_x, list_box_y, list_box_w, list_box_h);
state.cpu_list.set_content_w(CONTENT_W);
@@ -393,12 +396,13 @@ pub fn view(state: &mut ProcessesState, cx: f32, cy: f32, cw: f32, ch: f32, root
sec.pc.pop_clip_rect();
if state.processes.is_empty() {
- sec.pc.text("No active processes", list_box_x + 12.0, list_box_y + header_h + 16.0, 12.0, TEXT_DIM);
+ sec.pc.text("No active processes", list_box_x + inset, list_box_y + header_h + 16.0, 12.0, TEXT_DIM);
}
- // End the section so the well's bottom wall sits `inset` below the
- // list (finish() places the wall at content_y + padding + 12).
- sec.content_y = list_box_y + list_box_h + inset - (sec.padding() + 12.0);
+ // End the section so the well's bottom wall sits one margin below
+ // the list: finish() places the wall at content_y + padding +
+ // margin, so the list's own bottom margin and that one cancel.
+ sec.content_y = list_box_y + list_box_h - sec.padding();
}
});
diff --git a/src/pages/services.rs b/src/pages/services.rs
index 070b46b..6274b7a 100644
--- a/src/pages/services.rs
+++ b/src/pages/services.rs
@@ -59,6 +59,7 @@ pub enum ServicesMessage {
const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, _root_focused: bool, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut cce_ui::context::UiContext) -> PageContent {
+ let m = crate::app::section_margin();
let mut final_pc = PageContent::new();
let sec_w = 320.0f32;
let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(1);
@@ -69,7 +70,7 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, _root
sec.text("Loading systemd services...", 12.0, 0.0, 12.0, TEXT_DIM);
} else {
// Tab header buttons: System Services, User Services
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
let tab_h = 28.0;
let active_bg = [0.20, 0.40, 0.65, 0.4];
let inactive_bg = [0.10, 0.10, 0.16, 0.3];
@@ -78,7 +79,7 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, _root
let label1 = if stack.context.cw < 250.0 { "System" } else { "System Services" };
let label2 = if stack.context.cw < 250.0 { "User" } else { "User Services" };
- stack.add_row(2, 8.0, tab_h, |ctx, i, x, w| {
+ stack.add_row(2, cce_ui::layout::plate_gap(), tab_h, |ctx, i, x, w| {
if i == 0 {
ctx.button(
label1,
@@ -109,20 +110,20 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, _root
stack.context.spacing(4.0);
// Search textbox
- let search_w = sec_w - 24.0;
+ let search_w = sec_w - 2.0 * m;
let search_h = 46.0;
- state.search_box.set_row_rect(stack.context.left + 12.0, search_w);
+ state.search_box.set_row_rect(stack.context.left + m, search_w);
stack.add_widget(&mut state.search_box, search_w, search_h, ctx);
- stack.context.spacing(8.0);
+ stack.context.spacing(cce_ui::layout::plate_gap());
// Scroll box list
- let list_box_x = sec.left + 12.0;
+ let list_box_x = sec.left + m;
let list_box_y = sec.ay();
- let list_box_w = sec_w - 24.0;
+ let list_box_w = sec_w - 2.0 * m;
// Fill the page: the well's bottom wall lands at the page bottom,
- // the list keeps a 12px inset above it.
- let list_box_h = ((cy + ch) - 12.0 - list_box_y).max(120.0);
+ // the list keeps one margin above it.
+ let list_box_h = ((cy + ch) - m - list_box_y).max(120.0);
// Filter services
let query = if state.search_box.editing {
@@ -173,6 +174,8 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, _root
};
let btn_gap = if is_small { 4.0 } else { 6.0 };
+ // TODO(style): the row's control run and text column
+ // below are this list row's own layout.
let toggle_x = list_box_x + 10.0;
let restart_x = toggle_x + btn_w + btn_gap;
// The row's text starts after the controls and still ends
@@ -284,9 +287,10 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, _root
sec.pc.text("No services match the query", list_box_x + 16.0, list_box_y + 16.0, 12.0, TEXT_DIM);
}
- // End the section so the well's bottom wall sits 12px below the
- // list (finish() places the wall at content_y + padding + 12).
- sec.content_y = list_box_y + list_box_h + 12.0 - (sec.padding() + 12.0);
+ // End the section so the well's bottom wall sits one margin below
+ // the list: finish() places the wall at content_y + padding +
+ // margin, so the list's own bottom margin and that one cancel.
+ sec.content_y = list_box_y + list_box_h - sec.padding();
}
});
diff --git a/src/pages/storage.rs b/src/pages/storage.rs
index 8ecd19c..d091010 100644
--- a/src/pages/storage.rs
+++ b/src/pages/storage.rs
@@ -246,9 +246,9 @@ pub fn view(state: &mut StorageState, cx: f32, cy: f32, cw: f32, ch: f32, sec_fo
100.0, 0.0, 12.0, TEXT_FG,
);
- let bar_w = sec_w - 24.0;
+ let bar_w = sec_w - 2.0 * crate::app::section_margin();
let yt = sec.ay();
- let disk_bar_x = sec.ax(12.0);
+ let disk_bar_x = sec.ax(crate::app::section_margin());
let mut disk_bar = cce_ui::widget::UsageBar::new((disk_pct as f32 / 100.0).min(1.0))
.with_colors([0.36, 0.60, 0.36, 1.0], [0.15, 0.15, 0.25, 1.0]);
render_widget(sec.pc, &mut disk_bar, disk_bar_x, yt, bar_w, 8.0, ctx);
@@ -273,9 +273,9 @@ pub fn view(state: &mut StorageState, cx: f32, cy: f32, cw: f32, ch: f32, sec_fo
100.0, 0.0, 12.0, TEXT_FG,
);
- let bar_w = sec_w - 24.0;
+ let bar_w = sec_w - 2.0 * crate::app::section_margin();
let yt = sec.ay();
- let ram_bar_x = sec.ax(12.0);
+ let ram_bar_x = sec.ax(crate::app::section_margin());
let mut ram_bar = cce_ui::widget::UsageBar::new((ram_pct as f32 / 100.0).min(1.0))
.with_colors([0.50, 0.50, 0.65, 1.0], [0.15, 0.15, 0.25, 1.0]);
render_widget(sec.pc, &mut ram_bar, ram_bar_x, yt, bar_w, 8.0, ctx);
@@ -316,7 +316,7 @@ pub fn view(state: &mut StorageState, cx: f32, cy: f32, cw: f32, ch: f32, sec_fo
}
// Action Button
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
let btn_h = 32.0;
// Retained widget rather than an immediate `sec.button`, so it can hold
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 194882d..1616db0 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -266,7 +266,7 @@ where
/// pretends otherwise clips one and pads the other.
fn button_need(label: &str) -> f32 {
let (family, size) = cce_ui::layout::control_label_font_parsed();
- cce_ui::widget::display::measure_text_width(label, &family, size) + 24.0
+ cce_ui::widget::display::measure_text_width(label, &family, size) + 2.0 * cce_ui::layout::CONTROL_TEXT_INSET + 8.0
}
fn spawn_systemctl(action: &str) {
@@ -425,12 +425,12 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
// ── 2. System Actions Section ──
builder.add_section(&mut final_pc, "System Actions", sec_focused.get(1).copied().unwrap_or(false), |sec| {
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
let act_btn_h = 32.0;
const ACTIONS: [&str; 4] = ["Suspend", "Hibernate", "Reboot", "Power Off"];
let needs: Vec<f32> = ACTIONS.iter().map(|l| button_need(l)).collect();
- stack.add_row_for(&needs, 8.0, act_btn_h, |ctx, i, x, w| {
+ stack.add_row_for(&needs, cce_ui::layout::plate_gap(), act_btn_h, |ctx, i, x, w| {
match i {
0 => {
ctx.button("Suspend", x, ctx.ay(), w, act_btn_h,
@@ -540,7 +540,7 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
let has_work = sf.scanned && !sf.pending.is_empty();
let busy = sf.busy;
let needs = [button_need("Check"), button_need("Install (root)")];
- stack.add_row_for(&needs, 8.0, btn_h, move |c, i, x, w| {
+ stack.add_row_for(&needs, cce_ui::layout::plate_gap(), btn_h, move |c, i, x, w| {
match i {
0 => {
c.button("Check", x, c.ay(), w, btn_h,
diff --git a/src/pages/timers.rs b/src/pages/timers.rs
index c8eb4b5..db8afdc 100644
--- a/src/pages/timers.rs
+++ b/src/pages/timers.rs
@@ -323,6 +323,7 @@ fn systemctl_action(args: &[&str], is_system: bool) {
}
pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_focused: bool, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut cce_ui::context::UiContext) -> PageContent {
+ let m = crate::app::section_margin();
let mut final_pc = PageContent::new();
let sec_w = 320.0f32;
let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(1);
@@ -333,7 +334,7 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
sec.text("Loading systemd timers...", 12.0, 0.0, 12.0, TEXT_DIM);
} else {
// Tab header buttons: System Timers, User Timers
- let mut stack = sec.vstack(8.0);
+ let mut stack = sec.vstack(cce_ui::layout::plate_gap());
let tab_h = 28.0;
let active_bg = [0.20, 0.40, 0.65, 0.4];
let inactive_bg = [0.10, 0.10, 0.16, 0.3];
@@ -342,7 +343,7 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
let label1 = if stack.context.cw < 250.0 { "System" } else { "System Timers" };
let label2 = if stack.context.cw < 250.0 { "User" } else { "User Timers" };
- stack.add_row(2, 8.0, tab_h, |ctx, i, x, w| {
+ stack.add_row(2, cce_ui::layout::plate_gap(), tab_h, |ctx, i, x, w| {
if i == 0 {
ctx.button(
label1,
@@ -374,7 +375,7 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
// New Timer (user scope) — compact button; the form unfolds below.
let new_bg = if state.creating { active_bg } else { [0.13, 0.18, 0.14, 1.0] };
- stack.add_row(3, 8.0, 26.0, |c, i, x, w| {
+ stack.add_row(3, cce_ui::layout::plate_gap(), 26.0, |c, i, x, w| {
if i == 0 {
c.button("New Timer", x, c.ay(), w, 26.0,
new_bg, [0.25, 0.30, 0.26, 1.0], [0.90, 0.90, 0.95, 1.0],
@@ -383,7 +384,7 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
});
if state.creating || state.editing.is_some() {
- let item_w = sec_w - 2.0 * (stack.context.padding() + 12.0);
+ let item_w = sec_w - 2.0 * (stack.context.padding() + m);
let rx = stack.context.left;
let widget_h = cce_ui::layout::spinbox_height();
@@ -394,17 +395,17 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
}
if state.creating {
- state.name_box.set_row_rect(rx + 12.0, item_w);
+ state.name_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.name_box, item_w, widget_h, ctx);
}
- state.command_box.set_row_rect(rx + 12.0, item_w);
+ state.command_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.command_box, item_w, widget_h, ctx);
- state.schedule_box.set_row_rect(rx + 12.0, item_w);
+ state.schedule_box.set_row_rect(rx + m, item_w);
stack.add_widget(&mut state.schedule_box, item_w, widget_h, ctx);
stack.context.spacing(4.0);
let save_label = if state.editing.is_some() { "Save" } else { "Create" };
- stack.add_row(3, 8.0, 26.0, |c, i, x, w| {
+ stack.add_row(3, cce_ui::layout::plate_gap(), 26.0, |c, i, x, w| {
match i {
0 => c.button(save_label, x, c.ay(), w, 26.0,
[0.13, 0.18, 0.14, 1.0], [0.25, 0.30, 0.26, 1.0], [0.90, 0.90, 0.95, 1.0],
@@ -421,13 +422,13 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
stack.context.text(msg, 12.0, 0.0, 12.0, [0.56, 0.83, 0.56, 1.0]);
}
- stack.context.spacing(8.0);
+ stack.context.spacing(cce_ui::layout::plate_gap());
// Scroll box list, filling the page like the services list.
- let list_box_x = sec.left + 12.0;
+ let list_box_x = sec.left + m;
let list_box_y = sec.ay();
- let list_box_w = sec_w - 24.0;
- let list_box_h = ((cy + ch) - 12.0 - list_box_y).max(120.0);
+ let list_box_w = sec_w - 2.0 * m;
+ let list_box_h = ((cy + ch) - m - list_box_y).max(120.0);
let now = now_usec();
let filtered: Vec<&TimerInfo> = state.timers.iter()
@@ -455,6 +456,8 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
let en_w = if is_small { 40.0 } else { 66.0 };
let edit_w = if is_small { 36.0 } else { 50.0 };
let btn_gap = if is_small { 4.0 } else { 6.0 };
+ // TODO(style): the row's button run, dot and text
+ // column below are this list row's own layout.
let right_edge = list_box_x + list_box_w - 24.0 - 8.0;
let en_x = right_edge - en_w;
@@ -558,8 +561,10 @@ pub fn view(state: &mut TimersState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
sec.pc.text("No timers in this scope", list_box_x + 16.0, list_box_y + 16.0, 12.0, TEXT_DIM);
}
- // End the section so the well's bottom wall sits 12px below the list.
- sec.content_y = list_box_y + list_box_h + 12.0 - (sec.padding() + 12.0);
+ // End the section so the well's bottom wall sits one margin below
+ // the list: finish() places the wall at content_y + padding +
+ // margin, so the list's own bottom margin and that one cancel.
+ sec.content_y = list_box_y + list_box_h - sec.padding();
}
});
diff --git a/src/renderer.rs b/src/renderer.rs
index e85c338..ed1a64a 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -175,6 +175,8 @@ impl SystemInterface {
// height — the legacy window pass also ran before this frame's content
// was measured.
let sb_w = cce_ui::layout::scrollbar_width() * 1.6;
+ // TODO(style): the bar's 4px vertical stand-off pairs with the
+ // toolkit's `scrollbar_inset()` knob, not a rung of the ladder.
self.page_scroll_bar.set_rect(
logical_sw - sb_w - cce_ui::layout::scrollbar_inset(),
self.header_height + 4.0,
@@ -244,12 +246,14 @@ impl SystemInterface {
0.0,
(false, false, false, false),
));
+ // The search box stands on the root plate: the window-edge inset.
+ let inset = cce_ui::layout::root_plate_inset();
cce_ui::layout::render_widget(
&mut search_pc,
&mut self.search_box,
- self.sidebar_width + 12.0,
+ self.sidebar_width + inset,
sh - 36.0,
- sw - self.sidebar_width - 24.0,
+ sw - self.sidebar_width - 2.0 * inset,
30.0,
&mut self.ui_context,
);
@@ -599,7 +603,7 @@ impl SystemInterface {
let logical_tw = tw / scale_factor;
let logical_lh = lh / scale_factor;
let text_x = if left_align {
- base.x + 8.0
+ base.x + cce_ui::layout::CONTROL_TEXT_INSET
} else {
base.x + (base.w - logical_tw) / 2.0
};
@@ -795,12 +799,17 @@ impl SystemInterface {
pub(crate) fn render_page_content(&mut self, cx: f32, cy: f32, cw: f32, ch: f32) -> PageContent {
use cce_ui::layout::AdaptiveGrid;
- let margin = cce_ui::layout::page_margin();
+ // The sections are wells carved straight into the root plate (no
+ // sidebar, no pane between), so the page's edge is the window's edge:
+ // the root rung's inset, and the grid's gap the root rung's gap (set
+ // once in main; `AdaptiveGrid::init` reads the toolkit getters, the
+ // constructor args are the same numbers stated for the record).
+ let margin = cce_ui::layout::root_plate_inset();
let cx = cx + margin;
let cy = cy + margin;
let cw = (cw - 2.0 * margin).max(1.0);
let ch = (ch - 2.0 * margin).max(1.0);
- let mut layout = AdaptiveGrid::new(260.0, 20.0);
+ let mut layout = AdaptiveGrid::new(cce_ui::layout::grid_min_col_width(), cce_ui::layout::root_plate_gap());
// Page root dissolved (6u): the ctrl-nav entry focuses section 0, so root focus is
// permanently false; views that highlighted on it OR in their first section's bool.
// Section focus is the app-side index now (Phase 6w).