From ff9af2628a38828016305c5e7b645bcb7fcb1575 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 4 Oct 2026 18:20:19 +0700 Subject: [PATCH] M3.29: Live Paint Bucket and Appearance item colours feed the recent colours; the last-colour/gradient commands read "Apply Last Color" / "Apply Last Gradient" (no clash with Window > Gradient); `,` and `.` keyboard test Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/appearance.rs | 3 +++ crates/engine/src/cmd/buildcmds.rs | 6 ++++-- crates/engine/src/cmd/paint.rs | 4 ++-- crates/engine/src/tests_paintproxy.rs | 15 +++++++++++++++ crates/ui-egui/src/shortcuts.rs | 14 ++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/appearance.rs b/crates/engine/src/cmd/appearance.rs index c60cdd34..051c7e6c 100644 --- a/crates/engine/src/cmd/appearance.rs +++ b/crates/engine/src/cmd/appearance.rs @@ -126,6 +126,9 @@ fn set_item(s: &mut Session, p: &Value) -> Result { } Ok(()) })?; + if let Some(pa) = &paint { + s.remember_paint(pa); + } ok() } diff --git a/crates/engine/src/cmd/buildcmds.rs b/crates/engine/src/cmd/buildcmds.rs index 9e24fe2b..48207f44 100644 --- a/crates/engine/src/cmd/buildcmds.rs +++ b/crates/engine/src/cmd/buildcmds.rs @@ -570,9 +570,10 @@ fn lp_fill(s: &mut Session, p: &Value) -> Result { }; let fid = d.node(gid).and_then(|n| face_at(n, pt)).map(|f| f.id).ok_or_else(|| EngineError::Other("no Live Paint face at that point".into()))?; - d.node_mut(fid).ok_or(EngineError::NoNode(fid))?.appearance.set_fill(paint); + d.node_mut(fid).ok_or(EngineError::NoNode(fid))?.appearance.set_fill(paint.clone()); Ok((gid, fid)) })?; + s.remember_paint(&paint); Ok(json!({ "group": g.0, "face": face.0 })) } @@ -590,12 +591,13 @@ fn lp_stroke_edge(s: &mut Session, p: &Value) -> Result { let n = d.node(g).filter(|n| is_live_paint(n)).ok_or_else(|| bad(C, format!("{g} is not a Live Paint group")))?; let eid = edge_near(n, pt, tol).map(|e| e.id).ok_or_else(|| EngineError::Other("no Live Paint edge near that point".into()))?; let e = d.node_mut(eid).ok_or(EngineError::NoNode(eid))?; - e.appearance.set_stroke(paint); + e.appearance.set_stroke(paint.clone()); if let Some(st) = e.appearance.stroke_mut() { st.width = width; } Ok(eid) })?; + s.remember_paint(&paint); Ok(json!({ "group": g.0, "edge": edge.0 })) } diff --git a/crates/engine/src/cmd/paint.rs b/crates/engine/src/cmd/paint.rs index 0a12035a..66058c45 100644 --- a/crates/engine/src/cmd/paint.rs +++ b/crates/engine/src/cmd/paint.rs @@ -80,7 +80,7 @@ pub fn proxy_specs() -> Vec { ), cmd!( "paint.lastColor", - "Color", + "Apply Last Color", [], Some(","), "{stroke?: bool (default: the active proxy), ids?} apply the last solid colour used (paint.recent lastColor) to the active proxy of the selection and the default", @@ -92,7 +92,7 @@ pub fn proxy_specs() -> Vec { ), cmd!( "paint.lastGradient", - "Gradient", + "Apply Last Gradient", [], Some("."), "{stroke?: bool (default: the active proxy), ids?} apply the last gradient used (paint.recent lastGradient), fitted to each object, to the active proxy of the selection and the default", diff --git a/crates/engine/src/tests_paintproxy.rs b/crates/engine/src/tests_paintproxy.rs index 8a9d7e73..50c83fdf 100644 --- a/crates/engine/src/tests_paintproxy.rs +++ b/crates/engine/src/tests_paintproxy.rs @@ -195,3 +195,18 @@ fn a_mixed_selection_shows_a_question_mark_proxy() { s.execute("select.none", &json!({})).unwrap(); assert_eq!(q(&mut s)["fillMixed"], false); } + +#[test] +fn live_paint_and_appearance_items_feed_recent_colours() { + let mut s = session(); + let id = rect(&mut s); + s.execute("appearance.setItem", &json!({"index": 0, "color": "#0a0b0c"})).unwrap(); + assert_eq!(hexes(&s)[0], "#0a0b0c"); + // Opacity alone doesn't change the recent colours. + s.execute("appearance.setItem", &json!({"index": 0, "opacity": 50})).unwrap(); + assert_eq!(hexes(&s).len(), 1); + s.execute("livePaint.make", &json!({"ids": [id.0]})).unwrap(); + let g = s.doc().unwrap().selection.objects[0]; + s.execute("livePaint.fill", &json!({"group": g.0, "point": [50, 30], "color": "#00ff00"})).unwrap(); + assert_eq!(hexes(&s)[0], "#00ff00"); +} diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 5b814a74..f02ffbcf 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -244,6 +244,20 @@ mod tests { assert_eq!(app.session.doc().unwrap().doc.layers[0].children().unwrap().len(), 8); } + #[test] + fn comma_and_period_reapply_the_last_colour_and_gradient() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap(); + app.session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap(); + app.session.execute("paint.setFill", &json!({"gradient": {"kind": "linear"}})).unwrap(); + app.session.execute("paint.setFill", &json!({"color": "#336699"})).unwrap(); + let fill = |app: &VectorcraftApp| crate::panels::current_paints(app).0; + frame(&mut app, vec![egui::Event::Text(".".into())]); + assert!(matches!(fill(&app), vectorcraft_color::Paint::Gradient(_)), "`.` applies the last gradient"); + frame(&mut app, vec![egui::Event::Text(",".into())]); + assert_eq!(fill(&app).color().unwrap().to_hex(), "#336699", "`,` applies the last colour"); + } + #[test] fn parses() { let s = parse("Cmd+Shift+]").unwrap();