From f897e1c96a87d05fec333d6e89b33bbc273d74b7 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 12 Jun 2026 18:00:21 +0530 Subject: [PATCH] feat(tokens+cad): schematic palette + cad_tool_button paint-closure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 3 of the Tokito editor migration foundation. Two changes that let the editor delete `crate::ui::tokens::UiTokens` entirely and rebuild its hand-drawn CAD tool rail on the shared `cad_tool_button` primitive. ## Schematic palette in Tokens Adds 19 schematic-canvas colour fields to `Tokens` (canvas_bg, grid_*, canvas_frame, sym_ink, sym_body_fill, sym_ink_hover, sym_ink_selected, sym_outline, sym_sel_ring, wire, wire_highlight, wire_selected, label_ink, refdes_ink, pin_ink, pin_hot, selection, preview_bg) with sensible defaults in both `dark()` and `light()` presets. Cool teal-ink schematic on a deep slate sheet in dark, near-black ink on pale slate in light; warm orange selection ring in both. Effect: a consumer's editor canvas pulls from the same Tokens value that drives its chrome — one token source. Non-canvas consumers can ignore the schematic fields. ## cad_tool_button paint-closure Replaces the hardcoded `glyph: &str` parameter with a closure `FnOnce(&Painter, Rect, Color32)`. The caller decides what to draw: - Phosphor case: `cad_tool_button(ui, &t, 38.0, sel, "Wire", paint_phosphor_glyph(icons::ph::PEN_NIB))` - Hand-drawn schematic glyphs: pass a closure that runs line_segment / rect_stroke / circle_filled directly. Used by the Tokito schematic editor for wire-bend, GND-triangle, net-label, junction icons that aren't in Phosphor. `paint_phosphor_glyph(glyph)` is the convenience helper for the common case; it centres a glyph at a sensible size for the button. This is a breaking signature change vs the previous cad_tool_button — no production callers yet (no shipped consumers), so the simpler explicit-closure shape wins over backwards-compatible enum variants. ## Docs - README — Tokens groups now name the schematic palette; cad_tool_button table row shows the new signature. - AGENTS.md — paint_phosphor_glyph noted next to cad_tool_button. ## CI gates locally clean - `cargo fmt --all -- --check` - `cargo clippy --all-targets -- -D warnings` - `cargo build` - `RUSTDOCFLAGS="-D warnings" cargo doc --no-deps` Co-Authored-By: Claude Opus 4.7 --- AGENTS.md | 8 ++-- README.md | 11 ++++-- src/components.rs | 54 +++++++++++++++++++-------- src/tokens.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 586821c..6dd0654 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,10 +28,10 @@ src/ `text_button`, `link`, `badge`, `menu_button`, `menu_item`, `list_row`, `text_input`, `search_field`, `secret_input`, `toggle`, `modal`, `page_header`, `section_header`, `nav_item`, `checkbox`, `segmented`, -`select`, `select_option`, `banner`, `collapsing`, `cad_tool_button`, -`data_table` (+ `SortState`, `sortable_header`), `toast_overlay` -(+ `ToastStack`), `chip`, `content_card`, `inspector_row`, -`list_section_label`, `empty_state`. +`select`, `select_option`, `banner`, `collapsing`, `cad_tool_button` +(+ `paint_phosphor_glyph`), `data_table` (+ `SortState`, +`sortable_header`), `toast_overlay` (+ `ToastStack`), `chip`, +`content_card`, `inspector_row`, `list_section_label`, `empty_state`. ## Rules — keep these true diff --git a/README.md b/README.md index 9628397..21c89bb 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,13 @@ It is `#[non_exhaustive]` — new fields can be added without breaking you. Groups: surfaces (`bg`, `bg_chrome`, `card`, `card_hover`), borders (`border`, `border_soft`, `border_strong`), text (`text`, `text_2`, `text_3`, `text_disabled`), accents (`accent`, `accent_ink`, `accent_soft`, `accent_2`, -`accent_2_soft`), status (`danger`, `warning`, `success`), and metrics -(`radius_*`, `space_1..5`). +`accent_2_soft`), status (`danger`, `warning`, `success`), metrics +(`radius_*`, `space_1..5`), and a **schematic palette** for CAD canvas use +(`canvas_bg`, `canvas_grid_minor`, `canvas_grid_major`, `canvas_frame`, +`sym_ink`, `sym_body_fill`, `sym_ink_hover`, `sym_ink_selected`, +`sym_outline`, `sym_sel_ring`, `wire`, `wire_highlight`, `wire_selected`, +`label_ink`, `refdes_ink`, `pin_ink`, `pin_hot`, `selection`, `preview_bg`). +Non-canvas consumers can ignore the schematic fields. ## Components @@ -119,7 +124,7 @@ All live in `tokito_ui::components` (aliased `c` above). Each takes | `select_option` | `select_option(ui, t, label, selected) -> bool` | One option row inside a `select` popup. | | `banner` | `banner(ui, t, kind, glyph, title, body) -> Response` | A status callout — `BannerKind::Success` / `Danger` / `Warning` / `Info`. | | `collapsing` | `collapsing(ui, t, id_source, label, \|ui\| …)` | A collapsible "Advanced options" disclosure section. | -| `cad_tool_button` | `cad_tool_button(ui, t, glyph, side, selected, tooltip) -> Response` | A square, toggleable CAD tool-rail button — accent border + soft fill when selected. | +| `cad_tool_button` + `paint_phosphor_glyph` | `cad_tool_button(ui, t, side, selected, tooltip, \|p, r, ink\| …)` | A square, toggleable CAD tool-rail button. Caller paints the icon via the closure — pass `paint_phosphor_glyph(GLYPH)` for Phosphor, or paint hand-drawn schematic strokes. | | `data_table` + `sortable_header` | `data_table(ui, t, id, headers, cols, &mut SortState, n, h, \|row, i\| …)` | A scrollable [`egui_extras::TableBuilder`] table with click-to-sort column headers. | | `toast_overlay` + `ToastStack` | `toast_overlay(ctx, t, &mut ToastStack)` | Transient bottom-right notifications. `ToastStack` is the owned queue; push from anywhere, paint once per frame. | | `chip` | `chip(ui, t, label, selected) -> bool` | A small toggleable pill — filter chip / tag. Returns `true` on click. | diff --git a/src/components.rs b/src/components.rs index b663ee2..b047d67 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1055,20 +1055,28 @@ fn lighten(c: Color32, amount: f32) -> Color32 { /// A square, toggleable CAD-tool-rail button. /// /// Used for the left-side tool rail in a schematic / PCB editor (select, -/// wire, label, bus, etc.). `glyph` is a Phosphor constant; `side` is the -/// width and height; `selected` paints the active state (accent border + -/// soft accent fill); `tooltip` shows on hover. +/// wire, label, bus, etc.). `side` is the width and height; `selected` paints +/// the active state (accent border + soft accent fill); `tooltip` shows on +/// hover. /// -/// Hover eases an underlay fill in; the icon ink is `accent` when selected, -/// `text` otherwise. -pub fn cad_tool_button( +/// `paint_icon` is invoked with the button's `Painter`, the inner `Rect`, and +/// the current ink colour — the caller decides what symbol to draw (Phosphor +/// glyph, hand-drawn schematic strokes, an image, whatever). For the common +/// Phosphor case, use [`paint_phosphor_glyph`] as the closure. +/// +/// Hover eases an underlay fill in; the ink is `accent` when selected, +/// `text` otherwise (interpolated with `text_2` on hover). +pub fn cad_tool_button( ui: &mut Ui, t: &Tokens, - glyph: &str, side: f32, selected: bool, tooltip: &str, -) -> Response { + paint_icon: F, +) -> Response +where + F: FnOnce(&egui::Painter, Rect, Color32), +{ let (rect, mut response) = ui.allocate_exact_size(Vec2::splat(side), Sense::click()); let factor = hover_t(ui, response.id, response.hovered()); @@ -1093,14 +1101,7 @@ pub fn cad_tool_button( } else { lerp_color(t.text_2, t.text, factor) }; - let glyph_size = (side * 0.5).clamp(14.0, 24.0); - painter.text( - rect.center(), - egui::Align2::CENTER_CENTER, - glyph, - icons::font(glyph_size), - ink, - ); + paint_icon(painter, rect, ink); if !tooltip.is_empty() { response = response.on_hover_text(tooltip); @@ -1108,6 +1109,27 @@ pub fn cad_tool_button( response } +/// Helper closure for [`cad_tool_button`] that paints a centred Phosphor +/// glyph at a sensible size for the button. +/// +/// Usage: +/// ```ignore +/// cad_tool_button(ui, &t, 38.0, selected, "Wire", paint_phosphor_glyph(icons::ph::PEN_NIB)) +/// ``` +pub fn paint_phosphor_glyph(glyph: &'static str) -> impl FnOnce(&egui::Painter, Rect, Color32) { + move |painter, rect, ink| { + let side = rect.width().min(rect.height()); + let glyph_size = (side * 0.5).clamp(14.0, 24.0); + painter.text( + rect.center(), + egui::Align2::CENTER_CENTER, + glyph, + icons::font(glyph_size), + ink, + ); + } +} + // --------------------------------------------------------------------------- // table // --------------------------------------------------------------------------- diff --git a/src/tokens.rs b/src/tokens.rs index 050ce27..f7ef33d 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -81,6 +81,59 @@ pub struct Tokens { pub space_3: f32, pub space_4: f32, pub space_5: f32, + + // ----------------------------------------------------------------------- + // Schematic palette + // ----------------------------------------------------------------------- + // + // These describe colours for a schematic / CAD-style canvas. They live + // here (in the shared design layer) so a consumer's editor canvas pulls + // from the same `Tokens` value that drives its chrome — one token source. + // + // A non-canvas consumer can ignore these fields; the presets below pick + // reasonable defaults. + /// Schematic sheet background. + pub canvas_bg: Color32, + /// Minor grid line — quiet, for the dense grid step. + pub canvas_grid_minor: Color32, + /// Major grid line — stronger, every Nth grid step. + pub canvas_grid_major: Color32, + /// Sheet frame border / title block strokes. + pub canvas_frame: Color32, + + /// Symbol body ink — schematic outlines at rest. + pub sym_ink: Color32, + /// Symbol body fill — pale KiCad-style component body. + pub sym_body_fill: Color32, + /// Symbol body ink when hovered. + pub sym_ink_hover: Color32, + /// Symbol body ink when selected. + pub sym_ink_selected: Color32, + /// Subtle outline ring (anti-aliasing halo, soft separators). + pub sym_outline: Color32, + /// Selection ring around the symbol bounding box. + pub sym_sel_ring: Color32, + + /// Wire — default ink. + pub wire: Color32, + /// Wire — highlighted (same net under cursor / search match). + pub wire_highlight: Color32, + /// Wire — selected. + pub wire_selected: Color32, + + /// Net label ink. + pub label_ink: Color32, + /// Reference designator ink. + pub refdes_ink: Color32, + /// Pin name / number ink. + pub pin_ink: Color32, + /// Pin "hot" ink — active connection point, current drag. + pub pin_hot: Color32, + + /// Marquee / multi-select fill. + pub selection: Color32, + /// Preview backdrop (place-tool ghost, drag preview). + pub preview_bg: Color32, } impl Tokens { @@ -116,6 +169,27 @@ impl Tokens { space_3: 12.0, space_4: 16.0, space_5: 24.0, + // Schematic palette — dark variant. Cool teal-ink schematic + // against a deep slate sheet, warm orange selection ring. + canvas_bg: Color32::from_rgb(0x14, 0x16, 0x1c), + canvas_grid_minor: Color32::from_rgba_unmultiplied(0x60, 0x64, 0x70, 0x1c), + canvas_grid_major: Color32::from_rgba_unmultiplied(0x7a, 0x80, 0x8c, 0x34), + canvas_frame: Color32::from_rgb(0x4a, 0x4f, 0x5a), + sym_ink: Color32::from_rgb(0xe6, 0xe8, 0xec), + sym_body_fill: Color32::from_rgb(0x22, 0x24, 0x2a), + sym_ink_hover: Color32::from_rgb(0x2d, 0xd4, 0xbf), + sym_ink_selected: Color32::from_rgb(0xff, 0xff, 0xff), + sym_outline: Color32::from_rgb(0x2a, 0x2c, 0x32), + sym_sel_ring: Color32::from_rgb(0xe0, 0x78, 0x20), + wire: Color32::from_rgb(0x9d, 0xc7, 0xff), + wire_highlight: Color32::from_rgb(0x2d, 0xd4, 0xbf), + wire_selected: Color32::from_rgb(0xe0, 0x78, 0x20), + label_ink: Color32::from_rgb(0xb9, 0xc7, 0xdc), + refdes_ink: Color32::from_rgb(0x9a, 0x9d, 0xa7), + pin_ink: Color32::from_rgb(0xa8, 0xb0, 0xbe), + pin_hot: Color32::from_rgb(0xe0, 0x78, 0x20), + selection: Color32::from_rgba_unmultiplied(0xe0, 0x78, 0x20, 0x33), + preview_bg: Color32::from_rgb(0x1a, 0x1c, 0x22), } } @@ -151,6 +225,27 @@ impl Tokens { space_3: 12.0, space_4: 16.0, space_5: 24.0, + // Schematic palette — light variant. Schematic-ink (near-black) + // on a soft slate sheet; same warm orange selection. + canvas_bg: Color32::from_rgb(0xf4, 0xf7, 0xfa), + canvas_grid_minor: Color32::from_rgba_unmultiplied(0x8c, 0x94, 0x9e, 0x1c), + canvas_grid_major: Color32::from_rgba_unmultiplied(0x78, 0x80, 0x8c, 0x34), + canvas_frame: Color32::from_rgb(0xa8, 0xae, 0xb8), + sym_ink: Color32::from_rgb(0x1c, 0x20, 0x26), + sym_body_fill: Color32::from_rgb(0xff, 0xfb, 0xde), + sym_ink_hover: Color32::from_rgb(0x14, 0x34, 0x5c), + sym_ink_selected: Color32::from_rgb(0x10, 0x14, 0x1a), + sym_outline: Color32::from_rgb(0xfa, 0xfb, 0xfc), + sym_sel_ring: Color32::from_rgb(0xe0, 0x78, 0x20), + wire: Color32::from_rgb(0x30, 0x5e, 0x84), + wire_highlight: Color32::from_rgb(0x14, 0x84, 0x76), + wire_selected: Color32::from_rgb(0xe0, 0x78, 0x20), + label_ink: Color32::from_rgb(0x28, 0x48, 0x6c), + refdes_ink: Color32::from_rgb(0x30, 0x36, 0x3e), + pin_ink: Color32::from_rgb(0x48, 0x58, 0x6c), + pin_hot: Color32::from_rgb(0xe0, 0x78, 0x20), + selection: Color32::from_rgba_unmultiplied(0xe0, 0x78, 0x20, 0x33), + preview_bg: Color32::from_rgb(0xf4, 0xf5, 0xf7), } }