feat(host): VariablesPanel row click opens ColorPicker (variable mode)

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.
This commit is contained in:
Kayshen-X 2026-05-14 19:10:25 +08:00
parent 394ca2f829
commit c76af49b65
2 changed files with 68 additions and 0 deletions

View file

@ -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

View file

@ -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,
}
}
}