From 79c81d754ca7abedf6ad3f0a488dd2c497d27d0b Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sat, 10 Oct 2026 06:23:20 +0700 Subject: [PATCH] Fix #835: a recent colour recolours the selected gradient stop In the gradient stop popover (and the Color panel), clicking a recent colour replaced the whole gradient with that colour, while the sliders, spectrum and Black and White chips recoloured the selected stop. The recent colours now go through the Color panel's target like the chips. The Swatches panel's recent colours still paint the fill or stroke, as its swatches do. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/color.rs | 40 +++++++++++++++++++++++++-- crates/ui-egui/src/panels/mod.rs | 8 ++---- crates/ui-egui/src/panels/swatches.rs | 5 +++- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/panels/color.rs b/crates/ui-egui/src/panels/color.rs index 5b415279..e619a97e 100644 --- a/crates/ui-egui/src/panels/color.rs +++ b/crates/ui-egui/src/panels/color.rs @@ -496,7 +496,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { (Some(c), _) => components(mode, &c), (None, _) => vec![0.0; mode.labels().len()], }; - super::recent_colors_row(app, ui); + let recent = super::recent_colors_row(app, ui); widgets::divider(ui); // An edit of the active colour with its displayed components (`new`), or a colour clicked with // Alt held for the inactive proxy (`behind`). @@ -585,6 +585,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { *new = Some((c, phase, components(mode, &c))); } }; + // A recent colour, like the chips, recolours the selected gradient stop (or point). + if let Some(c) = recent { + pick(c, Live::Released, &mut new); + } // None / Black / White chips and the hex field. ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0; @@ -691,12 +695,23 @@ mod tests { /// Run the panel for one frame with `events` and `modifiers`; returns the spectrum's rect (when /// the options are shown) and the panel's height. fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec, modifiers: egui::Modifiers) -> (Option, f32) { + frame_with(app, ctx, events, modifiers, "color-spectrum") + } + + /// [`frame`] returning the rect of the panel's widget `id` instead of the spectrum's. + fn frame_with( + app: &mut VectorcraftApp, + ctx: &egui::Context, + events: Vec, + modifiers: egui::Modifiers, + id: impl std::hash::Hash + std::fmt::Debug, + ) -> (Option, f32) { let mut out = (None, 0.0); let events = std::iter::once(egui::Event::ModifiersChanged(modifiers)).chain(events).collect(); let input = egui::RawInput { events, ..Default::default() }; let mut full = ctx.run_ui(input, |ui| { show(app, ui); - out = (ctx.read_response(ui.id().with("color-spectrum")).map(|r| r.rect), ui.min_rect().height()); + out = (ctx.read_response(ui.id().with(&id)).map(|r| r.rect), ui.min_rect().height()); }); full.textures_delta.clear(); out @@ -712,6 +727,27 @@ mod tests { frame(app, ctx, vec![], egui::Modifiers::NONE); } + #[test] + fn a_recent_colour_recolours_the_selected_gradient_stop() { + // #835: it replaced the whole gradient with the colour. + let mut app = app(); + app.run("paint.setFill", json!({"color": "#ff0000"})).unwrap(); + let stops = json!([{"offset": 0, "color": "#ffffff"}, {"offset": 1, "color": "#000000"}]); + app.run("paint.setFill", json!({"gradient": {"stops": stops, "start": [10, 35], "end": [60, 35]}})).unwrap(); + app.select_tool("gradient"); + app.run("gradient.selectStop", json!({"index": 1})).unwrap(); + let red = app.session.recent_colors.iter().position(|c| c.to_hex() == "#ff0000").expect("red is a recent colour"); + let ctx = egui::Context::default(); + let none = egui::Modifiers::NONE; + let chip = frame_with(&mut app, &ctx, vec![], none, ("recent", red)).0.expect("the chip is drawn").center(); + let button = |pressed| egui::Event::PointerButton { pos: chip, button: egui::PointerButton::Primary, pressed, modifiers: none }; + frame(&mut app, &ctx, vec![egui::Event::PointerMoved(chip), button(true)], none); + frame(&mut app, &ctx, vec![button(false)], none); + let Paint::Gradient(g) = crate::panels::current_paints(&app).0 else { panic!("still a gradient") }; + let hex: Vec = g.gradient.stops.iter().map(|s| s.color.to_hex()).collect(); + assert_eq!(hex, ["#ffffff", "#ff0000"]); + } + fn paints_hex(app: &VectorcraftApp) -> (String, String) { let (f, s) = crate::panels::current_paints(app); (f.color().map(|c| c.to_hex()).unwrap_or_default(), s.color().map(|c| c.to_hex()).unwrap_or_default()) diff --git a/crates/ui-egui/src/panels/mod.rs b/crates/ui-egui/src/panels/mod.rs index 794df3b0..facb4162 100644 --- a/crates/ui-egui/src/panels/mod.rs +++ b/crates/ui-egui/src/panels/mod.rs @@ -412,8 +412,8 @@ pub(crate) fn label_or_name(s: &str, built_in: bool) -> &str { } /// "Recent Colors" header + a row of chips (the Session's recent colours, which every paint -/// command feeds); clicking one applies it to the active proxy (Alt: the inactive one). -pub(crate) fn recent_colors_row(app: &mut VectorcraftApp, ui: &mut Ui) { +/// command feeds); returns the one clicked, for the caller to apply. +pub(crate) fn recent_colors_row(app: &VectorcraftApp, ui: &mut Ui) -> Option { let t = Tokens::get(ui.ctx()); crate::widgets::subheader(ui, "Recent Colors"); let (r, _) = ui.allocate_exact_size(vec2(ui.available_width(), 22.0), Sense::hover()); @@ -430,9 +430,7 @@ pub(crate) fn recent_colors_row(app: &mut VectorcraftApp, ui: &mut Ui) { chosen = Some(*c); } } - if let Some(c) = chosen { - apply_click(app, ui, json!({"color": color_json(&c)})); - } + chosen } /// A colour as command JSON, keeping its model. diff --git a/crates/ui-egui/src/panels/swatches.rs b/crates/ui-egui/src/panels/swatches.rs index 3b59d933..cdf38e4f 100644 --- a/crates/ui-egui/src/panels/swatches.rs +++ b/crates/ui-egui/src/panels/swatches.rs @@ -642,7 +642,10 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, salt: &'static str) { } }); ui.add_space(4.0); - super::recent_colors_row(app, ui); + // A recent colour paints the active proxy (Alt: the inactive one), as a swatch does. + if let Some(c) = super::recent_colors_row(app, ui) { + super::apply_click(app, ui, json!({"color": super::color_json(&c)})); + } widgets::divider(ui); widgets::subheader(ui, tl!("Swatch Tiles")); let query = if pstate(ui.ctx(), "swatch-show-find") { widgets::search_field(ui, find_id(), tl!("Find")) } else { String::new() };