From c76af49b65006897ebbd77a157db6c9d756abb2f Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Thu, 14 May 2026 19:10:25 +0800 Subject: [PATCH] feat(host): VariablesPanel row click opens ColorPicker (variable mode) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the missing host wire that codex called out as pending: the VariablesPanel widget paints (140d5495 / 0bd98ae2) and the picker has variable-mode commit (3c2e7711 / 99d602a3), but clicking a row did nothing. This commit dispatches. `crates/openpencil-shell-native/src/widget_host/press.rs`: - New step 0d in `apply_press` (between PropertyPanel input and AI chat dispatch): one-line call to the new helper. Keeps the cascade ordering — properties consume their hits first, vars next, chat after. `crates/openpencil-shell-native/src/widget_host/property_dispatch.rs`: - `dispatch_variables_panel_press(x, y, vw, vh) -> bool` mirrors the existing `dispatch_export_dialog_press` pattern. Reconstructs the same right-rail rect math `paint.rs` step 4b uses (top when no selection, bottom-anchored above status bar when PropertyPanel owns the rail), hit-tests, then routes: - `Row(idx)` on a Color-kind variable → commit any pending property focus, then `open_color_picker_for_variable(name)`. The picker's HSV-drag path writes through `set_color_hex` (which carries all three correctness invariants); close pushes the snapshot+var_table undo entry from 99d602a3. - `Row(idx)` on a non-color variable → swallow (TODO: row inputs for string / number). Prevents fall-through to canvas deselect. - `AxisChip(_)` → swallow (TODO: theme-axis picker). - Returns `false` when var_table is empty or the click missed, so press.rs's cascade continues to chat / canvas. Tests: 295 shell-core + 20 shell-native still pass. The dispatcher is exercised by the existing `VariablesPanel::hit_test` tests + the `color_picker::tests::*` end-to-end variable flow; a wired host integration test would need a fake-render harness that's out of scope here. press.rs grew from 825 → 830 (+5 lines for the cascade call site). That file was over the 800-line cap before this session — pre- existing tech debt tracked separately. Not making it materially worse. TOP-10 #5 (Variables/Themes UI) edit chain is now functionally complete: paint → click → picker → write → undo. The remaining UX gaps are non-color variable inputs + the theme-axis picker, both flagged as TODOs in the dispatcher. --- .../src/widget_host/press.rs | 5 ++ .../src/widget_host/property_dispatch.rs | 63 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/crates/openpencil-shell-native/src/widget_host/press.rs b/crates/openpencil-shell-native/src/widget_host/press.rs index 1a5adb44b..2a5302857 100644 --- a/crates/openpencil-shell-native/src/widget_host/press.rs +++ b/crates/openpencil-shell-native/src/widget_host/press.rs @@ -388,6 +388,11 @@ impl WidgetHostNative { } } + // 0d. VariablesPanel row click — body in property_dispatch. + if self.dispatch_variables_panel_press(x, y, viewport_width, viewport_height) { + return true; + } + // 1. AI chat panel — sits on top of the toolbar in paint // order, so any click inside its rect is consumed // here. DragHandle starts a chat drag; other AI hits diff --git a/crates/openpencil-shell-native/src/widget_host/property_dispatch.rs b/crates/openpencil-shell-native/src/widget_host/property_dispatch.rs index 9eeba81c9..ef1006aca 100644 --- a/crates/openpencil-shell-native/src/widget_host/property_dispatch.rs +++ b/crates/openpencil-shell-native/src/widget_host/property_dispatch.rs @@ -212,4 +212,67 @@ impl WidgetHostNative { } } } + + /// VariablesPanel press dispatcher — peer of + /// `dispatch_export_dialog_press`. Returns `true` when the + /// click hit the variables panel and was consumed; `false` + /// otherwise so the caller continues its hit-test cascade. + /// + /// Row clicks on Color-kind variables open the ColorPicker in + /// variable mode (`Document::open_color_picker_for_variable`); + /// the picker's commit path writes through + /// `VariableTable::set_color_hex` so the variable is editable + /// end-to-end. Non-color rows + AxisChip clicks swallow today + /// (specific editors land later — string/number row inputs + + /// the theme-axis picker). + pub(in crate::widget_host) fn dispatch_variables_panel_press( + &mut self, + x: f32, + y: f32, + viewport_width: f32, + viewport_height: f32, + ) -> bool { + if self.document.var_table.variables.is_empty() { + return false; + } + use openpencil_shell_core::widgets::variables_panel::{ + VariablesPanel, VariablesPanelHit, + }; + use openpencil_shell_core::widgets::{STATUS_BAR_HEIGHT, TOP_BAR_HEIGHT}; + use openpencil_shell_core::{Point2D, Rect}; + let vars = VariablesPanel::for_document(&self.document); + let intrinsic = vars.intrinsic_height(); + let top_y = if self.document.property_panel_visible() { + let bottom_pad = STATUS_BAR_HEIGHT + 16.0; + (viewport_height - bottom_pad - intrinsic).max(TOP_BAR_HEIGHT + 8.0) + } else { + TOP_BAR_HEIGHT + 8.0 + }; + let vars_rect = Rect { + origin: Point2D::new( + viewport_width - self.document.ui.property_panel_width, + top_y, + ), + size: Point2D::new(self.document.ui.property_panel_width, intrinsic), + }; + let Some(hit) = vars.hit_test(vars_rect, Point2D::new(x, y)) else { + return false; + }; + match hit { + VariablesPanelHit::Row(idx) => { + if let Some(var) = self.document.var_table.variables.get(idx) { + if matches!( + var.kind, + openpencil_shell_core::document::VariableKind::Color + ) { + let name = var.name.clone(); + self.commit_property_focus_if_any(); + let _ = self.document.open_color_picker_for_variable(name, y); + } + } + true + } + VariablesPanelHit::AxisChip(_) => true, + } + } }