mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Brackets: keep to the tools whose size the keys are documented for
The typed [ and ] now reach the Blob Brush, the Eraser and the Symbolism tools, whose bracket keys are in the public shortcut lists. A size of the Paintbrush's own (and path.freehand's scale) is left out until its behaviour is decided: #1661 stays open for that.
This commit is contained in:
1 parent
3f1e72d983
commit
3152436020
4 files changed
+28
-94
No files matched your search
@@ -24,7 +24,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"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<Value> {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<Action> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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("[")]);
|
||||
|
||||
Reference in new issue
Block a user