mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:40:14 -06:00
Select: Link Block Series ignores selected type that Select All cannot reach
Type selected by id on a hidden, locked or template layer, or outside the isolated group, no longer counts as selected type: the command stays off and does not select that frame's thread. The check reads the object's ancestry, so the Select menu does not walk the whole document.
This commit is contained in:
@@ -11,7 +11,7 @@ use vectorcraft_geom::Vec2;
|
||||
|
||||
use super::edit::selected_roots;
|
||||
use super::pathops::Beside;
|
||||
use super::select::{selectable_ids, walk_selectable};
|
||||
use super::select::{is_selectable, selectable_ids, walk_selectable};
|
||||
use super::{
|
||||
CommandSpec, EngineError, Point, Result, Session, bad, bool_or, checked_ids_param, cmd, f64_or, f64_req, has_doc, has_selection, id_param, ok,
|
||||
str_param,
|
||||
@@ -32,7 +32,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Link Block Series",
|
||||
["Select", "Same"],
|
||||
None,
|
||||
"{} select every frame of the threads of the selected type objects (and of the visible, unlocked type inside selected groups); type in no thread selects itself; frames that are hidden or locked, inside a hidden or locked object or layer, on a template layer, or outside the isolated group or layer are left out → {count}",
|
||||
"{} select every frame of the threads of the selected type objects (and of the type inside selected groups); type in no thread selects itself; type and frames that are hidden or locked, inside a hidden or locked object or layer, on a template layer, or outside the isolated group or layer neither count as selected nor are selected → {count}",
|
||||
has_text_selection,
|
||||
same_link_block_series
|
||||
),
|
||||
@@ -322,8 +322,9 @@ fn same_text(s: &mut Session, cmd: &str, eq: fn(&CharStyle, &CharStyle) -> bool)
|
||||
finish_same(s, cmd, ids)
|
||||
}
|
||||
|
||||
/// The type objects among the selected objects and inside them, in selection order, leaving out
|
||||
/// every hidden or locked object with what it holds.
|
||||
/// The type objects among the selected objects and inside them, in selection order, keeping only
|
||||
/// those [`walk_selectable`] visits: none that is hidden, locked, on a hidden, locked or template
|
||||
/// layer, or outside the isolated group.
|
||||
fn selected_text(s: &Session) -> Vec<NodeId> {
|
||||
fn inside(n: &Node, out: &mut Vec<NodeId>) {
|
||||
if !n.visible || n.locked {
|
||||
@@ -338,8 +339,12 @@ fn selected_text(s: &Session) -> Vec<NodeId> {
|
||||
}
|
||||
let Some(st) = s.active() else { return vec![] };
|
||||
let mut out = vec![];
|
||||
for n in st.selection.objects.iter().filter_map(|id| st.doc.node(*id)) {
|
||||
inside(n, &mut out);
|
||||
for id in &st.selection.objects {
|
||||
if is_selectable(&st.doc, st.isolation, *id)
|
||||
&& let Some(n) = st.doc.node(*id)
|
||||
{
|
||||
inside(n, &mut out);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -249,6 +249,25 @@ pub(crate) fn selectable_ids(d: &Document, iso: Option<NodeId>) -> BTreeSet<Node
|
||||
ids
|
||||
}
|
||||
|
||||
/// Whether [`walk_selectable`] visits `id`, read from the objects that hold it instead of a walk of
|
||||
/// the document.
|
||||
pub(crate) fn is_selectable(d: &Document, iso: Option<NodeId>, id: NodeId) -> bool {
|
||||
let Some(a) = d.ancestry(id) else { return false };
|
||||
if !a.iter().all(|i| d.node(*i).is_some_and(|n| n.visible && !n.locked && !n.is_template())) {
|
||||
return false;
|
||||
}
|
||||
match iso.and_then(|i| d.node(i)) {
|
||||
// Inside the isolated container, or the single object isolated.
|
||||
Some(c) => a.iter().position(|i| *i == c.id).is_some_and(|p| p + 1 < a.len() || !c.is_container()),
|
||||
None => {
|
||||
let locks = d.locked_artboards();
|
||||
// The object Select All takes: the outermost one that is not a layer.
|
||||
let top = a.iter().filter_map(|i| d.node(*i)).find(|n| !n.is_layer());
|
||||
top.is_some_and(|t| locks.is_empty() || !locks.holds(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn all(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
// While the Type tool edits text, Select All takes all of that text and leaves the art
|
||||
// selection as it is (Illustrator: text cursor in a text object).
|
||||
|
||||
@@ -1993,6 +1993,22 @@ fn select_same_link_block_series_selects_the_frames_of_a_thread() {
|
||||
assert!(s.execute("select.same.linkBlockSeries", &json!({})).is_err(), "{props}");
|
||||
assert_eq!(selected(&s), vec![g], "{props}");
|
||||
}
|
||||
s.execute("layer.setProps", &json!({"id": d.0, "locked": false})).unwrap();
|
||||
// A frame on a hidden layer, selected by id, is not type to start from.
|
||||
let l1 = s.doc().unwrap().doc.layers[0].id;
|
||||
s.execute("layer.setProps", &json!({"id": l1.0, "visible": false})).unwrap();
|
||||
sel(&mut s, &[a]);
|
||||
assert!(!find_command("select.same.linkBlockSeries").unwrap().info(&s).enabled);
|
||||
assert!(s.execute("select.same.linkBlockSeries", &json!({})).is_err());
|
||||
s.execute("layer.setProps", &json!({"id": l1.0, "visible": true})).unwrap();
|
||||
// In an isolated group, only the frames inside it count and are selected.
|
||||
s.execute("object.isolate", &json!({"id": g.0})).unwrap();
|
||||
sel(&mut s, &[d]);
|
||||
assert_eq!(s.execute("select.same.linkBlockSeries", &json!({})).unwrap()["count"], 1);
|
||||
assert_eq!(selected(&s), vec![d]);
|
||||
sel(&mut s, &[a]);
|
||||
assert!(!find_command("select.same.linkBlockSeries").unwrap().info(&s).enabled);
|
||||
assert!(s.execute("select.same.linkBlockSeries", &json!({})).is_err());
|
||||
}
|
||||
|
||||
/// #1737: Select > Same reads type's fill and stroke from its characters (the first run), so type
|
||||
|
||||
Reference in New Issue
Block a user