mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 03:48:01 +00:00
Merge pull request #5371 from Silvertree2010/fix/5351-gradient-mesh-object-disappears
Lasso picks gradient mesh points instead of dropping the mesh
This commit is contained in:
@@ -697,8 +697,8 @@ fn black_or_white(c: &Color) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// The points the active tool has picked (its `meshPoints` option: the Mesh tool's or Direct
|
||||
/// Selection's) on the gradient meshes among `ids`, in range, each mesh with its points.
|
||||
/// The points the active tool has picked (its `meshPoints` option: the Mesh tool's, Direct
|
||||
/// Selection's or the Lasso's) on the gradient meshes among `ids`, in range, each mesh with its points.
|
||||
fn picked_mesh_points(s: &Session, ids: &[NodeId]) -> Vec<(NodeId, Vec<usize>)> {
|
||||
let o = s.tool_options();
|
||||
let (Some(picked), Ok(st)) = (o.get("meshPoints").and_then(Value::as_array), s.doc()) else { return vec![] };
|
||||
|
||||
@@ -976,6 +976,27 @@ fn lasso_selects_anchor_subset() {
|
||||
assert_eq!(st.selection.partial(a).map(|p| p.len()), Some(2));
|
||||
}
|
||||
|
||||
/// #5351: a Lasso loop round a gradient mesh's points keeps the mesh selected and picks the
|
||||
/// points inside it, so a solid fill colour recolours just those points.
|
||||
#[test]
|
||||
fn lasso_picks_gradient_mesh_points() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 100.0, 100.0, 100.0, 100.0);
|
||||
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
|
||||
s.execute("object.mesh.create", &json!({"rows": 2, "cols": 2})).unwrap();
|
||||
// Round the top row of points (100,100), (150,100) and (200,100).
|
||||
gesture(&mut s, "lasso", &[(90.0, 90.0), (210.0, 90.0), (210.0, 110.0), (90.0, 110.0), (90.0, 110.0)], Mods::default());
|
||||
let st = s.doc().unwrap();
|
||||
let Some(NodeKind::Mesh(m)) = st.doc.node(a).map(|n| &n.kind) else { panic!("still a gradient mesh") };
|
||||
assert_eq!(m.points.len(), 9);
|
||||
assert_eq!(st.selection.objects, vec![a], "the mesh stays selected");
|
||||
s.execute("paint.setFill", &json!({"color": "#ff0000"})).unwrap();
|
||||
let Some(NodeKind::Mesh(m)) = s.doc().unwrap().doc.node(a).map(|n| &n.kind) else { panic!("still a gradient mesh") };
|
||||
let red: Vec<usize> = (0..m.points.len()).filter(|i| m.points[*i].color.to_hex() == "#ff0000").collect();
|
||||
let top: Vec<usize> = (0..3).map(|c| m.idx(0, c)).collect();
|
||||
assert_eq!(red, top, "only the points inside the loop are recoloured");
|
||||
}
|
||||
|
||||
/// Shift-drag a marquee with the Selection tool (#483): the selected objects it reaches are
|
||||
/// deselected and the others selected, the rest of the selection staying as it was.
|
||||
#[test]
|
||||
|
||||
@@ -257,6 +257,27 @@ impl MeshEdit {
|
||||
meshes.into_iter().map(|id| (id, self.picked.range((id, 0)..=(id, usize::MAX)).next().is_some())).collect()
|
||||
}
|
||||
|
||||
/// A Lasso's picks: the points `inside` it are picked alone, or added to the picked points
|
||||
/// (`add`), or taken away from them (`sub`). Returns each mesh with points inside and whether
|
||||
/// any of its points are picked now.
|
||||
pub fn lasso(&mut self, inside: &[(NodeId, usize)], add: bool, sub: bool) -> Vec<(NodeId, bool)> {
|
||||
if !add && !sub {
|
||||
return self.marquee(inside, false);
|
||||
}
|
||||
for k in inside {
|
||||
if sub {
|
||||
self.picked.remove(k);
|
||||
} else {
|
||||
self.picked.insert(*k);
|
||||
}
|
||||
}
|
||||
self.click = None;
|
||||
self.focus = self.focus.filter(|f| self.picked.contains(f));
|
||||
let mut meshes: Vec<NodeId> = inside.iter().map(|(id, _)| *id).collect();
|
||||
meshes.dedup();
|
||||
meshes.into_iter().map(|id| (id, self.picked.range((id, 0)..=(id, usize::MAX)).next().is_some())).collect()
|
||||
}
|
||||
|
||||
/// An arrow key's nudge of the picked points of the meshes `shown` accepts
|
||||
/// (`object.mesh.movePoints`); `None` during a drag, for other keys or without picked points.
|
||||
pub fn nudge(&self, cx: &ToolContext, key: ToolKey, mods: Mods, shown: &dyn Fn(NodeId) -> bool) -> Option<Vec<Action>> {
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
//! Magic Wand: click an object to select every object with similar attributes. The matching and
|
||||
//! its settings (Magic Wand panel) live in the engine's `select.magicWand` command; Shift adds,
|
||||
//! Alt subtracts. Lasso: freehand loop selecting anchor points inside it (direct-selection
|
||||
//! style); Shift adds, Alt subtracts.
|
||||
//! style), and picking the gradient mesh points inside it with their meshes selected (the
|
||||
//! tool's `meshPoints` option, which a solid fill colour recolours); Shift adds, Alt subtracts.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
@@ -13,6 +14,7 @@ use vectorcraft_doc::{AnchorRef, NodeId, NodeKind};
|
||||
use vectorcraft_geom::Point;
|
||||
|
||||
use super::paint_owner;
|
||||
use crate::meshedit::{MeshEdit, grid_of};
|
||||
use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -67,6 +69,8 @@ pub fn point_in_polygon(poly: &[Point], p: Point) -> bool {
|
||||
pub struct LassoTool {
|
||||
points: Vec<Point>,
|
||||
active: bool,
|
||||
/// The picked gradient mesh points.
|
||||
mesh: MeshEdit,
|
||||
}
|
||||
|
||||
fn anchors_json(v: &BTreeSet<AnchorRef>) -> Value {
|
||||
@@ -105,7 +109,11 @@ impl Tool for LassoTool {
|
||||
}
|
||||
// Anchors inside the loop for every editable path the Lasso may pick.
|
||||
let mut hits: Vec<(NodeId, BTreeSet<AnchorRef>)> = vec![];
|
||||
let mut inside: Vec<(NodeId, usize)> = vec![];
|
||||
crate::draw2::walk_pickable(cx, |n| {
|
||||
if let Some(grid) = grid_of(n) {
|
||||
inside.extend(grid.points.iter().enumerate().filter(|(_, q)| point_in_polygon(&poly, q.p)).map(|(i, _)| (n.id, i)));
|
||||
}
|
||||
if let NodeKind::Path { path, .. } = &n.kind {
|
||||
let v: BTreeSet<AnchorRef> =
|
||||
path.anchors().filter(|(_, _, a)| point_in_polygon(&poly, a.p)).map(|(s, i, _)| (s, i)).collect();
|
||||
@@ -116,6 +124,14 @@ impl Tool for LassoTool {
|
||||
});
|
||||
let editable = cx.doc.editable();
|
||||
hits.retain(|(id, _)| editable(*id) && cx.doc.is_visible(*id));
|
||||
// The mesh points inside the loop are picked, and their meshes selected whole;
|
||||
// Alt deselects a mesh only when it loses its last picked point.
|
||||
inside.retain(|(id, _)| editable(*id) && cx.doc.is_visible(*id));
|
||||
for (id, any) in self.mesh.lasso(&inside, add, sub) {
|
||||
if any != sub {
|
||||
hits.push((id, BTreeSet::new()));
|
||||
}
|
||||
}
|
||||
let items: Vec<Value> = hits.iter().map(|(id, v)| json!({ "id": id.0, "anchors": anchors_json(v) })).collect();
|
||||
let mode = if sub {
|
||||
"subtract"
|
||||
@@ -138,11 +154,16 @@ impl Tool for LassoTool {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn overlays(&self, _cx: &ToolContext) -> Vec<Overlay> {
|
||||
if !self.active || self.points.len() < 2 {
|
||||
return vec![];
|
||||
fn options(&self) -> Value {
|
||||
self.mesh.options()
|
||||
}
|
||||
|
||||
fn overlays(&self, cx: &ToolContext) -> Vec<Overlay> {
|
||||
let mut out = self.mesh.overlays(cx, &|id| cx.selection.contains(id));
|
||||
if self.active && self.points.len() >= 2 {
|
||||
out.push(Overlay::Path { path: super::polygon(&self.points, false), color: [0x40, 0x40, 0x40], width: 1.0, dashed: true });
|
||||
}
|
||||
vec![Overlay::Path { path: super::polygon(&self.points, false), color: [0x40, 0x40, 0x40], width: 1.0, dashed: true }]
|
||||
out
|
||||
}
|
||||
|
||||
fn cursor(&self, _cx: &ToolContext, _p: Point, _m: Mods) -> Cursor {
|
||||
@@ -222,4 +243,44 @@ mod tests {
|
||||
assert_eq!(v["mode"], "set");
|
||||
assert!(point_in_polygon(&[Point::new(0.0, 0.0), Point::new(10.0, 0.0), Point::new(0.0, 10.0)], Point::new(2.0, 2.0)));
|
||||
}
|
||||
|
||||
/// A Lasso loop round a corner of a path warped by a mesh envelope still selects the path's
|
||||
/// anchor there, besides picking the envelope's grid point (#5351).
|
||||
#[test]
|
||||
fn lasso_keeps_picking_the_anchors_of_an_envelopes_path() {
|
||||
use std::sync::Arc;
|
||||
use vectorcraft_doc::Node;
|
||||
use vectorcraft_doc::live::{EnvelopeKind, EnvelopeOptions, grid_points};
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
let (mut d, a) = doc_with_rect();
|
||||
let rect = d.node(a).cloned().unwrap();
|
||||
let kind = EnvelopeKind::Mesh { rows: 1, cols: 1, points: grid_points(Rect::new(100.0, 100.0, 200.0, 200.0), 1, 1), handles: vec![] };
|
||||
let env = NodeKind::Envelope {
|
||||
content: vec![Arc::new(rect)],
|
||||
kind: Box::new(kind),
|
||||
fidelity: 50.0,
|
||||
editing: false,
|
||||
options: EnvelopeOptions::NEW,
|
||||
frame: Box::new(Affine::IDENTITY),
|
||||
};
|
||||
d.remove(a).unwrap();
|
||||
let e = d.alloc_id();
|
||||
let l = d.layers[0].id;
|
||||
d.insert(Some(l), 0, Node::new(e, env)).unwrap();
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let cx = cx(&d, &s, &p);
|
||||
let mut t = LassoTool::default();
|
||||
// A triangle around the top-left corner (100,100) only.
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 80.0, 80.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 140.0, 80.0));
|
||||
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 80.0, 140.0));
|
||||
let r = t.pointer(&cx, &PointerEvent::new(PointerKind::Up, 80.0, 140.0));
|
||||
let Some(Action::Exec(c, v)) = r.first() else { panic!("a selection command: {r:?}") };
|
||||
assert_eq!(c, "select.anchorsMany");
|
||||
let items = v["items"].as_array().unwrap();
|
||||
let path = items.iter().find(|it| it["id"] == a.0).expect("the warped path's anchor is selected");
|
||||
assert_eq!(path["anchors"].as_array().unwrap().len(), 1);
|
||||
assert!(items.iter().any(|it| it["id"] == e.0), "the envelope is selected with its grid point");
|
||||
assert_eq!(t.options()["meshPoints"].as_array().unwrap().len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -6177,7 +6177,8 @@ Envelope Options.
|
||||
|
||||
The Mesh tool and Direct Selection pick mesh points on gradient meshes and mesh envelopes. A click picks one point
|
||||
(its handles show); Shift-click adds a point or takes a picked one away. Direct Selection's marquee picks the points
|
||||
inside it (Shift toggles them) and selects their meshes; its click on a gradient mesh's line picks the line's two
|
||||
inside it (Shift toggles them) and selects their meshes, as does a Lasso loop round them (Shift adds, Alt takes
|
||||
away); its click on a gradient mesh's line picks the line's two
|
||||
ends, and a click inside a patch picks the four points around it. The picked points show filled. Dragging one of them
|
||||
moves them all as one undo step (`object.mesh.movePoints`), a click on one of several picked points picks it alone,
|
||||
and the arrow keys nudge them by the keyboard increment. The active tool lists them in its options
|
||||
|
||||
Reference in New Issue
Block a user