Object: Cmd/Ctrl+Alt+Shift+3 hides every unselected object

Squashed from #4418 onto main.
This commit is contained in:
dotCooCoo
2026-10-29 15:16:05 +07:00
committed by bflatastic
parent 20c9b363dd
commit 6d66eaf3dc
31 changed files with 232 additions and 4 deletions
+50 -1
View File
@@ -13,7 +13,7 @@ use vectorcraft_geom::{Affine, Anchor, PathData, Point, Rect, Vec2};
use vectorcraft_render::AntiAlias;
use vectorcraft_render::encode::{RasterExportOptions, RasterFormat};
use super::edit::{duplicate_in, selected_roots};
use super::edit::{duplicate_in, selected_roots, selected_roots_to_transform};
use super::{
CommandSpec, EngineError, Result, Session, bad, check_numbers, check_switches, cmd, f64_or, fileio, find_command, has_doc, has_selection,
has_selection_outside_isolation, switch_or,
@@ -74,6 +74,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection_outside_isolation,
|s, _| other_layers(s, false)
),
cmd!(
"object.hide.unselected",
"Unselected Artwork",
["Object", "Hide"],
Some("Cmd+Alt+Shift+3"),
"{} hide, in one undo step, every object select.all takes that is not selected and holds no selected object; in a group that holds one it hides the other members one by one, and a clip group, compound path or other object that holds one stays as it is; guide paths stay as they are while Lock Guides is on; object.showAll shows them again; disabled in isolation mode → {count}",
has_selection_outside_isolation,
hide_unselected
),
cmd!(
"object.transformEach",
"Transform Each…",
@@ -312,6 +321,46 @@ fn other_layers(s: &mut Session, lock: bool) -> Result<Value> {
Ok(json!({ "count": n }))
}
/// The objects [`super::select::walk_selectable`] visits that are not among the selected objects `roots` and hold none of
/// them. The walk goes into a plain group that holds one of `roots`; a clip group, a Shaper group, a compound path and
/// every other object that holds one is left out with its members. Guide paths are left out while `guides_locked`.
fn unselected(d: &vectorcraft_doc::Document, iso: Option<NodeId>, roots: &[NodeId], guides_locked: bool) -> Vec<NodeId> {
let holds: HashSet<NodeId> = d.index_paths(roots.iter().copied()).values().filter_map(|p| d.ancestry_at(p)).flatten().collect();
let roots: HashSet<NodeId> = roots.iter().copied().collect();
let mut out = vec![];
super::select::walk_selectable(d, iso, |n| {
if !holds.contains(&n.id) {
if !(guides_locked && matches!(n.kind, NodeKind::Path { guide: true, .. })) {
out.push(n.id);
}
return false;
}
!roots.contains(&n.id) && matches!(n.kind, NodeKind::Group { clip: false, .. }) && n.extras().shaper.is_none()
});
out
}
fn hide_unselected(s: &mut Session, _: &Value) -> Result<Value> {
let roots = selected_roots_to_transform(s)?;
let guides_locked = s.menu.guides_locked;
let st = s.doc()?;
let ids = unselected(&st.doc, st.isolation, &roots, guides_locked);
// With nothing to hide, the document and its undo history stay as they are.
if !ids.is_empty() {
s.edit("Hide", |d, _| {
// Hiding moves no node in the tree, so the paths found up front stay right.
let paths = d.index_paths(ids.iter().copied());
for id in &ids {
if let Some(n) = paths.get(id).and_then(|p| d.node_mut_at(p)) {
n.visible = false;
}
}
Ok(())
})?;
}
Ok(json!({ "count": ids.len() }))
}
// ---------- Transform Each ----------
/// Small deterministic PRNG (xorshift64*) for Transform Each → Random.