mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 12:17:14 -06:00
Shaper: the Selection tool moves a selected original shape while a mask or pattern is edited
While an opacity mask or a pattern was edited, a Shaper double-click selected a Shaper Group's original shape without isolating the group's original shapes. A Selection tool press on that shape then selected and moved the whole Shaper Group over the art the group shows, and started a marquee over a part of the shape the group does not show. A press on the selected original shape now holds it for a move, and the pointer shows the move cursor over it.
This commit is contained in:
@@ -462,6 +462,20 @@ pub(crate) fn hold_point(cx: &ToolContext, h: &Hit, p: Point) -> Point {
|
||||
cx.doc.node(h.leaf).and_then(|n| n.path_data()).and_then(|pd| hold_on_path(pd, p, reach)).unwrap_or(p)
|
||||
}
|
||||
|
||||
/// While an opacity mask or a pattern is edited, a Shaper double-click selects a Shaper Group's
|
||||
/// original shape without isolating the group's original shapes. A press at `p` on a selected
|
||||
/// original shape holds it for a move: the hit inside its Shaper Group's original shapes, and that
|
||||
/// container. None otherwise.
|
||||
fn selected_source_hit(cx: &ToolContext, p: Point) -> Option<(Hit, NodeId)> {
|
||||
if cx.doc.mask_edit.is_none() && cx.doc.pattern_edit.is_none() {
|
||||
return None;
|
||||
}
|
||||
let first = *cx.selection.objects.first()?;
|
||||
let sources = cx.doc.parent_of(first).filter(|g| cx.doc.node(*g).is_some_and(|n| n.name.as_deref() == Some(vectorcraft_doc::shaper::SOURCES)))?;
|
||||
let h = hit_test(cx.doc, p, vectorcraft_doc::hit::HitOptions { scope: Some(sources), ..cx.selection_hit_options() })?;
|
||||
cx.selection.contains(h.top_object(Some(sources))).then_some((h, sources))
|
||||
}
|
||||
|
||||
impl SelectionTool {
|
||||
fn drag_threshold(cx: &ToolContext) -> f64 {
|
||||
cx.tol(3.0)
|
||||
@@ -675,9 +689,13 @@ impl Tool for SelectionTool {
|
||||
self.state = State::Moving { start: p, press: p, hold: p, began: false, deselect: None, key: None };
|
||||
return vec![Action::Exec("select.set".into(), json!({ "ids": [behind.0] }))];
|
||||
}
|
||||
match hit_test(cx.doc, p, cx.selection_hit_options()) {
|
||||
let (hit, scope) = match selected_source_hit(cx, p) {
|
||||
Some((h, sources)) => (Some(h), Some(sources)),
|
||||
None => (hit_test(cx.doc, p, cx.selection_hit_options()), cx.isolation),
|
||||
};
|
||||
match hit {
|
||||
Some(h) => {
|
||||
let top = h.top_object(cx.isolation);
|
||||
let top = h.top_object(scope);
|
||||
let mut out = vec![];
|
||||
let (mut deselect, mut key) = (None, None);
|
||||
if m.shift {
|
||||
@@ -896,6 +914,9 @@ impl Tool for SelectionTool {
|
||||
if cx.snap_to_point && matches!(Self::hover_point(cx, p), Some((_, "anchor"))) {
|
||||
return Cursor::ArrowHollow;
|
||||
}
|
||||
if selected_source_hit(cx, p).is_some() {
|
||||
return Cursor::Move;
|
||||
}
|
||||
if let Some(h) = hit_test(cx.doc, p, cx.selection_hit_options()) {
|
||||
if cx.selection.contains(h.top_object(cx.isolation)) || m.alt {
|
||||
return Cursor::Move;
|
||||
|
||||
@@ -2,6 +2,35 @@ use super::*;
|
||||
use crate::testutil::*;
|
||||
use vectorcraft_doc::Selection;
|
||||
|
||||
/// While an opacity mask is edited, a press on a selected Shaper original shape moves it, over the
|
||||
/// art the group shows and over a part of the shape the group does not show. A press on an original
|
||||
/// shape that is not selected selects the Shaper Group or starts a marquee, as before.
|
||||
#[test]
|
||||
fn a_press_on_a_selected_shaper_source_moves_it_while_a_mask_is_edited() {
|
||||
let (mut d, g, sources, _) = doc_with_shaper_group();
|
||||
let rect = d.layers[0].children().unwrap()[0].id;
|
||||
let layer = d.layers[0].id;
|
||||
d.mask_edit = Some(vectorcraft_doc::MaskEdit { object: rect, layer });
|
||||
let ids: Vec<NodeId> = d.node(sources).unwrap().children().unwrap().iter().map(|n| n.id).collect();
|
||||
let (mut s, p) = (Selection::default(), paint());
|
||||
s.set([ids[1]]);
|
||||
let c = ToolContext { isolation: Some(layer), ..cx(&d, &s, &p) };
|
||||
for x in [75.0, 125.0] {
|
||||
let mut t = SelectionTool::default();
|
||||
assert_eq!(t.cursor(&c, Point::new(x, 350.0), Mods::default()), Cursor::Move, "at {x}");
|
||||
let down = t.pointer(&c, &ev(PointerKind::Down, x, 350.0));
|
||||
assert!(!down.iter().any(|a| matches!(a, Action::Exec(c, _) if c == "select.set")), "at {x}: {down:?}");
|
||||
let drag = t.pointer(&c, &ev(PointerKind::Drag, x + 20.0, 350.0));
|
||||
assert!(drag.iter().any(|a| matches!(a, Action::Preview(c, _) if c == "object.transform")), "at {x}: {drag:?}");
|
||||
}
|
||||
s.set([ids[0]]);
|
||||
let c = ToolContext { isolation: Some(layer), ..cx(&d, &s, &p) };
|
||||
let down = SelectionTool::default().pointer(&c, &ev(PointerKind::Down, 125.0, 350.0));
|
||||
assert!(down.is_empty(), "{down:?}");
|
||||
let down = SelectionTool::default().pointer(&c, &ev(PointerKind::Down, 75.0, 350.0));
|
||||
assert!(down.iter().any(|a| matches!(a, Action::Exec(c, v) if c == "select.set" && v["ids"] == json!([g.0]))), "{down:?}");
|
||||
}
|
||||
|
||||
fn ev(kind: PointerKind, x: f64, y: f64) -> PointerEvent {
|
||||
PointerEvent::new(kind, x, y)
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ Smart Guides and Snap to Point do not snap to the original shapes. The reference
|
||||
|
||||
Object › Shaper › Release restores the original paths. Expand keeps the visible result as ordinary paths. Each scribble and source edit records one undo step. Sources in a composition must share a parent layer/group; Shaper limits a composition to 64 source paths and 4,096 segments.
|
||||
|
||||
While an opacity mask or a pattern is edited, its editing layer stays isolated and the arrow widget does not show. A double-click with Shaper on a Shaper Group in the mask or pattern art selects the original shape under the pointer without isolating the group's original shapes, and a scribble, Release and Expand leave the editing layer isolated.
|
||||
While an opacity mask or a pattern is edited, its editing layer stays isolated and the arrow widget does not show. A double-click with Shaper on a Shaper Group in the mask or pattern art selects the original shape under the pointer without isolating the group's original shapes, the Selection tool moves that selected shape, and a scribble, Release and Expand leave the editing layer isolated.
|
||||
|
||||
Finer gesture heuristics remain to be implemented.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user