mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Fix #3705: art drawn or pasted inside an isolated object in Draw Inside stays in isolation mode
The drawing modes help page (https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html) has you draw or paste into the selected object in Draw Inside. The isolation help page (https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html) isolates a path, group or object on a double-click with the Selection tool, and puts art added in isolation mode where the isolated art was once the mode ends, so that art belongs with what is isolated. Steps: select a path, choose Draw Inside, double-click the path with the Selection tool (it is isolated), then draw or paste. The art went into the clip group Draw Inside makes around the path, but isolation mode stayed on the bare path, so the new art was outside it: drawn dimmed, passed through by clicks and marquees, and a double-click on it selected the path and never entered the clip group. Double-clicking an existing Draw Inside clip group from outside isolation mode already isolated it, and pastes then went inside it; that is unchanged. Cause: create::insert_look_turned and edit::paste_clip put the art into create::draw_inside_group's clip group but left DocState::isolation on the object drawn into, and with a single object isolated, hit testing reaches only that object. Change: when the object drawn into is the isolated one, isolation moves to its clip group (create::isolate_draw_inside_group), after new art and after Draw Inside pastes. A drag's previews replay from its snapshot and make the group under the same id, and cancelling the drag restores the isolation it began with. Undo and redo end isolation mode when the isolated object is no longer in the document (the undone clip group), as mask editing already does, instead of keeping an id a later object may take. Verification: drawing_inside_an_isolated_object_keeps_the_new_art_in_isolation_mode fails without the change (isolation Some(NodeId(2)) instead of the clip group Some(NodeId(6))). A whole-window egui run through the control channel's ui.click/ui.drag (double-click the rectangle, drag an ellipse inside, paste, click the copy) left isolation on the rectangle and the click selected the rectangle before; after, isolation is on the clip group and the click selects the copy.
This commit is contained in:
committed by
bflatastic
parent
0083587131
commit
0a83c19911
@@ -73,7 +73,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Drawing Mode",
|
||||
[],
|
||||
Some("Shift+D"),
|
||||
"{mode?: normal|behind|inside} (inside needs one selected path, compound path or type; no param cycles normal → behind → inside → normal, going from behind back to normal when Draw Inside can't draw into the selection)",
|
||||
"{mode?: normal|behind|inside} (inside needs one selected path, compound path or type; no param cycles normal → behind → inside → normal, going from behind back to normal when Draw Inside can't draw into the selection; art drawn or pasted inside the object isolation mode isolates takes isolation mode into the object's clip group)",
|
||||
has_doc,
|
||||
draw_mode
|
||||
),
|
||||
@@ -149,16 +149,18 @@ fn insert_look_turned(
|
||||
let mode = s.draw_mode;
|
||||
let inside = s.draw_inside;
|
||||
let behind_of = s.doc()?.selection.in_paint_order(&s.doc()?.doc).first().copied();
|
||||
let id = s.edit(label, |d, sel| {
|
||||
let (id, into) = s.edit(label, |d, sel| {
|
||||
let id = d.alloc_id();
|
||||
let mut n = Node::new(id, kind);
|
||||
look.apply(d, &mut n);
|
||||
n.name = name;
|
||||
n.bbox_angle = bbox_angle;
|
||||
let mut into = None;
|
||||
match (mode, inside) {
|
||||
(crate::DrawMode::Inside, Some(target)) if d.node(target).is_some() => {
|
||||
let group = draw_inside_group(d, target)?;
|
||||
d.insert(Some(group), usize::MAX, n)?;
|
||||
into = Some((target, group));
|
||||
}
|
||||
(crate::DrawMode::Behind, _) => match behind_of.and_then(|b| d.position(b)) {
|
||||
Some((par, idx, _)) => {
|
||||
@@ -175,8 +177,11 @@ fn insert_look_turned(
|
||||
if select {
|
||||
sel.set([id]);
|
||||
}
|
||||
Ok(id)
|
||||
Ok((id, into))
|
||||
})?;
|
||||
if let Some((target, group)) = into {
|
||||
isolate_draw_inside_group(s, target, group)?;
|
||||
}
|
||||
Ok(json!({ "id": id.0 }))
|
||||
}
|
||||
|
||||
@@ -204,6 +209,21 @@ pub(crate) fn draw_inside_group(d: &mut vectorcraft_doc::Document, target: NodeI
|
||||
Ok(gid)
|
||||
}
|
||||
|
||||
/// Draw Inside into the isolated object (#3705): isolation mode moves to the clip group the art
|
||||
/// went into ([`draw_inside_group`]), so what is drawn or pasted inside the object is in isolation
|
||||
/// mode with it, shown undimmed and picked by clicks, instead of locked beside it. Art added in
|
||||
/// isolation mode belongs with what is isolated: it takes the isolated art's place when the mode
|
||||
/// ends (<https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html>).
|
||||
/// A drag's next preview, replayed on the drag's snapshot, makes the group again under the same
|
||||
/// id, so isolation stays on it; cancelling the drag restores the isolation it began with.
|
||||
pub(crate) fn isolate_draw_inside_group(s: &mut Session, target: NodeId, group: NodeId) -> Result<()> {
|
||||
let st = s.doc_mut()?;
|
||||
if st.isolation == Some(target) {
|
||||
st.isolation = Some(group);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The object Draw Inside would draw into: the one selected object, when it is a path, a compound
|
||||
/// path or type.
|
||||
fn inside_target(st: &crate::DocState) -> Option<NodeId> {
|
||||
|
||||
@@ -411,6 +411,7 @@ fn undo(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
}
|
||||
st.sync_new_art();
|
||||
super::maskedit::follow_history(st, editing);
|
||||
leave_isolation_if_gone(st);
|
||||
Ok(json!({ "undone": label }))
|
||||
}
|
||||
|
||||
@@ -436,9 +437,19 @@ fn redo(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
}
|
||||
st.sync_new_art();
|
||||
super::maskedit::follow_history(st, editing);
|
||||
leave_isolation_if_gone(st);
|
||||
Ok(json!({ "redone": label }))
|
||||
}
|
||||
|
||||
/// After undo or redo, isolation mode ends when the isolated object is no longer in the document,
|
||||
/// such as the clip group Draw Inside made around the isolated object (#3705), rather than naming
|
||||
/// an id a later object may take.
|
||||
fn leave_isolation_if_gone(st: &mut crate::DocState) {
|
||||
if st.isolation.is_some_and(|i| st.doc.node(i).is_none()) {
|
||||
st.isolation = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// History panel › Clear History: removes the steps after the current state and the steps before
|
||||
/// it, and leaves the document as it is. Typing or a drag still in progress is first kept as a
|
||||
/// step, as Undo keeps it, so it is removed with the others and its change stays in the document.
|
||||
@@ -724,7 +735,7 @@ fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, cho
|
||||
// In place, in front, in back: onto the active artboard when the UI names it.
|
||||
_ => vec![onto_artboard(&st.doc, p, clip)],
|
||||
};
|
||||
let (ids, imported, artboards) = s.edit(mode.label(), |d, sel| {
|
||||
let (ids, imported, artboards, into) = s.edit(mode.label(), |d, sel| {
|
||||
let imported = clip.import_into(d, choices, same_doc);
|
||||
// Draw Inside: the clip group of the object drawn into, made on the first paste.
|
||||
let into = inside.map(|t| super::create::draw_inside_group(d, t)).transpose()?;
|
||||
@@ -771,8 +782,11 @@ fn paste_clip(s: &mut Session, p: &Value, mode: PasteMode, clip: &Clipboard, cho
|
||||
// Pasted type arrives without the layout bounds cache: fill it so boxes and alignment are
|
||||
// right away (opening the file later would, but paste must not depend on a reopen).
|
||||
super::typecmd::refresh_bounds_of(d, &new_ids);
|
||||
Ok((new_ids, imported, artboards))
|
||||
Ok((new_ids, imported, artboards, into))
|
||||
})?;
|
||||
if let (Some(target), Some(group)) = (inside, into) {
|
||||
super::create::isolate_draw_inside_group(s, target, group)?;
|
||||
}
|
||||
// The Artboard tool selects the pasted artboards, the first one active.
|
||||
if !artboards.is_empty() && s.tool_id() == "artboard" {
|
||||
s.set_tool_option("selected", &json!(artboards));
|
||||
|
||||
@@ -242,6 +242,64 @@ fn pasting_in_draw_inside_puts_the_copies_inside_the_object() {
|
||||
assert_eq!((s.doc().unwrap().doc.node(g).unwrap().kind_label(), parent(&s, ids[0])), ("Clip Group", Some(g)));
|
||||
}
|
||||
|
||||
/// #3705: in Draw Inside, with the object isolated by a double-click with the Selection tool, what
|
||||
/// is drawn or pasted inside it stays in isolation mode: isolation moves to the clip group Draw
|
||||
/// Inside makes around the object, so a click picks the pasted copy and it draws undimmed. It used
|
||||
/// to stay on the bare path, the new art locked and dimmed beside it, and double-clicks never
|
||||
/// reached the clip group. Undoing the step that made the clip group leaves isolation mode, as the
|
||||
/// group is gone.
|
||||
#[test]
|
||||
fn drawing_inside_an_isolated_object_keeps_the_new_art_in_isolation_mode() {
|
||||
use vectorcraft_geom::Affine;
|
||||
use vectorcraft_render::{RenderOptions, Renderer};
|
||||
use vectorcraft_tools::{PointerEvent, PointerKind};
|
||||
let mut s = session();
|
||||
let a = s.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 100})).unwrap()["id"].as_u64().unwrap();
|
||||
let red = s.execute("shape.ellipse", &json!({"x": 20, "y": 20, "width": 50, "height": 50})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("paint.setFill", &json!({"color": "#ff0000", "ids": [red]})).unwrap();
|
||||
s.execute("edit.copy", &json!({})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [a]})).unwrap();
|
||||
s.execute("view.drawMode", &json!({"mode": "inside"})).unwrap();
|
||||
let ev = |s: &mut Session, kinds: &[PointerKind], x: f64, y: f64| {
|
||||
for k in kinds {
|
||||
s.pointer(&PointerEvent::new(*k, x, y), ViewInfo::default()).unwrap();
|
||||
}
|
||||
};
|
||||
s.select_tool("selection", ViewInfo::default()).unwrap();
|
||||
ev(&mut s, &[PointerKind::Down, PointerKind::Up, PointerKind::Down, PointerKind::Up, PointerKind::DoubleClick], 150.0, 150.0);
|
||||
assert_eq!(s.doc().unwrap().isolation, Some(NodeId(a)), "the double-click isolates the object");
|
||||
// An ellipse dragged out with the Ellipse tool (several previews) goes inside the object, and
|
||||
// isolation mode with it.
|
||||
s.select_tool("ellipse", ViewInfo::default()).unwrap();
|
||||
ev(&mut s, &[PointerKind::Down], 110.0, 110.0);
|
||||
for d in [5.0, 10.0, 15.0, 20.0] {
|
||||
ev(&mut s, &[PointerKind::Drag], 110.0 + d, 110.0 + d);
|
||||
}
|
||||
ev(&mut s, &[PointerKind::Up], 130.0, 130.0);
|
||||
let d = &s.doc().unwrap().doc;
|
||||
let g = d.parent_of(NodeId(a)).unwrap();
|
||||
assert_eq!(d.node(g).unwrap().kind_label(), "Clip Group");
|
||||
assert_eq!(s.doc().unwrap().isolation, Some(g), "isolation mode follows the object into its clip group");
|
||||
// A paste goes into it too, in isolation mode, and a click with the Selection tool picks it.
|
||||
let pasted = s.execute("edit.paste", &json!({"center": [170, 170]})).unwrap()["ids"][0].as_u64().unwrap();
|
||||
let st = s.doc().unwrap();
|
||||
assert_eq!((st.doc.parent_of(NodeId(pasted)), st.isolation), (Some(g), Some(g)));
|
||||
// Isolation mode draws the art around what is isolated dimmed: the red copy is not.
|
||||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), isolated: st.isolation, ..Default::default() };
|
||||
let p = Renderer::new().render(&st.doc, 400, 300, Affine::IDENTITY, &opts).pixel(170, 170);
|
||||
assert_eq!([p[0], p[1], p[2]], [255, 0, 0], "the pasted copy draws undimmed");
|
||||
s.execute("select.none", &json!({})).unwrap();
|
||||
s.select_tool("selection", ViewInfo::default()).unwrap();
|
||||
ev(&mut s, &[PointerKind::Down, PointerKind::Up], 170.0, 170.0);
|
||||
assert_eq!(s.doc().unwrap().selection.objects, vec![NodeId(pasted)], "a click picks the pasted copy");
|
||||
// Undoing the paste keeps isolation mode; undoing the ellipse, which made the clip group, ends it.
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().isolation, Some(g));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
let st = s.doc().unwrap();
|
||||
assert!(st.doc.node(g).is_none() && st.isolation.is_none(), "{:?}", st.isolation);
|
||||
}
|
||||
|
||||
/// In Draw Behind, Paste in Place and Paste on All Artboards put the copies just below the
|
||||
/// selection, as Paste in Back does; Paste puts them on top, as in Draw Normal.
|
||||
#[test]
|
||||
|
||||
+3
-1
File diff suppressed because one or more lines are too long
+4
-1
@@ -2966,7 +2966,10 @@ pastes objects back into the layers they came from, by name, making missing ones
|
||||
too: in Draw Inside, `edit.paste`, `edit.pasteInPlace` and `edit.pasteOnAllArtboards` put the objects inside the
|
||||
object drawn into (its clip group, made on the first paste), whatever Paste Remembers Layers says; in Draw Behind,
|
||||
`edit.pasteInPlace` and `edit.pasteOnAllArtboards` put them just below the selection. `edit.pasteInFront` and
|
||||
`edit.pasteInBack` ignore the drawing mode.
|
||||
`edit.pasteInBack` ignore the drawing mode. When the object drawn into is the one isolation mode isolates (`object.isolate`,
|
||||
or a double-click on it with the Selection tool), art drawn or pasted inside it takes isolation mode into its clip group,
|
||||
so the new art can be picked and isn't dimmed; `edit.undo` or `edit.redo` that removes the isolated object ends
|
||||
isolation mode.
|
||||
|
||||
Artboards copy with their art. `artboard.copy {index? | indices?, art?}` puts artboards on the clipboard together
|
||||
with the art fully inside each (`index` and `indices` default to the artboards the Artboard tool has selected; `art`
|
||||
|
||||
File diff suppressed because one or more lines are too long
+3
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user