mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 21:27:00 -06:00
Merge pull request #906 from storytold/feat/852-pressure-brushes
Part of #852: Brush Options for Calligraphic and Bristle brushes, pen pressure
This commit is contained in:
@@ -5,7 +5,9 @@
|
||||
//! and commit on release; Alt on release (or ending near the start) closes the path, and starting
|
||||
//! near an end of a selected open path continues it. The Pencil's new path starts and ends on
|
||||
//! Smart Guides ([`DrawSnap`]: an anchor, a path, in line with the art), hovering too; the points
|
||||
//! between follow the hand. The other tools collect the drag polyline (shown as an overlay) and run
|
||||
//! between follow the hand. The Paintbrush records the pen pressure of each point (a stroke a pen
|
||||
//! pressed less than fully on sends `[x, y, pressure]` points) for pressure-sensitive brushes. The
|
||||
//! other tools collect the drag polyline (shown as an overlay) and run
|
||||
//! one command on release.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
@@ -19,6 +21,8 @@ use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, Tool
|
||||
pub struct GestureTool {
|
||||
id: &'static str,
|
||||
points: Vec<Point>,
|
||||
/// The pen pressure of each of `points` (the Paintbrush only).
|
||||
pressures: Vec<f32>,
|
||||
active: bool,
|
||||
began: bool,
|
||||
/// Pencil/Paintbrush continuing a selected open path: (path, continue from its start).
|
||||
@@ -53,6 +57,7 @@ impl GestureTool {
|
||||
Self {
|
||||
id,
|
||||
points: vec![],
|
||||
pressures: vec![],
|
||||
active: false,
|
||||
began: false,
|
||||
extend: None,
|
||||
@@ -74,6 +79,30 @@ impl GestureTool {
|
||||
matches!(self.id, "pencil" | "paintbrush")
|
||||
}
|
||||
|
||||
/// Add a stroke sample.
|
||||
fn push(&mut self, p: Point, pressure: f32) {
|
||||
self.points.push(p);
|
||||
if self.id == "paintbrush" {
|
||||
self.pressures.push(pressure);
|
||||
}
|
||||
}
|
||||
|
||||
/// Forget the stroke.
|
||||
fn clear(&mut self) {
|
||||
self.points.clear();
|
||||
self.pressures.clear();
|
||||
}
|
||||
|
||||
/// The stroke's points, each `[x, y, pressure]` when a pen pressed less than fully anywhere
|
||||
/// along it (a mouse always presses fully).
|
||||
fn stroke_json(&self) -> Value {
|
||||
if self.pressures.len() == self.points.len() && self.pressures.iter().any(|p| *p < 1.0) {
|
||||
Value::Array(self.points.iter().zip(&self.pressures).map(|(p, f)| json!([p.x, p.y, (f64::from(*f) * 1000.0).round() / 1000.0])).collect())
|
||||
} else {
|
||||
points_json(&self.points)
|
||||
}
|
||||
}
|
||||
|
||||
fn label(&self) -> &'static str {
|
||||
match self.id {
|
||||
"paintbrush" => "Paintbrush",
|
||||
@@ -110,7 +139,7 @@ impl GestureTool {
|
||||
/// Parameters of `path.freehand` for the current stroke.
|
||||
pub fn freehand_params(&self, closed: bool) -> Value {
|
||||
let mut v = json!({
|
||||
"points": points_json(&self.points),
|
||||
"points": self.stroke_json(),
|
||||
"fidelity": self.fidelity,
|
||||
"closed": closed,
|
||||
"style": if self.id == "paintbrush" { "brush" } else { "pencil" },
|
||||
@@ -168,7 +197,8 @@ impl Tool for GestureTool {
|
||||
self.extend = if self.draws_path() { self.find_extend(cx, p) } else { None };
|
||||
// A path continued starts at its end; a new one where it snaps.
|
||||
let start = if self.snaps() && self.extend.is_none() { self.snap.press(cx, p, &[], None) } else { p };
|
||||
self.points = vec![start];
|
||||
self.clear();
|
||||
self.push(start, ev.pressure);
|
||||
vec![]
|
||||
}
|
||||
PointerKind::Drag => {
|
||||
@@ -182,7 +212,7 @@ impl Tool for GestureTool {
|
||||
if self.points.last().is_some_and(|l| l.distance(p) < cx.tol(1.0)) {
|
||||
return vec![];
|
||||
}
|
||||
self.points.push(p);
|
||||
self.push(p, ev.pressure);
|
||||
if !self.draws_path() {
|
||||
return vec![];
|
||||
}
|
||||
@@ -201,7 +231,7 @@ impl Tool for GestureTool {
|
||||
let end = if self.snaps() { self.snap.drag(cx, p, None) } else { p };
|
||||
self.snap.clear();
|
||||
if self.points.last().is_some_and(|l| l.distance(p) >= cx.tol(1.0)) {
|
||||
self.points.push(end);
|
||||
self.push(end, ev.pressure);
|
||||
} else if self.snaps()
|
||||
&& self.points.len() > 1
|
||||
&& let Some(last) = self.points.last_mut()
|
||||
@@ -212,7 +242,7 @@ impl Tool for GestureTool {
|
||||
let out = self.finish(cx, ev.mods);
|
||||
self.active = false;
|
||||
self.began = false;
|
||||
self.points.clear();
|
||||
self.clear();
|
||||
self.extend = None;
|
||||
out
|
||||
}
|
||||
@@ -226,7 +256,7 @@ impl Tool for GestureTool {
|
||||
self.snap.clear();
|
||||
self.active = false;
|
||||
self.began = false;
|
||||
self.points.clear();
|
||||
self.clear();
|
||||
if began { vec![Action::Cancel] } else { vec![] }
|
||||
}
|
||||
ToolKey::BracketLeft if matches!(self.id, "blobBrush" | "eraser") => {
|
||||
@@ -245,7 +275,7 @@ impl Tool for GestureTool {
|
||||
self.snap.clear();
|
||||
self.active = false;
|
||||
self.began = false;
|
||||
self.points.clear();
|
||||
self.clear();
|
||||
if began { vec![Action::Commit] } else { vec![] }
|
||||
}
|
||||
fn overlays(&self, cx: &ToolContext) -> Vec<Overlay> {
|
||||
@@ -321,6 +351,29 @@ mod tests {
|
||||
assert_eq!(v["points"][0], json!([102.0, 98.0]));
|
||||
}
|
||||
|
||||
/// #852: the Paintbrush sends each point's pen pressure; a mouse (always fully pressed) and
|
||||
/// the Pencil send plain points.
|
||||
#[test]
|
||||
fn paintbrush_sends_pen_pressure() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let s = Selection::default();
|
||||
let p = paint();
|
||||
let cx = cx(&d, &s, &p);
|
||||
let stroke = |id: &str, pressures: [f32; 3]| {
|
||||
let mut t = GestureTool::new(id);
|
||||
let kinds = [PointerKind::Down, PointerKind::Drag, PointerKind::Up];
|
||||
let mut last = vec![];
|
||||
for ((k, x), f) in kinds.into_iter().zip([0.0, 40.0, 80.0]).zip(pressures) {
|
||||
last = t.pointer(&cx, &PointerEvent { pressure: f, ..PointerEvent::new(k, x, 0.0) });
|
||||
}
|
||||
let Some(Action::Preview(_, v)) = last.first() else { panic!("{last:?}") };
|
||||
v["points"].clone()
|
||||
};
|
||||
assert_eq!(stroke("paintbrush", [0.25, 0.5, 1.0]), json!([[0.0, 0.0, 0.25], [40.0, 0.0, 0.5], [80.0, 0.0, 1.0]]));
|
||||
assert_eq!(stroke("paintbrush", [1.0; 3]), json!([[0.0, 0.0], [40.0, 0.0], [80.0, 0.0]]));
|
||||
assert_eq!(stroke("pencil", [0.25, 0.5, 1.0]), json!([[0.0, 0.0], [40.0, 0.0], [80.0, 0.0]]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pencil_previews_and_commits() {
|
||||
let (d, _) = doc_with_rect();
|
||||
|
||||
Reference in New Issue
Block a user