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() };