From e31e04e5453a8f04d85355bfc2eeb092c50cf875 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 28 Oct 2026 22:08:06 -0700 Subject: [PATCH] Part of #3137: Color Guide takes its base from colors chosen in Color, Swatches and the Color Picker The Color Guide kept the base color it took when the panel first showed, and only the base chip changed it. In a new document the current fill is white, so every harmony and variation was white or gray until the base chip was clicked with another current color. A color chosen in the Color panel (spectrum, sliders, fields, chips and Recent Colors), in the Swatches panel or a swatch library panel, or with OK in the Color Picker for a fill or stroke now becomes the guide's base color, and the guide shows its harmonies. A color applied from the guide itself leaves the base as it is, and so does selecting artwork. The base chip (Set base color to the current color) still makes the current color the base. A new base clears the cells selected for Save Colors as Swatches. The base moves from egui's temporary memory to UiState::color_guide_base, which is not saved, so the Color panel, the Swatches panels and the Color Picker can set it. --- .../src/dialogs/color_guide_options.rs | 2 +- crates/ui-egui/src/dialogs/color_picker.rs | 11 +- .../ui-egui/src/dialogs/color_picker/tests.rs | 24 ++++ crates/ui-egui/src/panels/color.rs | 18 ++- crates/ui-egui/src/panels/color/tests.rs | 16 +++ crates/ui-egui/src/panels/color_guide.rs | 111 +++++++++++++++--- .../ui-egui/src/panels/library_panel/tests.rs | 12 ++ crates/ui-egui/src/panels/swatches.rs | 8 +- crates/ui-egui/src/panels/swatches/body.rs | 4 +- .../ui-egui/src/panels/swatches/libraries.rs | 6 +- crates/ui-egui/src/panels/swatches/tests.rs | 28 +++++ crates/ui-egui/src/state.rs | 6 + 12 files changed, 219 insertions(+), 27 deletions(-) diff --git a/crates/ui-egui/src/dialogs/color_guide_options.rs b/crates/ui-egui/src/dialogs/color_guide_options.rs index faeb11e0d..1fe685e60 100644 --- a/crates/ui-egui/src/dialogs/color_guide_options.rs +++ b/crates/ui-egui/src/dialogs/color_guide_options.rs @@ -45,7 +45,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { ui.add_space(10.0); // Preview: the base colour's row of variations. let o = options(app, d); - let base = base_color(app, ui.ctx()); + let base = base_color(app); let n = o.steps as i32; let (row, _) = ui.allocate_exact_size(vec2(ui.available_width(), 22.0), Sense::hover()); let w = row.width() / (2 * n + 1) as f32; diff --git a/crates/ui-egui/src/dialogs/color_picker.rs b/crates/ui-egui/src/dialogs/color_picker.rs index eb72ae7bb..60c037597 100644 --- a/crates/ui-egui/src/dialogs/color_picker.rs +++ b/crates/ui-egui/src/dialogs/color_picker.rs @@ -3,7 +3,8 @@ //! the out-of-gamut and out-of-web warnings (click to correct); New/Original chips; an eyedropper //! button that samples a color anywhere on the screen ([`crate::screen_color`]); and a Color //! 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. +//! undo step through `paint.setFill` / `paint.setStroke` and makes the color the Color Guide's +//! base color; Cancel puts the art back. //! Opened for an artboard (Properties › Artboards › Fill), it shows the color on the artboard's //! paper live, and OK keeps it as one undo step through `artboard.setProps {index, color}`. //! Opened over Recolor Artwork (double-click a row's new color), its OK sets that row's new color @@ -636,7 +637,13 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { return Ok(Value::Null); } let (cmd, params) = paint_command(app, d); - super::form::commit_preview(app, cmd, params) + let c = in_document_model(app, picked(d).color); + let r = super::form::commit_preview(app, cmd, params)?; + // A color picked for the fill or stroke also becomes the Color Guide's base color. + if !d.fields.contains_key("artboard") { + crate::panels::color_guide::choose_base(app, c); + } + Ok(r) } #[cfg(test)] diff --git a/crates/ui-egui/src/dialogs/color_picker/tests.rs b/crates/ui-egui/src/dialogs/color_picker/tests.rs index cc650978b..4d678b106 100644 --- a/crates/ui-egui/src/dialogs/color_picker/tests.rs +++ b/crates/ui-egui/src/dialogs/color_picker/tests.rs @@ -275,6 +275,30 @@ fn open_set_hex_and_confirm_fills() { assert_eq!(crate::panels::current_paints(&app).0.color(), Some(Color::cmyk(0.0, 1.0, 1.0, 0.0))); } +/// OK makes the picked fill or stroke color the Color Guide's base color (#3137); Cancel and a +/// color picked for an artboard leave the base as it is. +#[test] +fn ok_makes_the_picked_color_the_color_guides_base() { + let mut app = app(); + let base = |app: &VectorcraftApp| crate::panels::color_guide::base_color(app).to_hex(); + app.ui.color_guide_base = Some(Color::WHITE); + app.run("ui.colorPicker", json!({})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("hex".into(), json!("2050a0")); + super::super::confirm(&mut app).unwrap(); + assert_eq!(base(&app), "#2050a0"); + app.run("ui.colorPicker", json!({"stroke": true})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("hex".into(), json!("a02050")); + frame(&mut app); + super::super::cancel(&mut app); + assert_eq!(base(&app), "#2050a0", "Cancel keeps the base"); + app.run("select.set", json!({"ids": []})).unwrap(); + app.run("artboard.new", json!({})).unwrap(); + app.run("ui.colorPicker", json!({"artboard": 1})).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("hex".into(), json!("00ff00")); + super::super::confirm(&mut app).unwrap(); + assert_eq!(base(&app), "#2050a0", "an artboard's color leaves the base"); +} + /// #1692: Tab goes through one color model's fields before the next: H, S, B, then R, G, B, the /// hex field, then L, a, b, then C, M, Y, K. Each Lab or CMYK field is level with the field on /// its left. diff --git a/crates/ui-egui/src/panels/color.rs b/crates/ui-egui/src/panels/color.rs index 8ef401c6f..f9567e988 100644 --- a/crates/ui-egui/src/panels/color.rs +++ b/crates/ui-egui/src/panels/color.rs @@ -432,6 +432,15 @@ fn apply(app: &mut VectorcraftApp, tgt: &Target, c: Color, phase: Live, behind: } } +/// [`apply`] a color chosen in the panel; once the pick ends, it also becomes the Color Guide's +/// base color. +fn choose(app: &mut VectorcraftApp, tgt: &Target, c: Color, phase: Live, behind: bool) { + if phase == Live::Released { + super::color_guide::choose_base(app, c); + } + apply(app, tgt, c, phase, behind); +} + /// Set the target's tint of its swatch to `t` (0..1), keeping the link (the T slider). fn apply_tint(app: &mut VectorcraftApp, tgt: &Target, tint: &Tint, t: f32, phase: Live) { match tgt { @@ -580,7 +589,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { }) .inner; match hit { - Some(SpectrumHit::Pick(c, phase)) => apply(app, &tgt, c, phase, alt), + Some(SpectrumHit::Pick(c, phase)) => choose(app, &tgt, c, phase, alt), Some(SpectrumHit::Cycle) => set_mode(app, &tgt, mode.next()), Some(SpectrumHit::Recolor { invert }) => recolor(app, ui, &tgt, invert, alt), None => {} @@ -718,15 +727,18 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { None => {} } if let Some((c, phase)) = behind { - apply(app, &tgt, c, phase, true); + choose(app, &tgt, c, phase, true); } if let (Some((t, phase)), Some(tint)) = (tinted, tgt.tint()) { + if phase == Live::Released { + super::color_guide::choose_base(app, tint.base.tinted(t)); + } apply_tint(app, &tgt, tint, t, phase); } if let Some((c, phase, comps2)) = new { let key = (c.to_hex(), mode as u8); set_pstate(ui.ctx(), "color-comps", Some((key, comps2))); - apply(app, &tgt, c, phase, false); + choose(app, &tgt, c, phase, false); } } diff --git a/crates/ui-egui/src/panels/color/tests.rs b/crates/ui-egui/src/panels/color/tests.rs index 20485020e..548851e6b 100644 --- a/crates/ui-egui/src/panels/color/tests.rs +++ b/crates/ui-egui/src/panels/color/tests.rs @@ -318,6 +318,22 @@ fn alt_click_on_the_spectrum_paints_the_stroke() { assert_eq!(paints_hex(&app).0, "#ff0000"); } +/// A color picked in the panel, for either proxy, becomes the Color Guide's base color (#3137). +#[test] +fn a_picked_color_becomes_the_color_guides_base() { + let mut app = app(); + let ctx = egui::Context::default(); + app.run("paint.setFill", json!({"color": "#ffffff"})).unwrap(); + app.ui.color_guide_base = Some(Color::WHITE); + click_spectrum(&mut app, &ctx, (0.0, 0.5), egui::Modifiers::NONE); + assert_eq!(crate::panels::color_guide::base_color(&app).to_hex(), "#ff0000"); + // Alt: the stroke takes it, and it is still the base. + click_spectrum(&mut app, &ctx, (0.5, 0.5), egui::Modifiers::ALT); + let stroke = paints_hex(&app).1; + assert_ne!(stroke, "#ff0000"); + assert_eq!(crate::panels::color_guide::base_color(&app).to_hex(), stroke); +} + #[test] fn shift_click_on_the_spectrum_cycles_modes() { let mut app = app(); diff --git a/crates/ui-egui/src/panels/color_guide.rs b/crates/ui-egui/src/panels/color_guide.rs index aa19103ce..28534e443 100644 --- a/crates/ui-egui/src/panels/color_guide.rs +++ b/crates/ui-egui/src/panels/color_guide.rs @@ -1,8 +1,11 @@ //! Color Guide panel: the base colour, the harmony-rule dropdown with the harmony strip, and the //! variation grid: a row per harmony colour with the colour itself in the centre column, //! shades/cool/muted to the left and tints/warm/vivid to the right (steps and reach from Color -//! Guide Options). The base colour stays put until "Set base color" takes the current colour. A -//! click on a cell applies it to the active proxy and selects it; Shift- or Cmd/Ctrl-click adds +//! Guide Options). The base color is the color last chosen in the Color panel, the Swatches panel +//! or the Color Picker ([`choose_base`]); selecting artwork leaves it as it is, and the base chip +//! ("Set base color to the current color") takes the current color, such as a cell applied from +//! the guide or the selected artwork's color. A click on a cell applies it to the active proxy, +//! without changing the base, and selects it; Shift- or Cmd/Ctrl-click adds //! cells to the selection that Save Colors as Swatches saves. Limit to Library (the library button, //! `ui.colorGuideLimit`) snaps every colour to a swatch library or the document's swatches; Edit or //! Apply Colors opens Recolor Artwork with the harmony colours, limited the same way. @@ -21,14 +24,16 @@ use crate::widgets::{self, menu_item}; /// The base colour before one is set: an orange. const DEFAULT_BASE: Color = Color::Rgb { r: 230.0 / 255.0, g: 120.0 / 255.0, b: 40.0 / 255.0 }; -/// The guide's base colour: fixed once set; the current colour when the panel first shows. -pub(crate) fn base_color(app: &VectorcraftApp, ctx: &egui::Context) -> Color { - if let Some(c) = pstate::>(ctx, "cg-base") { - return c; - } - let c = active_paint(app).color().unwrap_or(DEFAULT_BASE); - set_pstate(ctx, "cg-base", Some(c)); - c +/// The guide's base color: the one last chosen or set with the base chip; before that, the current +/// color. +pub(crate) fn base_color(app: &VectorcraftApp) -> Color { + app.ui.color_guide_base.unwrap_or_else(|| active_paint(app).color().unwrap_or(DEFAULT_BASE)) +} + +/// Make `c` the guide's base color: a color chosen in the Color panel, the Swatches panel or the +/// Color Picker. A color applied from the guide itself leaves the base as it is. +pub(crate) fn choose_base(app: &mut VectorcraftApp, c: Color) { + app.ui.color_guide_base = Some(c); } fn rule(ctx: &egui::Context) -> Harmony { @@ -59,7 +64,7 @@ struct GuideKey { /// The guide the panel shows, limited to the Limit to Library colours. fn guide(app: &VectorcraftApp, ctx: &egui::Context) -> Guide { - let (base, rule, opts) = (base_color(app, ctx), rule(ctx), app.ui.color_guide); + let (base, rule, opts) = (base_color(app), rule(ctx), app.ui.color_guide); let limit = &app.ui.color_guide_limit; if limit.is_empty() { return Guide::new(base, rule, &opts); @@ -132,6 +137,15 @@ fn save_selected(app: &mut VectorcraftApp, ctx: &egui::Context) { pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let t = crate::theme::Tokens::get(ui.ctx()); let ctx = ui.ctx().clone(); + // The current color becomes the base when the panel first shows; selecting artwork later + // leaves it as it is. + let first = base_color(app); + let set = *app.ui.color_guide_base.get_or_insert(first); + // Selected cells belong to the guide they were picked from: a new base clears them. + if pstate::>(&ctx, "cg-sel-base") != Some(set) { + set_pstate(&ctx, "cg-sel-base", Some(set)); + clear_selection(&ctx); + } let h = rule(&ctx); let g = guide(app, &ctx); let base = g.colors[0]; @@ -142,7 +156,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { if resp.on_hover_text(tl!("Set base color to the current color")).clicked() && let Some(c) = active_paint(app).color() { - set_pstate(&ctx, "cg-base", Some(c)); + app.ui.color_guide_base = Some(c); clear_selection(&ctx); } let labels: Vec<&str> = Harmony::ALL.iter().map(|h| h.label()).collect(); @@ -294,22 +308,83 @@ mod tests { assert_eq!(role_and_state(&shown, "Limit colors to swatch library: Earth Tones"), (Role::Button, Some(Toggled::True))); } + /// The first show takes the current color; a color chosen in the Color panel, Swatches or the + /// Color Picker becomes the base; selecting artwork with another color leaves it. #[test] - fn base_stays_until_set_and_the_grid_follows_the_options() { + fn a_chosen_color_becomes_the_base_and_selecting_art_keeps_it() { let mut app = app(); + let red = app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone(); + app.run("paint.setFill", json!({"color": "#ff0000"})).unwrap(); + let blue = app.run("shape.rectangle", json!({"x": 20, "y": 0, "width": 10, "height": 10})).unwrap()["id"].clone(); app.run("paint.setFill", json!({"color": "#3366cc"})).unwrap(); let ctx = egui::Context::default(); frame(&mut app, &ctx); - assert_eq!(base_color(&app, &ctx).to_hex(), "#3366cc", "the first show takes the current colour"); - // Another current colour leaves the base alone. - app.run("paint.setFill", json!({"color": "#ff0000"})).unwrap(); + assert_eq!(base_color(&app).to_hex(), "#3366cc", "the first show takes the current color"); + app.run("select.set", json!({"ids": [red]})).unwrap(); + frame(&mut app, &ctx); + assert_eq!(base_color(&app).to_hex(), "#3366cc", "selecting art keeps the base"); + choose_base(&mut app, Color::from_hex("#20a060").unwrap()); frame(&mut app, &ctx); - assert_eq!(base_color(&app, &ctx).to_hex(), "#3366cc"); app.ui.color_guide.steps = 6; frame(&mut app, &ctx); let g = guide(&app, &ctx); assert_eq!(g.grid[0].len(), 13); - assert_eq!(g.grid[0][6].to_hex(), "#3366cc", "the base sits in the centre of the first row"); + assert_eq!(g.grid[0][6].to_hex(), "#20a060", "the chosen base sits in the centre of the first row"); + app.run("select.set", json!({"ids": [blue]})).unwrap(); + frame(&mut app, &ctx); + assert_eq!(base_color(&app).to_hex(), "#20a060"); + } + + /// The rect of the panel widget `id` after one frame. + fn rect_of(app: &mut VectorcraftApp, ctx: &egui::Context, id: impl std::hash::Hash + std::fmt::Debug) -> Rect { + let mut r = None; + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| { + show(app, ui); + r = ctx.read_response(ui.scope_id().with(&id)).map(|r| r.rect); + }); + out.textures_delta.clear(); + r.expect("the widget is drawn") + } + + fn click(app: &mut VectorcraftApp, ctx: &egui::Context, at: egui::Pos2) { + let button = + |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE }; + for events in [vec![egui::Event::PointerMoved(at), button(true)], vec![button(false)]] { + let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| show(app, ui)); + out.textures_delta.clear(); + } + } + + /// A harmony color or a variation clicked in the guide paints the art without changing the + /// base; the base chip then makes the current color the base. + #[test] + fn a_guide_color_applies_without_moving_the_base_until_set_as_base() { + let mut app = app(); + app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap(); + app.run("paint.setFill", json!({"color": "#3366cc"})).unwrap(); + let ctx = egui::Context::default(); + frame(&mut app, &ctx); + let g = guide(&app, &ctx); + let fill = |app: &VectorcraftApp| crate::panels::current_paints(app).0.color().map(|c| c.to_hex()); + let at = rect_of(&mut app, &ctx, ("cg-h", 1)).center(); + click(&mut app, &ctx, at); + assert_eq!(fill(&app), Some(g.colors[1].to_hex()), "the harmony color paints the art"); + assert_eq!(base_color(&app).to_hex(), "#3366cc", "and leaves the base"); + let at = rect_of(&mut app, &ctx, ("cg-v", 0usize, 2usize)).center(); + click(&mut app, &ctx, at); + let variation = g.grid[0][2].to_hex(); + assert_eq!(fill(&app), Some(variation.clone()), "a variation paints the art"); + assert_eq!(base_color(&app).to_hex(), "#3366cc"); + assert_eq!(guide(&app, &ctx).grid, g.grid, "the guide stays as it was"); + // Set base color to the current color: the applied variation becomes the base. + let mut chip = None; + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| { + show(&mut app, ui); + chip = Some(ui.min_rect().left_top() + egui::vec2(8.0, 8.0)); + }); + out.textures_delta.clear(); + click(&mut app, &ctx, chip.expect("the panel is drawn")); + assert_eq!(base_color(&app).to_hex(), variation, "the base chip adopts the current color"); } #[test] diff --git a/crates/ui-egui/src/panels/library_panel/tests.rs b/crates/ui-egui/src/panels/library_panel/tests.rs index 9e1db24a8..f908dbd9d 100644 --- a/crates/ui-egui/src/panels/library_panel/tests.rs +++ b/crates/ui-egui/src/panels/library_panel/tests.rs @@ -70,6 +70,18 @@ fn clicking_a_library_swatch_adds_and_applies_it_in_one_step() { assert!(last_open(&app.ui).is_none()); } +/// A color clicked in a swatch library becomes the Color Guide's base color (#3137). +#[test] +fn a_clicked_library_color_becomes_the_color_guides_base() { + let mut app = app(); + let ctx = context(); + app.run("window.swatchLibrary", json!({"library": "pastels"})).unwrap(); + app.ui.color_guide_base = Some(vectorcraft_color::Color::WHITE); + click(&mut app, &ctx, "Pastel Teal", Modifiers::NONE, 0.0); + let teal = app.session.doc().unwrap().doc.swatch("Pastel Teal").and_then(|w| w.paint.color()).unwrap(); + assert_eq!(crate::panels::color_guide::base_color(&app), teal); +} + #[test] fn modifier_clicks_select_and_add_to_swatches_adds_the_selection() { let mut app = app(); diff --git a/crates/ui-egui/src/panels/swatches.rs b/crates/ui-egui/src/panels/swatches.rs index 34262e71c..2dd054f39 100644 --- a/crates/ui-egui/src/panels/swatches.rs +++ b/crates/ui-egui/src/panels/swatches.rs @@ -187,9 +187,15 @@ fn swatch_params(e: &Entry) -> Option { /// Apply a clicked swatch to the active proxy (Alt: the inactive one). An Alt-click on a color /// swatch with a gradient stop selected recolors the stop as dropping the swatch on it does, -/// and with a freeform gradient point selected it recolors the point. +/// and with a freeform gradient point selected it recolors the point. A color swatch also becomes +/// the Color Guide's base color. fn apply(app: &mut VectorcraftApp, ui: &Ui, e: &Entry) { let Some(params) = swatch_params(e) else { return }; + if let Entry::Swatch { paint, .. } = e + && let Some(c) = paint.color() + { + super::color_guide::choose_base(app, c); + } if super::alt_held(ui) && let Some(c) = drag_of(e, vec![]).color() && super::gradient::recolor_selected(app, c, ¶ms) diff --git a/crates/ui-egui/src/panels/swatches/body.rs b/crates/ui-egui/src/panels/swatches/body.rs index 7f9c5d296..9147960ba 100644 --- a/crates/ui-egui/src/panels/swatches/body.rs +++ b/crates/ui-egui/src/panels/swatches/body.rs @@ -30,8 +30,10 @@ pub(super) fn body(app: &mut VectorcraftApp, ui: &mut Ui, salt: &'static str) { } }); ui.add_space(4.0); - // A recent colour paints the active proxy (Alt: the inactive one), as a swatch does. + // A recent colour paints the active proxy (Alt: the inactive one), as a swatch does, and + // becomes the Color Guide's base color. if let Some(c) = crate::panels::recent_colors_row(app, ui) { + crate::panels::color_guide::choose_base(app, c); crate::panels::apply_click(app, ui, json!({"color": crate::panels::color_json(&c)})); } widgets::divider(ui); diff --git a/crates/ui-egui/src/panels/swatches/libraries.rs b/crates/ui-egui/src/panels/swatches/libraries.rs index 02f149987..f5b4d4fb6 100644 --- a/crates/ui-egui/src/panels/swatches/libraries.rs +++ b/crates/ui-egui/src/panels/swatches/libraries.rs @@ -91,8 +91,12 @@ impl LibraryKind for SwatchLibraries { fn describe(w: &Swatch) -> String { describe(&w.paint, w.global, w.spot) } - /// Adds the swatch and applies it to the active proxy (Alt: the inactive one), as one step. + /// Adds the swatch and applies it to the active proxy (Alt: the inactive one), as one step. A + /// color swatch also becomes the Color Guide's base color. fn click(app: &mut VectorcraftApp, ui: &Ui, id: &str, name: &str) { + if let Some(c) = swatchlib::library(&app.session, id).and_then(|(_, lib)| lib.iter().find(|w| w.name == name).and_then(|w| w.paint.color())) { + crate::panels::color_guide::choose_base(app, c); + } let alt = crate::panels::alt_held(ui); let apply = if crate::panels::proxy_cmd(app, alt) == "paint.setFill" { "fill" } else { "stroke" }; add_from_library(app, json!({"library": id, "names": [name], "apply": apply, "focus": !alt})); diff --git a/crates/ui-egui/src/panels/swatches/tests.rs b/crates/ui-egui/src/panels/swatches/tests.rs index 4175ed0cb..7d751fe9d 100644 --- a/crates/ui-egui/src/panels/swatches/tests.rs +++ b/crates/ui-egui/src/panels/swatches/tests.rs @@ -133,6 +133,34 @@ fn modifier_clicks_select_several_swatches_and_groups() { assert_eq!(selected(&ctx), ["Grays"], "a plain click on a group selects it alone"); } +/// A color swatch or a recent color clicked in the panel becomes the Color Guide's base color +/// (#3137); a modifier click only selects. +#[test] +fn a_clicked_color_swatch_becomes_the_color_guides_base() { + let mut app = app(); + let ctx = context(); + app.ui.color_guide_base = Some(Color::WHITE); + click_tile(&mut app, &ctx, "Red", Modifiers::NONE, 0.0); + let base = |app: &VectorcraftApp| crate::panels::color_guide::base_color(app).to_hex(); + assert_eq!(base(&app), "#ed1c24"); + click_tile(&mut app, &ctx, "Amber", Modifiers::SHIFT, 2.0); + assert_eq!(base(&app), "#ed1c24", "a modifier click only selects"); + // A recent color: the panel's first recent chip. + app.run("paint.setFill", json!({"color": "#2050a0"})).unwrap(); + app.ui.color_guide_base = Some(Color::WHITE); + let mut chip = None; + let screen_rect = Some(Rect::from_min_size(Pos2::ZERO, vec2(280.0, 700.0))); + let mut out = ctx.run_ui(egui::RawInput { time: Some(4.0), screen_rect, ..Default::default() }, |ui| { + show(&mut app, ui); + chip = ctx.read_response(ui.scope_id().with(("recent", 0usize))).map(|r| r.rect.center()); + }); + out.textures_delta.clear(); + let at = chip.expect("the recent chip is drawn"); + let button = |pressed| Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }; + frame(&mut app, &ctx, vec![Event::PointerMoved(at), button(true), button(false)], 5.0, show); + assert_eq!(base(&app), "#2050a0"); +} + /// The hex colors of the stops of the selection's gradient fill, and its stroke's color. fn stops_and_stroke(app: &VectorcraftApp) -> (Vec, String) { let (fill, stroke) = crate::panels::current_paints(app); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index f4e90f6d8..bfe63c699 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -483,6 +483,11 @@ pub struct UiState { /// swatches) or "" (none) (`ui.colorGuideLimit`). #[serde(default)] pub color_guide_limit: String, + /// Color Guide panel: the base color, set by a color chosen in the Color panel, the Swatches + /// panel or the Color Picker and by the panel's base chip (none: the current color). Not + /// saved. + #[serde(skip)] + pub color_guide_base: Option, /// Align panel: the Align To choice, Selection or Artboard, which Object → Align and the align /// buttons follow ([`crate::panels::align::with_align_to`]). #[serde(default, deserialize_with = "crate::panels::align::deserialize_align_to")] @@ -855,6 +860,7 @@ impl Default for UiState { library_panels: vec![], flattener_preview: Default::default(), color_guide_limit: String::new(), + color_guide_base: None, align_to: Default::default(), align_spacing: 0.0, color_mode: None,