mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
Ruler guides were a list kept beside the layers. Each guide is now an object of its layer or sublayer, always directly in it and above the layer's art. These behave as on main: - Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All, Select All, the art bounds, clicks and marquees, and a layer's Pathfinder and Transform effects leave them out. The Layers panel lists them as guide rows of their layer, the highest guide.list index first. - Deleting a layer deletes its guides, and merging layers keeps their guides in the merged layer. - The guide commands number guides as guide.list does, in the order they were made. New: each guide takes an id from the document's object ids. An object command given a guide's id fails with an error, and node.move cannot put a guide into a group or out of its layers. A guide made while an opacity mask, a symbol or a pattern is edited moves to the top visible, unlocked layer when editing ends. File format: native files still save the guides in the document's guides list, in every file version, so older builds read them. Each entry may now also carry the guide's id, name, locked and hidden flags; older builds ignore those fields. Opening a file puts each saved guide on its layer (on the top visible, unlocked layer when the file names none or a layer it does not have), and each saved guide counts against the object limit for opening a file. A ruler guide node that a file holds below a layer's art moves up to the layer's guides, and one anywhere else (in a group or another object, beside the layers, or in a symbol) is removed. Ruler guides read from an .ai file become guide objects on the layer they were on, numbered in the order the file lists them.
61 lines
2.9 KiB
Rust
61 lines
2.9 KiB
Rust
use super::{Document, MAX_COORD, NodeId, NodeKind, Selection, cmd};
|
|
|
|
/// Is `id` a path made into a guide (View → Guides → Make Guides)?
|
|
pub(crate) fn is_guide_path(d: &Document, id: NodeId) -> bool {
|
|
d.node(id).is_some_and(|n| matches!(n.kind, NodeKind::Path { guide: true, .. }))
|
|
}
|
|
|
|
/// Deselect the ruler guides and the paths made into guides: while View → Guides → Lock Guides is
|
|
/// on, they can't be selected (#1534), whatever selects them. → whether any was selected.
|
|
pub(crate) fn deselect_guides(d: &Document, sel: &mut Selection) -> bool {
|
|
let paths: Vec<NodeId> = sel.objects.iter().copied().filter(|id| is_guide_path(d, *id)).collect();
|
|
let any = !paths.is_empty() || !sel.guides.is_empty();
|
|
for id in paths {
|
|
sel.remove(id);
|
|
}
|
|
sel.guides.clear();
|
|
sel.key_guide = None;
|
|
// A key object kept only for the guides to line up on goes with them.
|
|
if sel.key.is_some_and(|k| sel.objects.iter().all(|o| *o == k)) {
|
|
sel.key = None;
|
|
}
|
|
any
|
|
}
|
|
|
|
/// Deselect what the View menu keeps from being selected: the guides while Lock Guides is on
|
|
/// ([`deselect_guides`]), and the slices that the Slice Selection tool or a slice command selected
|
|
/// ([`Selection::slices`]) while Lock Slices or Hide Slices is on. A selected object that is an
|
|
/// object slice stays selected. → whether any was selected.
|
|
pub(crate) fn deselect_hidden(menu: &cmd::menucmds::MenuState, d: &Document, sel: &mut Selection) -> bool {
|
|
let guides = menu.guides_locked && deselect_guides(d, sel);
|
|
let slices = (menu.slices_locked || menu.slices_hidden) && !sel.slices.is_empty();
|
|
if slices {
|
|
sel.slices.clear();
|
|
}
|
|
guides || slices
|
|
}
|
|
|
|
/// The error of an edit that would nest objects deeper than [`vectorcraft_doc::MAX_DEPTH`].
|
|
pub const TOO_DEEP: &str = "objects can't be nested more than 32 deep";
|
|
|
|
/// Cheap sanity check after an edit: artboards and the objects just touched (the selection) must
|
|
/// have finite, in-range geometry, so saved files always reload and renderers never see NaN/∞.
|
|
pub(crate) fn doc_sane(d: &Document, sel: &Selection) -> bool {
|
|
d.ruler_origin.is_finite()
|
|
&& d.ruler_origin.x.abs() <= MAX_COORD
|
|
&& d.ruler_origin.y.abs() <= MAX_COORD
|
|
&& d.artboards.iter().all(|a| rect_in_range(a.rect))
|
|
&& d.ruler_guides().iter().all(|g| g.pos.is_finite() && g.pos.abs() <= MAX_COORD)
|
|
&& objects_in_range(d, &sel.objects)
|
|
}
|
|
|
|
fn rect_in_range(r: vectorcraft_geom::Rect) -> bool {
|
|
[r.x0, r.y0, r.x1, r.y1].iter().all(|v| v.is_finite() && v.abs() <= MAX_COORD)
|
|
}
|
|
|
|
/// Do the objects `ids` have finite geometry within [`MAX_COORD`] (the ones not there pass)?
|
|
pub(crate) fn objects_in_range(d: &Document, ids: &[NodeId]) -> bool {
|
|
let paths = d.index_paths(ids.iter().copied());
|
|
ids.iter().all(|id| paths.get(id).and_then(|p| d.node_at(p)).and_then(|n| n.geometric_bounds()).is_none_or(rect_in_range))
|
|
}
|