diff --git a/crates/ui-egui/src/dialogs/color_picker.rs b/crates/ui-egui/src/dialogs/color_picker.rs index 11ed3571..47338834 100644 --- a/crates/ui-egui/src/dialogs/color_picker.rs +++ b/crates/ui-egui/src/dialogs/color_picker.rs @@ -1,7 +1,8 @@ //! The Color Picker (double-click a Fill/Stroke proxy, or `ui.colorPicker`): a colour field with a //! channel slider for H, S, B, R, G or B; HSB, RGB, Lab and CMYK fields and hex; Only Web Colors; //! the out-of-gamut and out-of-web warnings (click to correct); New/Original chips; and a Color -//! Swatches list. OK applies the colour to its proxy through `paint.setFill` / `paint.setStroke`. +//! Swatches list. With art selected the colour shows on it live as it is picked; OK keeps it as one +//! undo step through `paint.setFill` / `paint.setStroke`, Cancel puts the art back. //! //! Fields agents set with `ui.dialog.set`: `hex` (`"00FF00"`) or `color` (any colour the paint //! commands take), `channel` (`hue`, `saturation`, `brightness`, `red`, `green`, `blue`), `webOnly` @@ -15,6 +16,7 @@ use vectorcraft_doc::ColorMode; use vectorcraft_engine::cmd::color_value; use super::DialogSpec; +use super::form::PREVIEWED; use crate::panels::color::{GAMUT_WARNING, WEB_WARNING, gamut_fix, hex_digits, is_web_safe, parse_hex, warning_chip, web_safe}; use crate::panels::{c32, color_json}; use crate::state::Dialog; @@ -23,8 +25,15 @@ use crate::{VectorcraftApp, widgets}; pub(super) const KIND: &str = "colorPicker"; -pub(super) const SPEC: DialogSpec = - DialogSpec { heading: |_| tl!("Color Picker").into(), body, confirm, min_width: 600.0, max_width: Some(640.0), ..DialogSpec::FORM }; +pub(super) const SPEC: DialogSpec = DialogSpec { + heading: |_| tl!("Color Picker").into(), + body, + confirm, + preview: true, + min_width: 600.0, + max_width: Some(640.0), + ..DialogSpec::FORM +}; /// Side of the colour field (and height of the channel slider). const FIELD: f32 = 240.0; @@ -242,9 +251,39 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog) -> bool { d.fields.insert("swatches".into(), json!(!swatches)); } store(d, &pick.unwrap_or(p).snapped(d.bool("webOnly"))); + preview(app, d); false } +/// Show the picked colour on the selected art while the picker is open: each new colour replaces +/// the last preview. Nothing previews until the colour first changes, nor without a selection +/// (the colour then only becomes the default paint, on OK). +fn preview(app: &mut VectorcraftApp, d: &mut Dialog) { + let (cmd, params) = paint_command(app, d); + let first = !app.session.in_interaction(); + let unchanged = d.fields.get(PREVIEWED) == Some(¶ms) || first && d.fields.get("original").and_then(color_value) == Some(picked(d).color); + let selected = app.session.active().is_some_and(|st| !st.selection.objects.is_empty()); + if unchanged || !selected { + return; + } + let _ = app.session.begin_interaction(if d.bool("stroke") { "Stroke Color" } else { "Fill Color" }); + if let Err(e) = app.session.preview(cmd, ¶ms) { + app.status(e.to_string()); + } + d.fields.insert(PREVIEWED.into(), params); +} + +/// The command that applies the picked colour to the proxy, and its params (as CMYK in a CMYK +/// document). +fn paint_command(app: &VectorcraftApp, d: &Dialog) -> (&'static str, Value) { + let mut c = picked(d).color; + if matches!(c, Color::Rgb { .. }) && app.session.active().is_some_and(|s| s.doc.color_mode == ColorMode::Cmyk) { + let [cy, m, y, k] = c.to_cmyk_managed(cms::active().settings().intent); + c = Color::cmyk(cy, m, y, k); + } + (if d.bool("stroke") { "paint.setStroke" } else { "paint.setFill" }, json!({ "color": color_json(&c) })) +} + /// The New (top) and Original (bottom) chips. Returns true when Original is clicked. fn new_original_chips(ui: &mut Ui, new: &Color, original: &Color) -> bool { let t = Tokens::get(ui.ctx()); @@ -363,15 +402,10 @@ fn swatch_list(app: &VectorcraftApp, ui: &mut Ui, current: &Color) -> Option