mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 12:17:14 -06:00
Merge pull request #4415 from dotCooCoo/feat/shaper-drag-out
Shaper: dragging a shape out of a Shaper Group's bounds takes it out of the group
This commit is contained in:
@@ -15,7 +15,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Transform",
|
||||
[],
|
||||
None,
|
||||
"{matrix: [a,b,c,d,e,f], copy?: bool, ids?, strokes?: bool, corners?: bool, typeAreas?: bool, anchors?: bool, objects?: bool (Transform Objects, default true; false: the art stays and, with patterns, only its pattern tiles transform), patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles)} apply an affine to the selection (or ids); anchors (without ids or copy): paths with anchors selected with Direct Selection have only those anchors and their handles transformed, as the Transform tools and object.move/rotate/scale/reflect/shear do (default: the whole objects); strokes/corners: Scale Strokes & Effects / Scale Corners (default: the preferences); typeAreas: area type among them (not type inside a group) reshapes its frame by the matrix and its text reflows at its size, as a bounding-box handle drag does (default: the type transforms too)",
|
||||
"{matrix: [a,b,c,d,e,f], copy?: bool, ids?, strokes?: bool, corners?: bool, typeAreas?: bool, anchors?: bool, objects?: bool (Transform Objects, default true; false: the art stays and, with patterns, only its pattern tiles transform), patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles), releaseOutside?: bool} apply an affine to the selection (or ids); anchors (without ids or copy): paths with anchors selected with Direct Selection have only those anchors and their handles transformed, as the Transform tools and object.move/rotate/scale/reflect/shear do (default: the whole objects); strokes/corners: Scale Strokes & Effects / Scale Corners (default: the preferences); typeAreas: area type among them (not type inside a group) reshapes its frame by the matrix and its text reflows at its size, as a bounding-box handle drag does (default: the type transforms too); releaseOutside (without copy or anchors): a Shaper Group's original shape that ends wholly outside the bounds the group's original shapes had before takes shaper.releaseSource in the same undo step, named Remove from Shaper Group, as the Selection tool's moves in Construction mode do",
|
||||
has_doc,
|
||||
transform
|
||||
),
|
||||
|
||||
@@ -235,13 +235,40 @@ fn resize_type_area(n: &mut Node, xf: Affine) -> bool {
|
||||
|
||||
pub(super) fn transform(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
check_switches(p, &TRANSFORM_SWITCHES, "object.transform")?;
|
||||
check_switches(p, &["anchors"], "object.transform")?;
|
||||
check_switches(p, &["anchors", "releaseOutside"], "object.transform")?;
|
||||
let m = matrix_param(p, "matrix").ok_or_else(|| bad("object.transform", "missing matrix [a,b,c,d,e,f]"))?;
|
||||
let (ids, anchors) = match ids_param(p, "ids") {
|
||||
Some(v) => (outermost(&s.doc()?.doc, v), false),
|
||||
None => (selected_roots_to_transform(s)?, bool_or(p, "anchors", false)),
|
||||
};
|
||||
apply_transform(s, "Transform", ids, m, p, anchors)
|
||||
// Shaper's Construction mode: an original shape moved wholly outside its group's bounds leaves
|
||||
// the group.
|
||||
let moved = if bool_or(p, "releaseOutside", false) && !bool_or(p, "copy", false) && !anchors {
|
||||
crate::cmd::shaper::moving_sources(s, &ids)?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
// Outside a drag and an undo group, the move and the removal are one undo step, named for the
|
||||
// removal when there is one.
|
||||
let grouped = !moved.is_empty() && s.doc()?.interaction.is_none() && s.doc()?.undo_group.is_none();
|
||||
if grouped {
|
||||
s.begin_named_undo_group(None);
|
||||
}
|
||||
let r = apply_transform(s, "Transform", ids, m, p, anchors);
|
||||
let released = match &r {
|
||||
Ok(_) if !moved.is_empty() => crate::cmd::shaper::release_outside(s, &moved),
|
||||
_ => Ok(false),
|
||||
};
|
||||
if grouped && let Some(st) = s.active_mut() {
|
||||
st.undo_group = None;
|
||||
if matches!(released, Ok(true))
|
||||
&& let Some(e) = st.history.undo.last_mut()
|
||||
{
|
||||
e.label = "Remove from Shaper Group".into();
|
||||
}
|
||||
}
|
||||
released?;
|
||||
r
|
||||
}
|
||||
|
||||
pub(super) fn move_cmd(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
@@ -53,6 +53,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
expand
|
||||
),
|
||||
cmd!(
|
||||
"shaper.releaseSource",
|
||||
"Remove from Shaper Group",
|
||||
[],
|
||||
None,
|
||||
"{source: id, group?: id (default: the Shaper Group that holds `source`)} take one original shape out of its Shaper Group: it becomes a path just above the group, keeps its look and is selected, and Construction mode ends; the group's result is computed again from the shapes left, and a group left with none is removed. In Construction mode, a Selection-tool move that ends with an original shape wholly outside the bounds the group's original shapes had does the same in the move's undo step (object.transform's `releaseOutside`) → {id}",
|
||||
has_doc,
|
||||
release_source
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -789,3 +798,105 @@ fn expand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
end_isolation(s)?;
|
||||
Ok(json!({"id": id.0}))
|
||||
}
|
||||
|
||||
/// The editable Shaper Group whose original shapes include `source`.
|
||||
fn holder(d: &Document, source: NodeId) -> Option<NodeId> {
|
||||
let sources = d.parent_of(source)?;
|
||||
let group = d.parent_of(sources)?;
|
||||
(d.node(group).and_then(source_group).is_some_and(|g| g.id == sources) && d.is_editable(source)).then_some(group)
|
||||
}
|
||||
|
||||
/// Take each of `shapes` (group, original shape) out of its Shaper Group and put it just above that
|
||||
/// group; the shapes taken out are selected, alone when `only`, else along with what was
|
||||
/// selected. [`refresh`] then computes each group's result again, or removes a group that holds no
|
||||
/// shape any more. Construction mode ends when one of those groups was in it.
|
||||
fn take_out(s: &mut Session, shapes: &[(NodeId, NodeId)], only: bool) -> Result<()> {
|
||||
let sources: Vec<NodeId> = {
|
||||
let d = &s.doc()?.doc;
|
||||
shapes.iter().filter_map(|(group, _)| d.node(*group).and_then(source_group).map(|n| n.id)).collect()
|
||||
};
|
||||
s.edit("Remove from Shaper Group", |d, sel| {
|
||||
// The topmost shape goes first, so each one put just above its group goes below the ones
|
||||
// that were above it.
|
||||
let mut order = shapes.to_vec();
|
||||
order.sort_by_key(|(_, source)| std::cmp::Reverse(d.position(*source).map_or(0, |p| p.1)));
|
||||
for (group, source) in order {
|
||||
let node = d.remove(source)?;
|
||||
let (parent, index, _) = d.position(group).ok_or(EngineError::NoNode(group))?;
|
||||
d.insert(parent, index + 1, Arc::unwrap_or_clone(node))?;
|
||||
}
|
||||
if only {
|
||||
sel.set(shapes.iter().map(|(_, source)| *source));
|
||||
}
|
||||
for &(_, source) in shapes {
|
||||
if !sel.contains(source) {
|
||||
sel.objects.push(source);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
let st = s.doc_mut()?;
|
||||
if st.isolation.is_some_and(|i| sources.contains(&i)) {
|
||||
st.isolation = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release_source(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "shaper.releaseSource";
|
||||
let source = id_param(p, "source").ok_or_else(|| bad(C, "source must be the id of a Shaper Group's original shape"))?;
|
||||
let held = holder(&s.doc()?.doc, source);
|
||||
let group = match p.get("group") {
|
||||
None | Some(Value::Null) => held,
|
||||
Some(_) => Some(id_param(p, "group").ok_or_else(|| bad(C, "group must be the id of a Shaper Group"))?),
|
||||
};
|
||||
let group = group.filter(|g| held == Some(*g)).ok_or_else(|| bad(C, "the shape is not an editable original shape of that Shaper Group"))?;
|
||||
take_out(s, &[(group, source)], true)?;
|
||||
Ok(json!({"id": source.0}))
|
||||
}
|
||||
|
||||
/// What `object.transform`'s `releaseOutside` checks once the move is made: each of `ids` that is
|
||||
/// an original shape of an editable Shaper Group, with that group and the bounds the group's
|
||||
/// original shapes have before the move.
|
||||
pub(crate) fn moving_sources(s: &Session, ids: &[NodeId]) -> Result<Vec<(NodeId, NodeId, Rect)>> {
|
||||
let d = &s.doc()?.doc;
|
||||
Ok(ids
|
||||
.iter()
|
||||
.filter_map(|&id| {
|
||||
let group = holder(d, id)?;
|
||||
Some((group, id, d.node(group).and_then(source_group).and_then(Node::geometric_bounds)?))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Do bounds `b` lie wholly outside `bounds`? Bounds that touch an edge, and flat bounds (a
|
||||
/// horizontal or vertical line's) on the same line as flat `bounds`, are not outside.
|
||||
pub(crate) fn wholly_outside(b: Rect, bounds: Rect) -> bool {
|
||||
b.x0 > bounds.x1 || b.x1 < bounds.x0 || b.y0 > bounds.y1 || b.y1 < bounds.y0
|
||||
}
|
||||
|
||||
/// Take out of their Shaper Groups the `moved` shapes ([`moving_sources`]) that now lie wholly
|
||||
/// outside the bounds their group's original shapes had before the move. Inside a drag the step
|
||||
/// is named for that. → whether any shape was taken out.
|
||||
pub(crate) fn release_outside(s: &mut Session, moved: &[(NodeId, NodeId, Rect)]) -> Result<bool> {
|
||||
let outside: Vec<(NodeId, NodeId)> = {
|
||||
let d = &s.doc()?.doc;
|
||||
moved
|
||||
.iter()
|
||||
.filter(|(_, id, before)| d.node(*id).and_then(Node::geometric_bounds).is_some_and(|b| wholly_outside(b, *before)))
|
||||
.map(|(group, id, _)| (*group, *id))
|
||||
.collect()
|
||||
};
|
||||
if outside.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
// Shapes the group can't be computed from without the ones taken out keep it as it is: the
|
||||
// edit is rolled back, and the move stays a plain move.
|
||||
if take_out(s, &outside, false).is_err() {
|
||||
return Ok(false);
|
||||
}
|
||||
if let Some(it) = &mut s.doc_mut()?.interaction {
|
||||
it.label = "Remove from Shaper Group".into();
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -611,3 +611,162 @@ fn shaper_joined_outline_keeps_its_stroke_paint_after_source_edits() {
|
||||
assert_ne!(node(&s, id).appearance.stroke_paint().label(), "#00ff00");
|
||||
}
|
||||
}
|
||||
|
||||
fn source_ids(s: &Session, g: NodeId) -> Vec<NodeId> {
|
||||
node(s, g).children().unwrap()[0].children().unwrap().iter().map(|n| n.id).collect()
|
||||
}
|
||||
/// A Selection-tool drag from `from` to `to`.
|
||||
fn drag(s: &mut Session, v: ViewInfo, from: [f64; 2], to: [f64; 2]) {
|
||||
s.pointer(&PointerEvent::new(PointerKind::Down, from[0], from[1]), v).unwrap();
|
||||
for t in [0.25, 0.5, 1.0] {
|
||||
let p = [from[0] + (to[0] - from[0]) * t, from[1] + (to[1] - from[1]) * t];
|
||||
s.pointer(&PointerEvent::new(PointerKind::Drag, p[0], p[1]), v).unwrap();
|
||||
}
|
||||
s.pointer(&PointerEvent::new(PointerKind::Up, to[0], to[1]), v).unwrap();
|
||||
}
|
||||
/// Two overlapping rectangles in a Shaper Group, in Construction mode with the left one selected
|
||||
/// and the Selection tool chosen → (left, right, group).
|
||||
fn construction(s: &mut Session, v: ViewInfo) -> (NodeId, NodeId, NodeId) {
|
||||
let a = rectangle(s, 0.0, "#ff0000");
|
||||
let b = rectangle(s, 50.0, "#0000ff");
|
||||
let g = scribble(s, &[[25.0, 20.0], [75.0, 35.0], [30.0, 50.0], [125.0, 65.0]]);
|
||||
s.execute("object.isolate", &json!({"id": g.0})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [a.0]})).unwrap();
|
||||
s.select_tool("selection", v).unwrap();
|
||||
(a, b, g)
|
||||
}
|
||||
|
||||
/// In Construction mode, a Selection-tool drag that drops an original shape wholly outside the
|
||||
/// bounds of the group's original shapes takes it out of the group, in one undo step: it becomes a
|
||||
/// path just above the group with its own look, and stays selected. Undo puts it back. With
|
||||
/// Real-time Drawing off the drop does the same.
|
||||
#[test]
|
||||
fn shaper_dragging_an_original_shape_out_of_the_bounds_takes_it_out_of_the_group() {
|
||||
for real_time in [true, false] {
|
||||
let mut s = session();
|
||||
s.prefs.real_time_drawing = real_time;
|
||||
let v = ViewInfo { smart_guides: false, ..Default::default() };
|
||||
let (a, b, g) = construction(&mut s, v);
|
||||
let look = node(&s, a).appearance.clone();
|
||||
let steps = s.doc().unwrap().history.undo.len();
|
||||
drag(&mut s, v, [25.0, 50.0], [25.0, 200.0]);
|
||||
let st = s.doc().unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![b], "real-time {real_time}");
|
||||
let (parent, index, _) = st.doc.position(a).unwrap();
|
||||
assert_eq!((parent, index), { st.doc.position(g).map(|(p, i, _)| (p, i + 1)).unwrap() });
|
||||
assert_eq!(node(&s, a).appearance, look);
|
||||
assert_eq!(node(&s, a).geometric_bounds().unwrap().y0, 150.0);
|
||||
assert_eq!(st.selection.objects, vec![a]);
|
||||
assert_eq!(st.isolation, None);
|
||||
assert_eq!(st.history.undo.len(), steps + 1);
|
||||
assert_eq!(st.history.undo.last().unwrap().label, "Remove from Shaper Group");
|
||||
// The group's result is the right rectangle alone.
|
||||
assert_eq!(fill(&s, g, 25.0, 50.0), None);
|
||||
assert_eq!(fill(&s, g, 125.0, 50.0).as_deref(), Some("#0000ff"));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![a, b]);
|
||||
assert_eq!(node(&s, a).geometric_bounds().unwrap().y0, 0.0);
|
||||
assert_eq!(fill(&s, g, 25.0, 50.0).as_deref(), Some("#ff0000"));
|
||||
}
|
||||
}
|
||||
|
||||
/// A drop that leaves the original shape across the bounds of the group's original shapes only
|
||||
/// moves it, as before.
|
||||
#[test]
|
||||
fn shaper_dragging_an_original_shape_across_the_bounds_only_moves_it() {
|
||||
let mut s = session();
|
||||
let v = ViewInfo { smart_guides: false, ..Default::default() };
|
||||
let (a, b, g) = construction(&mut s, v);
|
||||
let sources = node(&s, g).children().unwrap()[0].id;
|
||||
drag(&mut s, v, [25.0, 50.0], [25.0, 100.0]);
|
||||
let st = s.doc().unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![a, b]);
|
||||
assert_eq!(node(&s, a).geometric_bounds().unwrap().y0, 50.0);
|
||||
assert_eq!(st.isolation, Some(sources));
|
||||
assert_eq!(st.history.undo.last().unwrap().label, "Move");
|
||||
// Outside Construction mode the same drop moves the group as usual.
|
||||
s.execute("object.exitIsolation", &json!({})).unwrap();
|
||||
drag(&mut s, v, [25.0, 100.0], [25.0, 400.0]);
|
||||
assert_eq!(source_ids(&s, g), vec![a, b]);
|
||||
}
|
||||
|
||||
/// Taking the last original shape out removes the group, and the native file keeps the result.
|
||||
#[test]
|
||||
fn shaper_taking_the_last_original_shape_out_removes_the_group_and_saves() {
|
||||
let mut s = session();
|
||||
let v = ViewInfo { smart_guides: false, ..Default::default() };
|
||||
let (a, b, g) = construction(&mut s, v);
|
||||
drag(&mut s, v, [25.0, 50.0], [25.0, 200.0]);
|
||||
s.execute("object.isolate", &json!({"id": g.0})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [b.0]})).unwrap();
|
||||
drag(&mut s, v, [125.0, 50.0], [400.0, 50.0]);
|
||||
let doc = &s.doc().unwrap().doc;
|
||||
assert!(doc.node(g).is_none());
|
||||
assert_eq!(doc.parent_of(a), doc.parent_of(b));
|
||||
assert_eq!(node(&s, b).geometric_bounds().unwrap().x0, 325.0);
|
||||
let back = vectorcraft_format::load(&vectorcraft_format::save(doc, false)).unwrap();
|
||||
assert_eq!(back.layers, doc.layers);
|
||||
}
|
||||
|
||||
/// Flat bounds of horizontal or vertical lines on the line of a group's flat bounds, and bounds that
|
||||
/// touch the group's bounds, are not outside them.
|
||||
#[test]
|
||||
fn shaper_outside_test_handles_flat_and_touching_bounds() {
|
||||
use crate::cmd::shaper::wholly_outside;
|
||||
use vectorcraft_geom::Rect;
|
||||
let row = Rect::new(0.0, 150.0, 200.0, 150.0);
|
||||
assert!(!wholly_outside(Rect::new(50.0, 150.0, 120.0, 150.0), row));
|
||||
assert!(!wholly_outside(Rect::new(150.0, 150.0, 300.0, 150.0), row));
|
||||
assert!(wholly_outside(Rect::new(250.0, 150.0, 300.0, 150.0), row));
|
||||
assert!(wholly_outside(Rect::new(50.0, 160.0, 120.0, 160.0), row));
|
||||
let column = Rect::new(80.0, 0.0, 80.0, 100.0);
|
||||
assert!(!wholly_outside(Rect::new(80.0, 50.0, 80.0, 150.0), column));
|
||||
assert!(wholly_outside(Rect::new(90.0, 50.0, 90.0, 150.0), column));
|
||||
let square = Rect::new(0.0, 0.0, 100.0, 100.0);
|
||||
assert!(!wholly_outside(Rect::new(100.0, 0.0, 200.0, 100.0), square));
|
||||
assert!(wholly_outside(Rect::new(100.5, 0.0, 200.0, 100.0), square));
|
||||
}
|
||||
|
||||
/// `shaper.releaseSource` takes the named original shape out, refuses a shape of another group or
|
||||
/// none, and `object.transform`'s `releaseOutside` makes one undo step outside a drag too.
|
||||
#[test]
|
||||
fn shaper_release_source_command_and_transform_flag() {
|
||||
let mut s = session();
|
||||
let a = rectangle(&mut s, 0.0, "#ff0000");
|
||||
let b = rectangle(&mut s, 50.0, "#0000ff");
|
||||
let g = scribble(&mut s, &[[25.0, 20.0], [75.0, 35.0], [30.0, 50.0], [125.0, 65.0]]);
|
||||
let other = rectangle(&mut s, 300.0, "#00ff00");
|
||||
let untouched = s.doc().unwrap().doc.layers.clone();
|
||||
for p in [json!({"source": other.0}), json!({"source": a.0, "group": other.0}), json!({"source": "x"}), json!({})] {
|
||||
assert!(s.execute("shaper.releaseSource", &p).is_err(), "{p}");
|
||||
assert_eq!(s.doc().unwrap().doc.layers, untouched);
|
||||
}
|
||||
s.execute("shaper.releaseSource", &json!({"source": b.0, "group": g.0})).unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![a]);
|
||||
assert_eq!(s.doc().unwrap().selection.objects, vec![b]);
|
||||
assert_eq!(s.doc().unwrap().history.undo.last().unwrap().label, "Remove from Shaper Group");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
let steps = s.doc().unwrap().history.undo.len();
|
||||
s.execute("object.transform", &json!({"matrix": [1, 0, 0, 1, 0, 150], "ids": [a.0], "releaseOutside": true})).unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![b]);
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), steps + 1);
|
||||
assert_eq!(s.doc().unwrap().history.undo.last().unwrap().label, "Remove from Shaper Group");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![a, b]);
|
||||
assert_eq!(node(&s, a).geometric_bounds().unwrap().y0, 0.0);
|
||||
// With a history of one step, that step still goes back to before the move.
|
||||
s.doc_mut().unwrap().history.limit = 1;
|
||||
s.execute("object.transform", &json!({"matrix": [1, 0, 0, 1, 0, 150], "ids": [a.0], "releaseOutside": true})).unwrap();
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![a, b]);
|
||||
assert_eq!(node(&s, a).geometric_bounds().unwrap().y0, 0.0);
|
||||
// Shapes taken out together keep their stacking order, whatever order they are named in.
|
||||
for ids in [[a.0, b.0], [b.0, a.0]] {
|
||||
s.execute("object.transform", &json!({"matrix": [1, 0, 0, 1, 0, 150], "ids": ids, "releaseOutside": true})).unwrap();
|
||||
let doc = &s.doc().unwrap().doc;
|
||||
assert!(doc.node(g).is_none());
|
||||
assert!(doc.position(a).unwrap().1 < doc.position(b).unwrap().1, "{ids:?}");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(source_ids(&s, g), vec![a, b]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,9 @@ pub struct SelectionTool {
|
||||
/// What a bounding-box handle being dragged snaps to.
|
||||
targets: Option<PointSnap>,
|
||||
moving: Option<MoveSnap>,
|
||||
/// The params of the move's last preview: in Shaper's Construction mode the move ends with
|
||||
/// them and `releaseOutside` ([`construction`]).
|
||||
last_move: Option<Value>,
|
||||
/// The move snapped to a point or a guide at its last step: the arrow turns hollow.
|
||||
snapped: bool,
|
||||
guide: GuideEdit,
|
||||
@@ -809,8 +812,17 @@ impl Tool for SelectionTool {
|
||||
self.measure = None;
|
||||
self.guides.clear();
|
||||
self.moving = None;
|
||||
let last = self.last_move.take();
|
||||
match (began, deselect, key) {
|
||||
(true, ..) => vec![Action::Commit],
|
||||
// In Construction mode, an original shape dropped wholly outside the group's
|
||||
// bounds leaves the group, in the move's undo step (`releaseOutside`).
|
||||
(true, ..) => match last.filter(|p| construction(cx) && p["copy"] != json!(true)) {
|
||||
Some(mut params) => {
|
||||
params["releaseOutside"] = json!(true);
|
||||
vec![Action::Preview("object.transform".into(), params), Action::Commit]
|
||||
}
|
||||
None => vec![Action::Commit],
|
||||
},
|
||||
(false, Some(id), _) => vec![Action::Exec("select.toggle".into(), json!({ "id": id.0 }))],
|
||||
// A click on the key object again: no key.
|
||||
(false, None, Some(id)) if cx.selection.key == Some(id) => vec![Action::Exec("select.key".into(), json!({}))],
|
||||
@@ -965,6 +977,11 @@ fn can_be_key(cx: &ToolContext, id: NodeId) -> bool {
|
||||
cx.doc.node(id).is_some_and(|n| !n.is_layer()) && !inside_selected && !member
|
||||
}
|
||||
|
||||
/// Shaper's Construction mode: a Shaper Group's original shapes are isolated.
|
||||
fn construction(cx: &ToolContext) -> bool {
|
||||
cx.isolation.and_then(|id| cx.doc.node(id)).is_some_and(|n| n.name.as_deref() == Some(vectorcraft_doc::shaper::SOURCES))
|
||||
}
|
||||
|
||||
/// Is `n` area type (text in a frame) whose frame the tools reshape? Type in perspective isn't:
|
||||
/// it transforms whole.
|
||||
pub(crate) fn is_area_type(n: &vectorcraft_doc::Node) -> bool {
|
||||
@@ -1192,7 +1209,9 @@ impl SelectionTool {
|
||||
self.snapped = (d - unsnapped).hypot() > 1e-9;
|
||||
self.state = State::Moving { start, press, hold, began: true, deselect: None, key: None };
|
||||
self.measure = cx.measurement_labels.then(|| (p, cx.offset_label(d.x, d.y)));
|
||||
out.push(Action::Preview("object.transform".into(), json!({ "matrix": matrix_json(Affine::translate(d)), "copy": m.alt })));
|
||||
let params = json!({ "matrix": matrix_json(Affine::translate(d)), "copy": m.alt });
|
||||
self.last_move = Some(params.clone());
|
||||
out.push(Action::Preview("object.transform".into(), params));
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4410,6 +4410,7 @@ axis Both И двете
|
||||
Shaper Инструмент за форми
|
||||
Expand Shaper Group Разширяване на групата от форми
|
||||
Release Shaper Group Освобождаване на групата от форми
|
||||
Remove from Shaper Group Премахване от групата от форми
|
||||
Select Shaper Face Избиране на лице на формата
|
||||
Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Плъзнете графика, цветове и текстови стилове тук, за да ги използвате повторно в различни документи. Библиотеките се съхраняват на това устройство — не се изисква акаунт.
|
||||
1 Color Job Задание с 1 цвят
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -823,6 +823,10 @@
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Rozložit skupinu Shaper
|
||||
Release Shaper Group Uvolnit skupinu Shaper
|
||||
Remove from Shaper Group Odebrat ze skupiny Shaper
|
||||
@msg source must be the id of a Shaper Group's original shape source musí být identifikátor původního tvaru skupiny Shaper
|
||||
@msg group must be the id of a Shaper Group group musí být identifikátor skupiny Shaper
|
||||
@msg the shape is not an editable original shape of that Shaper Group tvar není upravitelný původní tvar této skupiny Shaper
|
||||
Select Shaper Face Vybrat oblast Shaper
|
||||
Constrain Omezit
|
||||
Free Transform Volná transformace
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -758,6 +758,7 @@
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Udvid shapergruppe
|
||||
Release Shaper Group Frigør shapergruppe
|
||||
Remove from Shaper Group Fjern fra shapergruppe
|
||||
Select Shaper Face Markér shaperflade
|
||||
Constrain Begræns
|
||||
Free Transform Fri transformering
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4440,6 +4440,10 @@ theme Appearance Erscheinungsbild
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Shaper-Gruppe umwandeln
|
||||
Release Shaper Group Shaper-Gruppe zurückwandeln
|
||||
Remove from Shaper Group Aus Shaper-Gruppe entfernen
|
||||
@msg source must be the id of a Shaper Group's original shape source muss die ID einer Originalform einer Shaper-Gruppe sein
|
||||
@msg group must be the id of a Shaper Group group muss die ID einer Shaper-Gruppe sein
|
||||
@msg the shape is not an editable original shape of that Shaper Group die Form ist keine bearbeitbare Originalform dieser Shaper-Gruppe
|
||||
Select Shaper Face Shaper-Teilfläche auswählen
|
||||
Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Ziehen Sie Grafiken, Farben und Textformate hierher, um sie in mehreren Dokumenten wiederzuverwenden. Bibliotheken werden auf diesem Computer gespeichert – kein Konto erforderlich.
|
||||
1 Color Job 1-Farben-Auftrag
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4472,6 +4472,10 @@ theme Appearance Εμφάνιση
|
||||
Shaper Διαμορφωτής σχημάτων (Shaper Tool)
|
||||
Expand Shaper Group Ανάπτυξη ομάδας διαμόρφωσης σχημάτων
|
||||
Release Shaper Group Αποδέσμευση ομάδας διαμόρφωσης σχημάτων
|
||||
Remove from Shaper Group Αφαίρεση από ομάδα διαμόρφωσης σχημάτων
|
||||
@msg source must be the id of a Shaper Group's original shape Το source πρέπει να είναι το αναγνωριστικό ενός αρχικού σχήματος ομάδας διαμόρφωσης σχημάτων
|
||||
@msg group must be the id of a Shaper Group Το group πρέπει να είναι το αναγνωριστικό μιας ομάδας διαμόρφωσης σχημάτων
|
||||
@msg the shape is not an editable original shape of that Shaper Group Το σχήμα δεν είναι επεξεργάσιμο αρχικό σχήμα αυτής της ομάδας διαμόρφωσης σχημάτων
|
||||
Select Shaper Face Επιλογή επιφάνειας διαμορφωτή
|
||||
Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Σύρετε εδώ σχέδια, χρώματα και στυλ κειμένου για χρήση σε άλλα έγγραφα. Οι βιβλιοθήκες αποθηκεύονται στον υπολογιστή — χωρίς λογαριασμό.
|
||||
1 Color Job Εργασία ενός χρώματος
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2071 and column 19.
|
@@ -4520,6 +4520,10 @@ axis Both Ambos
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Expandir grupo Shaper
|
||||
Release Shaper Group Soltar grupo Shaper
|
||||
Remove from Shaper Group Quitar del grupo Shaper
|
||||
@msg source must be the id of a Shaper Group's original shape source debe ser el identificador de una forma original de un grupo Shaper
|
||||
@msg group must be the id of a Shaper Group group debe ser el identificador de un grupo Shaper
|
||||
@msg the shape is not an editable original shape of that Shaper Group la forma no es una forma original editable de ese grupo Shaper
|
||||
Select Shaper Face Seleccionar cara Shaper
|
||||
Constrain Restringir
|
||||
Free Transform Transformación libre
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -809,6 +809,10 @@
|
||||
Shaper Muotoilija
|
||||
Expand Shaper Group Laajenna muotoilijaryhmä
|
||||
Release Shaper Group Vapauta muotoilijaryhmä
|
||||
Remove from Shaper Group Poista muotoilijaryhmästä
|
||||
@msg source must be the id of a Shaper Group's original shape source-arvon on oltava muotoilijaryhmän alkuperäisen muodon tunnus
|
||||
@msg group must be the id of a Shaper Group group-arvon on oltava muotoilijaryhmän tunnus
|
||||
@msg the shape is not an editable original shape of that Shaper Group muoto ei ole kyseisen muotoilijaryhmän muokattava alkuperäinen muoto
|
||||
Select Shaper Face Valitse muotoilijan pinta
|
||||
Constrain Rajoita
|
||||
Free Transform Vapaa muunnos
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4517,6 +4517,10 @@ axis Both Les deux
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Décomposer le groupe Shaper
|
||||
Release Shaper Group Annuler le groupe Shaper
|
||||
Remove from Shaper Group Retirer du groupe Shaper
|
||||
@msg source must be the id of a Shaper Group's original shape source doit être l’identifiant d’une forme d’origine d’un groupe Shaper
|
||||
@msg group must be the id of a Shaper Group group doit être l’identifiant d’un groupe Shaper
|
||||
@msg the shape is not an editable original shape of that Shaper Group la forme n’est pas une forme d’origine modifiable de ce groupe Shaper
|
||||
Select Shaper Face Sélectionner la face Shaper
|
||||
Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Faites glisser ici des illustrations, des couleurs et des styles de texte pour les réutiliser d’un document à l’autre. Les bibliothèques sont enregistrées sur cet ordinateur — aucun compte n’est nécessaire.
|
||||
1 Color Job Travail 1 couleur
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3100,6 +3100,10 @@ mark-style Japanese Japán
|
||||
Release Opacity Mask Átlátszósági maszk megszüntetése
|
||||
Release Selection Kijelölés felszabadítása
|
||||
Release Shaper Group Alakformáló csoport megszüntetése
|
||||
Remove from Shaper Group Eltávolítás az alakformáló csoportból
|
||||
@msg source must be the id of a Shaper Group's original shape A source értékének egy alakformáló csoport eredeti alakzatának azonosítójának kell lennie
|
||||
@msg group must be the id of a Shaper Group A group értékének egy alakformáló csoport azonosítójának kell lennie
|
||||
@msg the shape is not an editable original shape of that Shaper Group Az alakzat nem szerkeszthető eredeti alakzata ennek az alakformáló csoportnak
|
||||
Release Text Wrap Körbefuttatás megszüntetése
|
||||
Release to Layers (Build) Szétosztás rétegekre (építkezve)
|
||||
Release to Layers (Sequence) Szétosztás rétegekre (sorozatként)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -758,6 +758,7 @@
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Perluas grup Shaper
|
||||
Release Shaper Group Lepaskan grup Shaper
|
||||
Remove from Shaper Group Hapus dari grup Shaper
|
||||
Select Shaper Face Seleksi sisi Shaper
|
||||
Constrain Batasi
|
||||
Free Transform Transformasi bebas
|
||||
|
||||
|
@@ -4560,6 +4560,10 @@ axis Both Entrambi
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Espandi gruppo Shaper
|
||||
Release Shaper Group Rilascia gruppo Shaper
|
||||
Remove from Shaper Group Rimuovi dal gruppo Shaper
|
||||
@msg source must be the id of a Shaper Group's original shape source deve essere l’identificatore di una forma originale di un gruppo Shaper
|
||||
@msg group must be the id of a Shaper Group group deve essere l’identificatore di un gruppo Shaper
|
||||
@msg the shape is not an editable original shape of that Shaper Group la forma non è una forma originale modificabile di quel gruppo Shaper
|
||||
Select Shaper Face Seleziona faccia Shaper
|
||||
Constrain Vincola
|
||||
Free Transform Trasformazione libera
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3791,6 +3791,10 @@ theme Appearance 外観
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Shaper グループを拡張
|
||||
Release Shaper Group Shaper グループを解除
|
||||
Remove from Shaper Group Shaper グループから削除
|
||||
@msg source must be the id of a Shaper Group's original shape source には Shaper グループの元のシェイプの ID を指定してください
|
||||
@msg group must be the id of a Shaper Group group には Shaper グループの ID を指定してください
|
||||
@msg the shape is not an editable original shape of that Shaper Group そのシェイプはその Shaper グループの編集可能な元のシェイプではありません
|
||||
Select Shaper Face Shaper の面を選択
|
||||
Constrain 制限
|
||||
Free Transform 自由変形
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -699,6 +699,10 @@
|
||||
Shaper ფორმირება
|
||||
Expand Shaper Group ფორმირების ჯგუფის გაშლა
|
||||
Release Shaper Group ფორმირების ჯგუფის მოხსნა
|
||||
Remove from Shaper Group ფორმირების ჯგუფიდან ამოღება
|
||||
@msg source must be the id of a Shaper Group's original shape source უნდა იყოს ფორმირების ჯგუფის საწყისი ფიგურის იდენტიფიკატორი
|
||||
@msg group must be the id of a Shaper Group group უნდა იყოს ფორმირების ჯგუფის იდენტიფიკატორი
|
||||
@msg the shape is not an editable original shape of that Shaper Group ფიგურა არ არის ამ ფორმირების ჯგუფის რედაქტირებადი საწყისი ფიგურა
|
||||
Select Shaper Face ფორმირების არის მონიშვნა
|
||||
Constrain შეზღუდვა
|
||||
Free Transform თავისუფალი გარდაქმნა
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3778,6 +3778,10 @@ theme Appearance 외관
|
||||
Shaper 셰이퍼
|
||||
Expand Shaper Group 셰이퍼 그룹 확장
|
||||
Release Shaper Group 셰이퍼 그룹 해제
|
||||
Remove from Shaper Group 셰이퍼 그룹에서 제거
|
||||
@msg source must be the id of a Shaper Group's original shape source는 셰이퍼 그룹의 원본 모양 ID여야 합니다
|
||||
@msg group must be the id of a Shaper Group group은 셰이퍼 그룹의 ID여야 합니다
|
||||
@msg the shape is not an editable original shape of that Shaper Group 모양이 해당 셰이퍼 그룹의 편집 가능한 원본 모양이 아닙니다
|
||||
Select Shaper Face 셰이퍼 면 선택
|
||||
Constrain 제한
|
||||
Free Transform 자유 변형
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -772,6 +772,7 @@
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Shapergroep uitbreiden
|
||||
Release Shaper Group Shapergroep vrijgeven
|
||||
Remove from Shaper Group Verwijderen uit shapergroep
|
||||
Select Shaper Face Shapervlak selecteren
|
||||
Constrain Beperken
|
||||
Free Transform Vrij transformeren
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -767,6 +767,10 @@
|
||||
Shaper Kształtowanie
|
||||
Expand Shaper Group Rozszerz grupę kształtowania
|
||||
Release Shaper Group Uwolnij grupę kształtowania
|
||||
Remove from Shaper Group Usuń z grupy kształtowania
|
||||
@msg source must be the id of a Shaper Group's original shape source musi być identyfikatorem oryginalnego kształtu grupy kształtowania
|
||||
@msg group must be the id of a Shaper Group group musi być identyfikatorem grupy kształtowania
|
||||
@msg the shape is not an editable original shape of that Shaper Group kształt nie jest edytowalnym oryginalnym kształtem tej grupy kształtowania
|
||||
Select Shaper Face Zaznacz ścianę kształtowania
|
||||
Constrain Ogranicz
|
||||
Free Transform Swobodne przekształcanie
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3070,6 +3070,10 @@ imageTrace Outline Contorno
|
||||
Shaper Formador
|
||||
Expand Shaper Group Expandir Grupo Formador
|
||||
Release Shaper Group Liberar Grupo Formador
|
||||
Remove from Shaper Group Remover do Grupo Formador
|
||||
@msg source must be the id of a Shaper Group's original shape source deve ser o ID de uma forma original de um Grupo Formador
|
||||
@msg group must be the id of a Shaper Group group deve ser o ID de um Grupo Formador
|
||||
@msg the shape is not an editable original shape of that Shaper Group a forma não é uma forma original editável desse Grupo Formador
|
||||
Select Shaper Face Selecionar Face do Formador
|
||||
Constrain Restringir
|
||||
Free Transform Transformação Livre
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -4556,6 +4556,10 @@ axis Both Оба
|
||||
Shaper Shaper
|
||||
Expand Shaper Group Разобрать группу Shaper
|
||||
Release Shaper Group Освободить группу Shaper
|
||||
Remove from Shaper Group Удалить из группы Shaper
|
||||
@msg source must be the id of a Shaper Group's original shape source должен быть идентификатором исходной фигуры группы Shaper
|
||||
@msg group must be the id of a Shaper Group group должен быть идентификатором группы Shaper
|
||||
@msg the shape is not an editable original shape of that Shaper Group фигура не является редактируемой исходной фигурой этой группы Shaper
|
||||
Select Shaper Face Выбрать область Shaper
|
||||
Constrain Ограничить
|
||||
Free Transform Свободное трансформирование
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4426,6 +4426,10 @@ axis Both Båda
|
||||
Shaper Formning
|
||||
Expand Shaper Group Expandera Shaper-grupp
|
||||
Release Shaper Group Släpp formgivargrupp
|
||||
Remove from Shaper Group Ta bort från formgivargrupp
|
||||
@msg source must be the id of a Shaper Group's original shape source måste vara id för en originalform i en formgivargrupp
|
||||
@msg group must be the id of a Shaper Group group måste vara id för en formgivargrupp
|
||||
@msg the shape is not an editable original shape of that Shaper Group formen är inte en redigerbar originalform i den formgivargruppen
|
||||
Select Shaper Face Markera Shaper-yta
|
||||
Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Dra hit grafik, färger och textstilar för att återanvända dem i andra dokument. Biblioteken lagras på den här datorn – inget konto krävs.
|
||||
1 Color Job 1-färgsjobb
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4271,6 +4271,10 @@ axis Both Her İkisi
|
||||
Shaper Şekillendirici
|
||||
Expand Shaper Group Shaper Grubunu Genişlet
|
||||
Release Shaper Group Şekillendirici Grubunu Yayınla
|
||||
Remove from Shaper Group Şekillendirici Grubundan Kaldır
|
||||
@msg source must be the id of a Shaper Group's original shape source, bir Şekillendirici Grubunun özgün şeklinin kimliği olmalıdır
|
||||
@msg group must be the id of a Shaper Group group, bir Şekillendirici Grubunun kimliği olmalıdır
|
||||
@msg the shape is not an editable original shape of that Shaper Group şekil, o Şekillendirici Grubunun düzenlenebilir özgün şekli değil
|
||||
Select Shaper Face Şekillendirici Yüz'ü seçin
|
||||
Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Resimleri, renkleri ve metin stillerini belgelerde yeniden kullanmak için buraya sürükleyin. Kitaplıklar bu makinede depolanır; hesap gerekmez.
|
||||
1 Color Job 1 Renk İşi
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4542,6 +4542,10 @@ axis Both Обидві
|
||||
Shaper Формоутворення
|
||||
Expand Shaper Group Розібрати групу формоутворення
|
||||
Release Shaper Group Звільнити групу формоутворення
|
||||
Remove from Shaper Group Вилучити з групи формоутворення
|
||||
@msg source must be the id of a Shaper Group's original shape source має бути ідентифікатором вихідної фігури групи формоутворення
|
||||
@msg group must be the id of a Shaper Group group має бути ідентифікатором групи формоутворення
|
||||
@msg the shape is not an editable original shape of that Shaper Group фігура не є редагованою вихідною фігурою цієї групи формоутворення
|
||||
Select Shaper Face Виділити область формоутворення
|
||||
Constrain Обмежити
|
||||
Free Transform Вільне трансформування
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 749 and column 40.
|
@@ -2875,6 +2875,10 @@ mark-style Japanese Kiểu Nhật
|
||||
Release Opacity Mask Giải phóng mặt nạ độ mờ
|
||||
Release Selection Giải phóng vùng chọn
|
||||
Release Shaper Group Giải phóng nhóm Shaper
|
||||
Remove from Shaper Group Xóa khỏi nhóm Shaper
|
||||
@msg source must be the id of a Shaper Group's original shape source phải là mã định danh của một hình gốc trong nhóm Shaper
|
||||
@msg group must be the id of a Shaper Group group phải là mã định danh của một nhóm Shaper
|
||||
@msg the shape is not an editable original shape of that Shaper Group hình này không phải là hình gốc có thể chỉnh sửa của nhóm Shaper đó
|
||||
Release to Layers (Build) Giải phóng ra các lớp (Xây dựng)
|
||||
Release to Layers (Sequence) Giải phóng ra các lớp (Chuỗi)
|
||||
Release with Perspective Giải phóng với phối cảnh
|
||||
|
||||
|
@@ -4831,6 +4831,10 @@ axis Both 两者
|
||||
The dialog was closed before its file dialog answered 文件对话框给出结果之前,对话框已关闭
|
||||
Expand Shaper Group 扩展 Shaper 组
|
||||
Release Shaper Group 释放 Shaper 组
|
||||
Remove from Shaper Group 从 Shaper 组中移除
|
||||
@msg source must be the id of a Shaper Group's original shape source 必须是 Shaper 组中原始形状的 ID
|
||||
@msg group must be the id of a Shaper Group group 必须是 Shaper 组的 ID
|
||||
@msg the shape is not an editable original shape of that Shaper Group 该形状不是该 Shaper 组中可编辑的原始形状
|
||||
Select Shaper Face 选择 Shaper 区域
|
||||
Constrain 约束
|
||||
Perspective Distort 透视扭曲
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4641,6 +4641,10 @@ axis Both 兩者
|
||||
Shaper Shaper
|
||||
Expand Shaper Group 展開 Shaper 群組
|
||||
Release Shaper Group 釋放 Shaper 群組
|
||||
Remove from Shaper Group 從 Shaper 群組中移除
|
||||
@msg source must be the id of a Shaper Group's original shape source 必須是 Shaper 群組中原始形狀的 ID
|
||||
@msg group must be the id of a Shaper Group group 必須是 Shaper 群組的 ID
|
||||
@msg the shape is not an editable original shape of that Shaper Group 該形狀不是該 Shaper 群組中可編輯的原始形狀
|
||||
Select Shaper Face 選取 Shaper 區域
|
||||
Constrain 強制
|
||||
Free Transform 任意變形
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
+14
-1
@@ -1104,7 +1104,7 @@ The transform, Align, Object Properties and Live Shape Properties commands refus
|
||||
that is not true or false with `` `strokes` must be true or false ``, naming the key, and leave the document and its
|
||||
undo history as they were: the options `copy`, `strokes`, `corners`, `patterns`, `objects` and `typeAreas` in
|
||||
`object.transform`, `object.move`, `object.nudge`, `object.rotate`, `object.scale`, `object.reflect`, `object.shear`,
|
||||
`object.transformAgain` and `object.setBounds`; `anchors` in `object.transform`; `big` in `object.nudge`;
|
||||
`object.transformAgain` and `object.setBounds`; `anchors` and `releaseOutside` in `object.transform`; `big` in `object.nudge`;
|
||||
`absolute` in `object.rotate`; `proportional` in `object.setBounds`; `patterns` in `object.align`, `object.distribute`
|
||||
and `object.distributeSpacing`; `visible`, `locked`, `isolate` and `knockoutShape` in `object.setProps`; and
|
||||
`invertPie`, `makeSidesEqual` and `relativeRadii` in `object.setLiveShape`. A null is read as a key left out.
|
||||
@@ -1389,6 +1389,19 @@ everything in it, and `select.all`, `select.allOnArtboard` and `select.inverse`
|
||||
among the objects beside it in its group or layer, passing over hidden and locked objects and sublayers. When there
|
||||
is no such object, the selection stays as it is.
|
||||
|
||||
## Shaper Groups
|
||||
|
||||
`shaper.releaseSource {source, group?}` takes the original shape `source` out of its Shaper Group: the shape becomes a
|
||||
path just above the group with its own fill and stroke, and is selected. The group's result is computed again from the
|
||||
original shapes left, and a group left with none is removed. When `group` is given, it must be the Shaper Group that
|
||||
holds `source`. A source that is not an original shape of an editable Shaper Group is an error, and the document and its
|
||||
undo history stay as they were. The command returns `{id}`, the shape's id, and records one undo step, Remove from Shaper
|
||||
Group. Construction mode ends when the group was in it.
|
||||
|
||||
`object.transform` with `releaseOutside: true` does the same for each moved original shape whose bounds end wholly
|
||||
outside the bounds the group's original shapes had before the move, in the move's undo step. A shape whose bounds still
|
||||
overlap them only moves. The Selection tool sends it at the end of a move in Construction mode.
|
||||
|
||||
## Global Edit
|
||||
|
||||
Select › Start Global Edit is `select.globalEdit {appearance?, size?, artboards?, includeCanvas?}`. With one path,
|
||||
|
||||
@@ -13,6 +13,8 @@ Click once to select the group; click again to select its visible fill and chang
|
||||
|
||||
While the group or one of its faces is selected (Face Selection mode) and the Shaper or Selection tool is active, an arrow widget sits beside the middle of the right side of the original shapes’ bounds. Click it to enter Construction mode: the original shapes are isolated and nothing is selected, and a click with Shaper selects the group’s original shape under the pointer. Click the widget again to leave Construction mode with the group selected. The arrow points right in Face Selection mode and left in Construction mode.
|
||||
|
||||
To take a shape out of a Shaper Group, drag it with the Selection tool in Construction mode and drop it wholly outside the bounds of the group's original shapes. The shape becomes a path just above the group, keeps its fill and stroke and stays selected, and Construction mode ends. The group is computed again from the shapes left, and a group left with no shapes is removed. The move and the removal are one undo step, Remove from Shaper Group. A shape dropped across the bounds only moves.
|
||||
|
||||
Smart Guides and Snap to Point do not snap to the original shapes. The reference point of the transform tools snaps to an original shape only when that shape is selected, or under the pointer in Construction mode. Outside Construction mode the Lasso does not pick the anchor points of the original shapes, and the Add Anchor Point, Delete Anchor Point, Anchor Point and Scissors tools do not pick the anchor points or segments of an original shape that is not selected.
|
||||
|
||||
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.
|
||||
|
||||
@@ -627,6 +627,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- On Windows a maximized window stops one pixel short of each monitor edge where an auto-hidden taskbar sits.
|
||||
- **Tools:**
|
||||
- **Shaper:** uneven squares and rectangles become live rectangles even when drawing begins mid-edge. Recognition uses uniformly spaced samples and closed-loop corners, fits rectangle edges/angles rather than requiring a nearly perfect bounding-box fill, and includes the pointer-release position. Rectangles/squares and ellipses snap to 45° steps, hexagons to 90°, and triangles point up or down; lines keep their free angle even with Shift. Shaper Groups retain the live originals, merge regions with the gesture-origin fill, punch fills independently from strokes, remove boundaries on exiting scribbles and trim protruding stroke pieces. Source moves/resizes re-evaluate the result in the same undo step; double-click enters construction editing, and with the Shaper or Selection tool a click on the arrow widget beside a Shaper Group's original shapes switches the group between Face Selection and Construction mode. Native save/reopen, face recoloring, Release and Expand are covered. Finer gesture heuristics remain.
|
||||
In Construction mode, a Selection-tool drag that drops an original shape wholly outside the bounds of the group's original shapes takes it out of the group in the move's undo step (Remove from Shaper Group, `shaper.releaseSource`): the shape becomes a path above the group and stays selected, and a group left with no shapes is removed. Engine tests drag a shape out of a two-shape group and back across the bounds, take the last shape out, undo, and save the result.
|
||||
- **Selection:** Selection, Direct/Group Selection, Magic Wand, Lasso. A Shift-drag marquee toggles what it reaches: objects with the Selection tool, anchors with Direct and Group Selection (#483); the Lasso adds with Shift and subtracts with Alt. Dragging a path segment with Direct Selection bends it when it's curved (its anchors stay) and moves its two anchors when it's straight, one undo step; the fill, Shift or Alt still pick the whole path (Alt-drag copies it).
|
||||
With the Selection or Group Selection tool, pressing E while dragging a marquee makes the marquee select only the objects that lie entirely inside it. Pressing E again switches the marquee back to its default selection.
|
||||
The Lasso picks anchor points only on art a click can pick, and the Add Anchor Point, Delete Anchor Point, Anchor Point and Scissors tools pick anchor points and segments only on the selected paths and on art a click can pick: not on a Shaper Group's original shapes outside Construction mode, and in isolation mode only inside the isolated group or object. Smart Guides and Snap to Point do not snap to a Shaper Group's original shapes, and the reference point of the transform tools snaps to one only when it is selected, or under the pointer in Construction mode.
|
||||
|
||||
Reference in New Issue
Block a user