diff --git a/crates/engine/src/cmd/draw2.rs b/crates/engine/src/cmd/draw2.rs index 4da085a3..f3258523 100644 --- a/crates/engine/src/cmd/draw2.rs +++ b/crates/engine/src/cmd/draw2.rs @@ -24,7 +24,7 @@ pub fn specs() -> Vec { "Pencil", [], None, - "{points: [[x,y]…] or [[x,y,pressure]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic brushes with Pressure variation; a point without one presses fully), fidelity?: pt (1.5), closed?, style?: \"pencil\"|\"brush\", fill?: bool, extend?: {id, end: \"start\"|\"end\"}, scale?: number (1; the Paintbrush's size, set with [ and ]: a new stroke drawn at this times the stroke weight, which stays as it is)} fit a freehand stroke → {id}", + "{points: [[x,y]…] or [[x,y,pressure]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic brushes with Pressure variation; a point without one presses fully), fidelity?: pt (1.5), closed?, style?: \"pencil\"|\"brush\", fill?: bool, extend?: {id, end: \"start\"|\"end\"}} fit a freehand stroke → {id}", has_doc, freehand ), @@ -618,10 +618,7 @@ fn freehand(s: &mut Session, p: &Value) -> Result { // A pencil path with a fill keeps a stroke of None (#1679); only one that would show nothing // gets black. A brush is its stroke. let stroke = if brush || fill.is_none() { stroke_paint(s) } else { s.paint.stroke.clone() }; - // The Paintbrush's own size scales the stroke it draws, not the weight new art takes. - let scale = f64_or(p, "scale", 1.0); - let scale = if scale.is_finite() { scale.clamp(0.1, 100.0) } else { 1.0 }; - let mut look = s.new_art_look(fill, stroke, s.paint.stroke_width.max(0.1) * scale); + let mut look = s.new_art_look(fill, stroke, s.paint.stroke_width.max(0.1)); if let Some(st) = look.appearance.stroke_mut() { st.pressure = pressure; } diff --git a/crates/engine/src/tests_brushsym.rs b/crates/engine/src/tests_brushsym.rs index a1d72838..818bda51 100644 --- a/crates/engine/src/tests_brushsym.rs +++ b/crates/engine/src/tests_brushsym.rs @@ -3,7 +3,7 @@ use serde_json::{Value, json}; use vectorcraft_doc::{Document, Node, NodeKind}; use vectorcraft_geom::Rect; -use vectorcraft_tools::{Mods, PointerEvent, PointerKind, ToolKey}; +use vectorcraft_tools::{Mods, PointerEvent, PointerKind}; use super::*; @@ -590,31 +590,3 @@ fn brushes_and_symbols_survive_vectorcraft_round_trip() { let b = s2.doc().unwrap().doc.node(NodeId(r["id"].as_u64().unwrap())).unwrap().geometric_bounds().unwrap(); assert!(close(b, Rect::new(60.0, 80.0, 140.0, 120.0), 1e-9)); } - -/// #1661: with the Paintbrush, `]` and `[` size the brush itself, a tenth a press: the stroke -/// weight new art takes stays, the selected path keeps its stroke, nothing goes on the undo list, -/// and the next stroke is drawn at the new size. -#[test] -fn paintbrush_brackets_size_the_next_stroke_and_leave_the_weight() { - let mut s = session(); - let v = ViewInfo::default(); - let selected = rect(&mut s, 0.0, 0.0, 50.0, 50.0); - s.execute("stroke.set", &json!({"weight": 3})).unwrap(); - let before = node(&s, selected); - let undo = s.doc().unwrap().history.undo.len(); - s.select_tool("paintbrush", v).unwrap(); - for _ in 0..2 { - s.tool_key(ToolKey::BracketRight, Mods::default(), v).unwrap(); - } - s.tool_key(ToolKey::BracketLeft, Mods::default(), v).unwrap(); - assert_eq!(s.paint.stroke_width, 3.0, "the weight stays"); - assert_eq!(node(&s, selected), before, "the selection keeps its stroke"); - assert_eq!(s.doc().unwrap().history.undo.len(), undo, "nothing to undo"); - for (k, x, y) in [(PointerKind::Down, 100.0, 100.0), (PointerKind::Drag, 150.0, 130.0), (PointerKind::Up, 200.0, 110.0)] { - s.pointer(&PointerEvent::new(k, x, y), v).unwrap(); - } - let drawn = s.doc().unwrap().selection.objects[0]; - assert_ne!(drawn, selected); - assert!((node(&s, drawn).appearance.stroke().unwrap().width - 3.0 * 1.1).abs() < 1e-12); - assert_eq!(s.paint.stroke_width, 3.0); -} diff --git a/crates/tools/src/draw2/gesture.rs b/crates/tools/src/draw2/gesture.rs index 30529453..1de71d7f 100644 --- a/crates/tools/src/draw2/gesture.rs +++ b/crates/tools/src/draw2/gesture.rs @@ -29,8 +29,7 @@ pub struct GestureTool { extend: Option<(NodeId, bool)>, /// Fit tolerance in points (Pencil/Paintbrush/Smooth "Fidelity"). pub fidelity: f64, - /// Brush / eraser diameter in points; the Paintbrush's size, its strokes drawn at - /// `size / PAINTBRUSH_SIZE` of the stroke weight. + /// Brush / eraser diameter in points. pub size: f64, /// Fill new pencil/brush strokes. pub fill: bool, @@ -42,11 +41,6 @@ pub struct GestureTool { snap: DrawSnap, } -/// The Paintbrush's starting size, as the Blob Brush's: strokes at the stroke weight. `[` and `]` -/// take a point off or add one, so each press draws the next stroke a tenth of the weight thinner -/// or thicker (our own step; the public references give none). -pub const PAINTBRUSH_SIZE: f64 = 10.0; - impl GestureTool { pub fn new(id: &str) -> Self { let id: &'static str = match id { @@ -68,7 +62,7 @@ impl GestureTool { began: false, extend: None, fidelity, - size: if id == "paintbrush" { PAINTBRUSH_SIZE } else { 10.0 }, + size: 10.0, fill: false, edit_within: 12.0, close_within: 15.0, @@ -153,8 +147,6 @@ impl GestureTool { }); if let Some((id, start)) = self.extend { v["extend"] = json!({"id": id.0, "end": if start { "start" } else { "end" }}); - } else if self.id == "paintbrush" && self.size != PAINTBRUSH_SIZE { - v["scale"] = json!(self.size / PAINTBRUSH_SIZE); } v } @@ -257,8 +249,9 @@ impl Tool for GestureTool { _ => vec![], } } + /// `[` and `]` are the Blob Brush's and the Eraser's: their size (#1661). fn claims_key(&self, _cx: &ToolContext, key: ToolKey) -> bool { - matches!(key, ToolKey::BracketLeft | ToolKey::BracketRight) && matches!(self.id, "paintbrush" | "blobBrush" | "eraser") + matches!(key, ToolKey::BracketLeft | ToolKey::BracketRight) && matches!(self.id, "blobBrush" | "eraser") } fn key(&mut self, _cx: &ToolContext, key: ToolKey, _m: Mods) -> Vec { match key { @@ -270,13 +263,11 @@ impl Tool for GestureTool { self.clear(); if began { vec![Action::Cancel] } else { vec![] } } - // The Paintbrush's size is its own: the stroke weight and the selection stay as they - // are, and the next stroke is drawn thinner or thicker. - ToolKey::BracketLeft if matches!(self.id, "blobBrush" | "eraser" | "paintbrush") => { + ToolKey::BracketLeft if matches!(self.id, "blobBrush" | "eraser") => { self.size = (self.size - 1.0).max(1.0); vec![] } - ToolKey::BracketRight if matches!(self.id, "blobBrush" | "eraser" | "paintbrush") => { + ToolKey::BracketRight if matches!(self.id, "blobBrush" | "eraser") => { self.size = (self.size + 1.0).min(1000.0); vec![] } @@ -309,9 +300,8 @@ impl Tool for GestureTool { } fn options(&self) -> Value { match self.id { - "pencil" => json!({"fidelity": self.fidelity, "fill": self.fill, "editWithin": self.edit_within, "closeWithin": self.close_within}), - "paintbrush" => { - json!({"fidelity": self.fidelity, "fill": self.fill, "editWithin": self.edit_within, "closeWithin": self.close_within, "size": self.size}) + "pencil" | "paintbrush" => { + json!({"fidelity": self.fidelity, "fill": self.fill, "editWithin": self.edit_within, "closeWithin": self.close_within}) } "smooth" => json!({"fidelity": self.fidelity}), "blobBrush" | "eraser" => json!({"size": self.size}), @@ -453,28 +443,4 @@ mod tests { b.key(&cx, ToolKey::BracketRight, Mods::default()); assert_eq!(b.options()["size"], 11.0); } - - /// #1661: `[` and `]` size the Paintbrush itself, a tenth of its starting size a press: - /// the next stroke is drawn that much thinner or thicker; continuing a path keeps its weight. - #[test] - fn paintbrush_brackets_size_its_next_stroke() { - let (d, _) = doc_with_rect(); - let s = Selection::default(); - let p = paint(); - let cx = cx(&d, &s, &p); - let mut t = GestureTool::new("paintbrush"); - assert!(t.claims_key(&cx, ToolKey::BracketRight) && t.freehand_params(false).get("scale").is_none()); - for _ in 0..10 { - assert!(t.key(&cx, ToolKey::BracketRight, Mods::default()).is_empty(), "no command: the weight stays"); - } - assert_eq!(t.options()["size"], 20.0); - assert_eq!(t.freehand_params(false)["scale"], json!(2.0)); - for _ in 0..40 { - t.key(&cx, ToolKey::BracketLeft, Mods::default()); - } - assert_eq!(t.options()["size"], 1.0); - t.extend = Some((NodeId(1), false)); - assert!(t.freehand_params(false).get("scale").is_none()); - assert!(!GestureTool::new("pencil").claims_key(&cx, ToolKey::BracketRight)); - } } diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 887e454c..c485b965 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -391,8 +391,8 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context) { } } } - // `[` and `]` the active tool takes (the Paintbrush's, the Blob Brush's and the - // Eraser's size, the Symbolism tools' diameter, with Shift their intensity). Read as typed, so + // `[` and `]` the active tool takes (the Blob Brush's and the Eraser's size, the Symbolism + // tools' diameter, with Shift their intensity). Read as typed, so // layouts where a bracket takes Option or AltGr count too; `{` and `}` are Shift's. if !(m.command || m.ctrl) { let typed: Vec<(String, ToolKey, bool)> = ctx.input(|i| { @@ -731,28 +731,27 @@ mod tests { assert_eq!(kind(&app), None, "another tool has its own Enter"); } - /// #1661: typed `[` and `]` reach the tool that takes them: the Paintbrush's own size, the Blob - /// Brush's (Option or AltGr brackets too), the stroke weight left as it is; other tools leave - /// them alone. + /// #1661: typed `[` and `]` reach the tool that takes them: the Blob Brush's and the Eraser's + /// size (Option or AltGr brackets too), the stroke weight left as it is; other tools leave them + /// alone. #[test] - fn brackets_size_the_paintbrush_and_the_blob_brush() { + fn brackets_size_the_blob_brush_and_the_eraser() { 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("stroke.set", &json!({"weight": 3})).unwrap(); let size = |app: &mut VectorcraftApp| app.session.tool_options()["size"].as_f64().unwrap(); let text = |t: &str| egui::Event::Text(t.into()); - app.select_tool("paintbrush"); - frame(&mut app, vec![text("]")]); - frame(&mut app, vec![text("]")]); - assert_eq!(size(&mut app), 12.0); - // Option-8 types `[` on a Turkish Mac keyboard. - let option_8 = egui::Event::Key { key: Key::Num8, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::ALT }; - frame(&mut app, vec![option_8, text("[")]); - assert_eq!(size(&mut app), 11.0, "a bracket typed with Option"); - app.select_tool("blobBrush"); - let before = size(&mut app); - frame(&mut app, vec![text("]")]); - assert_eq!(size(&mut app), before + 1.0); + for tool in ["blobBrush", "eraser"] { + app.select_tool(tool); + let before = size(&mut app); + frame(&mut app, vec![text("]")]); + frame(&mut app, vec![text("]")]); + assert_eq!(size(&mut app), before + 2.0, "{tool}"); + // Option-8 types `[` on a Turkish Mac keyboard. + let option_8 = egui::Event::Key { key: Key::Num8, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::ALT }; + frame(&mut app, vec![option_8, text("[")]); + assert_eq!(size(&mut app), before + 1.0, "{tool}: a bracket typed with Option"); + } assert_eq!(app.session.paint.stroke_width, 3.0, "the stroke weight stays"); app.select_tool("rectangle"); frame(&mut app, vec![text("[")]);