mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-24 10:16:59 -06:00
Merge PR #487: Pen: a click does what the cursor shows over an open path's end point
This commit is contained in:
+12
-12
@@ -1,6 +1,8 @@
|
||||
//! InDesign. Clicking an end point of an open path (the cursor shows it) continues that path from
|
||||
//! there; dragging from it first reshapes its direction lines. Between paths, a click on a selected
|
||||
//! path's anchor deletes it and a click on its outline adds an anchor; the cursor shows which.
|
||||
//! Pen tool (P): click for corner points, drag for smooth points, click the first point to close,
|
||||
//! Enter/Esc (or switching tools) to finish an open path. Each anchor is its own undo step.
|
||||
//! Between paths, a click on a selected path's anchor deletes it and a click on its outline adds an
|
||||
//! anchor; clicking an end point of an open path continues that path from there (dragging from it
|
||||
//! first reshapes its direction lines). The cursor shows which.
|
||||
|
||||
use designcraft_doc::{ItemId, SpreadRef};
|
||||
use designcraft_geom::{BezPath, Point};
|
||||
@@ -76,6 +78,8 @@ enum Hover {
|
||||
Delete(u64, usize, usize),
|
||||
/// Add an anchor on a selected path's outline.
|
||||
Add(ItemId),
|
||||
/// Continue subpath `.1` of open path `.0` from its last (`.2`) or first anchor.
|
||||
Continue(ItemId, usize, bool),
|
||||
}
|
||||
|
||||
impl PenTool {
|
||||
@@ -93,6 +97,9 @@ impl PenTool {
|
||||
if !self.anchors.is_empty() {
|
||||
return Hover::Place;
|
||||
}
|
||||
if let Some((id, si, at_end)) = endpoint_at(cx, p) {
|
||||
return Hover::Continue(id, si, at_end);
|
||||
}
|
||||
let ids = &cx.selection.items;
|
||||
if let Some((id, si, ai, None)) = anchor_at_in(cx, ids, p) {
|
||||
return Hover::Delete(id, si, ai);
|
||||
@@ -208,6 +215,7 @@ impl Tool for PenTool {
|
||||
self.finish();
|
||||
return out;
|
||||
}
|
||||
Hover::Continue(id, si, at_end) => return self.continue_from(cx, id, si, at_end),
|
||||
Hover::Delete(id, si, ai) => {
|
||||
return vec![Action::Exec("path.deleteAnchor".into(), json!({"id": id, "subpath": si, "anchor": ai}))];
|
||||
}
|
||||
@@ -216,12 +224,6 @@ impl Tool for PenTool {
|
||||
}
|
||||
Hover::Place => {}
|
||||
}
|
||||
// On an open path's end point: continue that path.
|
||||
if self.anchors.is_empty()
|
||||
&& let Some((id, si, at_end)) = endpoint_at(cx, ev.pos)
|
||||
{
|
||||
return self.continue_from(cx, id, si, at_end);
|
||||
}
|
||||
let p = self.snap_click(cx, snap_spread, sp, ev.mods.shift);
|
||||
if self.anchors.is_empty() {
|
||||
self.spread = Some(sr);
|
||||
@@ -301,12 +303,10 @@ impl Tool for PenTool {
|
||||
}
|
||||
|
||||
fn cursor(&self, cx: &ToolContext, p: Point, _m: Mods) -> Cursor {
|
||||
if self.anchors.is_empty() && endpoint_at(cx, p).is_some() {
|
||||
return Cursor::PenContinue;
|
||||
}
|
||||
match self.hover_at(cx, p) {
|
||||
Hover::Place => Cursor::Pen,
|
||||
Hover::Close => Cursor::PenClose,
|
||||
Hover::Continue(..) => Cursor::PenContinue,
|
||||
Hover::Delete(..) => Cursor::PenDelete,
|
||||
Hover::Add(_) => Cursor::PenAdd,
|
||||
}
|
||||
|
||||
@@ -1091,6 +1091,32 @@ fn pen_continues_an_open_path_from_either_end_point() {
|
||||
assert_eq!(t.cursor(&cx, Point::new(at(200.0, 100.0).0, at(200.0, 100.0).1), Mods::default()), Cursor::Pen);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pen_over_a_selected_open_paths_end_point_continues_it() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
let lid = d.default_layer();
|
||||
let id = ItemId(d.alloc());
|
||||
let anchors = vec![
|
||||
designcraft_geom::Anchor::corner(Point::new(100.0, 100.0)),
|
||||
designcraft_geom::Anchor::corner(Point::new(200.0, 100.0)),
|
||||
designcraft_geom::Anchor::corner(Point::new(200.0, 200.0)),
|
||||
];
|
||||
let mut it = Item::new(id, lid, Shape::Path, designcraft_geom::PathData::single(designcraft_geom::SubPath::new(anchors, false)));
|
||||
it.stroke = Stroke::default();
|
||||
d.insert_item(SpreadRef::Doc(0), it, None).unwrap();
|
||||
let (s, c, l) = (Selection::items(vec![id]), Cache::new(), CanvasLayout::new(&d, false));
|
||||
let cx = ctx(&d, &s, &c, &l);
|
||||
let off = l.xf(SpreadRef::Doc(0)).translation();
|
||||
let at = |x: f64, y: f64| Point::new(x + off.x, y + off.y);
|
||||
let mut t = create("pen");
|
||||
// The end point wins over deleting the selected path's anchor; a middle anchor still deletes.
|
||||
assert_eq!(t.cursor(&cx, at(200.0, 200.0), Mods::default()), Cursor::PenContinue);
|
||||
assert_eq!(t.cursor(&cx, at(200.0, 100.0), Mods::default()), Cursor::PenDelete);
|
||||
let p = at(200.0, 200.0);
|
||||
assert!(t.pointer(&cx, &PointerEvent::new(PointerKind::Down, p.x, p.y)).is_empty(), "no anchor is deleted");
|
||||
assert!(t.busy(), "the path is being continued");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_selection_tool_drags_a_path_type_bracket_along_its_path() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Tool cursors drawn in code: black glyphs with a white halo, so they read on any page colour.
|
||||
//! The Selection tool's filled arrow, Direct Selection's hollow arrow, the pen nib with a badge for
|
||||
//! what a click does (add, delete, close, convert), and a curved double arrow for rotating.
|
||||
//! what a click does (add, delete, close, convert, continue), and a curved double arrow for rotating.
|
||||
//! Cursors without a glyph stay system cursors.
|
||||
//!
|
||||
//! Linux and Windows show the glyphs as OS cursors ([`Images`]): the system moves them itself, so
|
||||
@@ -96,6 +96,7 @@ enum Badge {
|
||||
Delete,
|
||||
Close,
|
||||
Convert,
|
||||
Continue,
|
||||
}
|
||||
|
||||
/// The pen nib, tip at `o` (top left), with a badge below right.
|
||||
@@ -121,6 +122,7 @@ fn pen(ink: &mut Ink, o: Pos2, badge: Badge) {
|
||||
ink.line(b + vec2(0.0, 6.0), b + vec2(3.0, 0.0));
|
||||
ink.line(b + vec2(3.0, 0.0), b + vec2(6.0, 6.0));
|
||||
}
|
||||
Badge::Continue => ink.line(b + vec2(1.0, 6.0), b + vec2(5.0, 0.0)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +154,7 @@ fn glyph(c: Cursor, p: Pos2) -> Option<Vec<Shape>> {
|
||||
Cursor::PenDelete => pen(&mut ink, p, Badge::Delete),
|
||||
Cursor::PenClose => pen(&mut ink, p, Badge::Close),
|
||||
Cursor::PenConvert => pen(&mut ink, p, Badge::Convert),
|
||||
Cursor::PenContinue => pen(&mut ink, p, Badge::Continue),
|
||||
Cursor::Rotate => rotate(&mut ink, p),
|
||||
_ => return None,
|
||||
}
|
||||
@@ -321,8 +324,17 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Every cursor with a glyph.
|
||||
const GLYPHS: [Cursor; 8] =
|
||||
[Cursor::Arrow, Cursor::ArrowHollow, Cursor::Pen, Cursor::PenAdd, Cursor::PenDelete, Cursor::PenClose, Cursor::PenConvert, Cursor::Rotate];
|
||||
const GLYPHS: [Cursor; 9] = [
|
||||
Cursor::Arrow,
|
||||
Cursor::ArrowHollow,
|
||||
Cursor::Pen,
|
||||
Cursor::PenAdd,
|
||||
Cursor::PenDelete,
|
||||
Cursor::PenClose,
|
||||
Cursor::PenConvert,
|
||||
Cursor::PenContinue,
|
||||
Cursor::Rotate,
|
||||
];
|
||||
|
||||
fn alpha(img: &CustomCursorImage, x: usize, y: usize) -> u8 {
|
||||
img.rgba.get((y * img.size[0] as usize + x) * 4 + 3).copied().unwrap_or(0)
|
||||
|
||||
Reference in New Issue
Block a user