From b68dfd70e5d3bd0785aa0ce0528da857b3854e78 Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Thu, 14 May 2026 15:43:46 +0800 Subject: [PATCH] feat(shell-core/canvas): paint-time \$ref substitution active MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `paint_fill_then_stroke` now takes the resolved fill explicitly rather than reading `node.fill` directly. Callers in `paint_node` pre-resolve via `node_fill(node, var_table)` which checks `var_table.fill_for(node.id)` first, falling through to `node.fill` otherwise. Result: a Frame / Rect whose canonical loader registered a `$ref` for its fill paints the current themed value at runtime; flipping `active_theme` repaints with the new colour. - `paint_fill_then_stroke` signature: adds `fill: Option` as the last arg (after world_rect + zoom). - Both `NodeKind::Frame` + `NodeKind::Rect` branches in `paint_node` now compute `node_fill(node, var_table)` before calling the helper. #5 Variables/Themes: types + storage + canonical loader + fill_refs map + resolve + paint-time substitution all working. Variables panel UI (active-theme picker + variable list with edit) is the remaining piece — that's a widget, not a model change. Tests total: 236 shell-core (no new assertions in this commit; the existing 8 variable tests cover the resolution chain that paint now consumes). Wasm32 build clean. --- .../src/widgets/canvas_viewport.rs | 4 ++-- .../src/widgets/canvas_viewport_overlay.rs | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs b/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs index 8bdae843f..0a1691948 100644 --- a/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs +++ b/crates/openpencil-shell-core/src/widgets/canvas_viewport.rs @@ -412,7 +412,7 @@ fn paint_node( match &node.kind { NodeKind::Frame => { - paint_fill_then_stroke(cx, node, world_rect, zoom); + paint_fill_then_stroke(cx, node, world_rect, zoom, node_fill(node, var_table)); for child in &node.children { paint_node(cx, child, viewport_origin, zoom, selected, edit_caret, cull, var_table); } @@ -426,7 +426,7 @@ fn paint_node( } } NodeKind::Rect => { - paint_fill_then_stroke(cx, node, world_rect, zoom); + paint_fill_then_stroke(cx, node, world_rect, zoom, node_fill(node, var_table)); } NodeKind::Ellipse => { if let Some(fill) = node.fill { diff --git a/crates/openpencil-shell-core/src/widgets/canvas_viewport_overlay.rs b/crates/openpencil-shell-core/src/widgets/canvas_viewport_overlay.rs index 9dabb58db..83cf0b907 100644 --- a/crates/openpencil-shell-core/src/widgets/canvas_viewport_overlay.rs +++ b/crates/openpencil-shell-core/src/widgets/canvas_viewport_overlay.rs @@ -138,15 +138,19 @@ pub fn paint_selection_overlay( /// Paint a node's fill rect followed by its stroke rect. Stroke /// width is scaled by `zoom` so it stays visually constant under -/// canvas zoom. -pub fn paint_fill_then_stroke(cx: &mut PaintCx<'_>, node: &Node, world_rect: Rect, zoom: f32) { - // Scale doc-space radius into world-space alongside the rect. - // 0.5px is below the visible threshold for most renders; collapse - // to a square fill so the round-rect path doesn't accidentally - // soften 0-radius corners due to sub-pixel rounding. +/// canvas zoom. Effective `fill` is passed explicitly so callers +/// can substitute via `var_table.fill_for(node.id)` before +/// rendering (paint-time `$ref` resolution — #5). +pub fn paint_fill_then_stroke( + cx: &mut PaintCx<'_>, + node: &Node, + world_rect: Rect, + zoom: f32, + fill: Option, +) { let r = node.corner_radius * zoom; let use_round = r > 0.5; - if let Some(fill) = node.fill { + if let Some(fill) = fill { if use_round { cx.backend.fill_round_rect(world_rect, r, fill); } else {