mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3271 from FractalEncrypt/fix/live-trace-bounds
Exclude retained trace bitmaps from result bounds and highlights
This commit is contained in:
+15
-8
@@ -1159,6 +1159,17 @@ impl Node {
|
||||
pub fn geometric_bounds_with(&self, symbols: &SymbolBoxes) -> Option<Rect> {
|
||||
self.geometric(Some(symbols))
|
||||
}
|
||||
/// Children that contribute to a container's editing bounds and selection highlights. A live trace retains a
|
||||
/// hidden bitmap for re-tracing/release; only source-image views measure that bitmap.
|
||||
pub fn editing_children(&self) -> &[Arc<Node>] {
|
||||
let children = self.children().map(Vec::as_slice).unwrap_or_default();
|
||||
let trace_source = self.extras().trace.is_some()
|
||||
&& !self.trace_view().shows_source()
|
||||
&& children.first().is_some_and(|n| !n.visible && matches!(n.kind, NodeKind::Image(_)));
|
||||
let skip = usize::from(self.extras().shaper.is_some() || trace_source);
|
||||
children.get(skip..).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The box of [`Node::geometric_bounds`], or of [`Node::geometric_bounds_with`] when `symbols`
|
||||
/// is given.
|
||||
pub(crate) fn geometric(&self, symbols: Option<&SymbolBoxes>) -> Option<Rect> {
|
||||
@@ -1168,9 +1179,9 @@ impl Node {
|
||||
NodeKind::Group { children, clip: true } | NodeKind::Layer { children, clip: true, .. } => {
|
||||
children.first().and_then(|c| c.geometric(symbols))
|
||||
}
|
||||
NodeKind::Layer { children, .. } | NodeKind::Group { children, .. } | NodeKind::Compound { children, .. } => children
|
||||
NodeKind::Layer { .. } | NodeKind::Group { .. } | NodeKind::Compound { .. } => self
|
||||
.editing_children()
|
||||
.iter()
|
||||
.skip(usize::from(self.extras().shaper.is_some()))
|
||||
.filter(|c| c.visible || !matches!(self.kind, NodeKind::Layer { .. }))
|
||||
.fold(None, |acc, c| vectorcraft_geom::union_opt(acc, c.geometric(symbols))),
|
||||
NodeKind::Text(t) => self.projected(t.bounds()),
|
||||
@@ -1223,14 +1234,10 @@ impl Node {
|
||||
}
|
||||
}
|
||||
NodeKind::Intertwine { children } => children.iter().fold(None, |acc, c| vectorcraft_geom::union_opt(acc, c.visual(symbols))),
|
||||
NodeKind::Layer { children, .. } | NodeKind::Group { children, .. } => {
|
||||
NodeKind::Layer { .. } | NodeKind::Group { .. } => {
|
||||
// The container's own strokes paint around its members.
|
||||
let o = self.appearance.outset();
|
||||
children
|
||||
.iter()
|
||||
.skip(usize::from(self.extras().shaper.is_some()))
|
||||
.fold(None, |acc, c| vectorcraft_geom::union_opt(acc, c.visual(symbols)))
|
||||
.map(|b| b.inflate(o, o))
|
||||
self.editing_children().iter().fold(None, |acc, c| vectorcraft_geom::union_opt(acc, c.visual(symbols))).map(|b| b.inflate(o, o))
|
||||
}
|
||||
NodeKind::Blend { children, spec } => {
|
||||
let b = children.iter().fold(None, |acc, c| vectorcraft_geom::union_opt(acc, c.visual(symbols)));
|
||||
|
||||
@@ -90,8 +90,8 @@ impl Node {
|
||||
/// ([`Node::geometric_bounds_with`]).
|
||||
pub fn bounds_in(&self, symbols: &SymbolBoxes, a: Affine, visual: bool) -> Option<Rect> {
|
||||
match &self.kind {
|
||||
NodeKind::Group { children, clip: false } => {
|
||||
let b = children.iter().fold(None, |acc, c| union_opt(acc, c.bounds_in(symbols, a, visual)));
|
||||
NodeKind::Group { clip: false, .. } => {
|
||||
let b = self.editing_children().iter().fold(None, |acc, c| union_opt(acc, c.bounds_in(symbols, a, visual)));
|
||||
// The group's own strokes paint around its members (as in `visual_bounds`).
|
||||
let o = if visual { self.appearance.outset() } else { 0.0 };
|
||||
b.map(|b| b.inflate(o, o))
|
||||
|
||||
@@ -89,4 +89,53 @@ mod tests {
|
||||
g.extras_mut().trace = Some(Box::new(serde_json::json!({"view": "sepia"})));
|
||||
assert_eq!(g.trace_view(), TraceView::Result);
|
||||
}
|
||||
#[test]
|
||||
fn live_trace_result_bounds_exclude_the_retained_bitmap_at_any_angle() {
|
||||
use crate::{Appearance, Document, ImageObject, NodeId, NodeKind};
|
||||
use std::sync::Arc;
|
||||
use vectorcraft_geom::{Affine, PathData, Point, Rect, SubPath};
|
||||
let mut source = Node::new(
|
||||
NodeId(2),
|
||||
NodeKind::Image(Box::new(ImageObject {
|
||||
key: "synthetic".into(),
|
||||
width: 200,
|
||||
height: 200,
|
||||
xf: Affine::IDENTITY,
|
||||
link: None,
|
||||
placement: Default::default(),
|
||||
})),
|
||||
);
|
||||
source.visible = false;
|
||||
let art = Node::path(
|
||||
NodeId(3),
|
||||
PathData::single(SubPath::polyline(
|
||||
&[Point::new(20.0, 30.0), Point::new(80.0, 30.0), Point::new(80.0, 90.0), Point::new(20.0, 90.0)],
|
||||
true,
|
||||
)),
|
||||
Appearance::basic(vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK), vectorcraft_color::Paint::None, 0.0),
|
||||
);
|
||||
let mut group = Node::group(NodeId(1), vec![Arc::new(source), Arc::new(art.clone())]);
|
||||
group.extras_mut().trace = Some(Box::new(serde_json::json!({})));
|
||||
let doc = Document::new(300.0, 300.0);
|
||||
for view in TraceView::ALL {
|
||||
group.extras_mut().trace = Some(Box::new(serde_json::json!({"view":view.id()})));
|
||||
let want = if view.shows_source() { Rect::new(0.0, 0.0, 200.0, 200.0) } else { Rect::new(20.0, 30.0, 80.0, 90.0) };
|
||||
assert_eq!(group.geometric_bounds(), Some(want));
|
||||
assert_eq!(group.visual_bounds(), Some(want));
|
||||
if !view.shows_source() {
|
||||
for angle in [0.0_f64, 30.0, 90.0] {
|
||||
let xf = Affine::rotate(angle.to_radians());
|
||||
for visual in [false, true] {
|
||||
assert_eq!(group.bounds_in(&doc.symbol_boxes(), xf, visual), art.bounds_in(&doc.symbol_boxes(), xf, visual));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
group.extras_mut().trace = None;
|
||||
assert_eq!(
|
||||
group.geometric_bounds(),
|
||||
Some(Rect::new(0.0, 0.0, 200.0, 200.0)),
|
||||
"ordinary hidden group members retain their existing bounds policy"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1884,8 +1884,7 @@ fn too_many_anchors<'a>(nodes: impl IntoIterator<Item = &'a Node>) -> bool {
|
||||
if n.clips() {
|
||||
return n.children().and_then(|children| children.first()).is_some_and(|clip| over(clip, left));
|
||||
}
|
||||
!matches!(n.kind, NodeKind::Envelope { .. })
|
||||
&& n.children().into_iter().flatten().skip(usize::from(n.extras().shaper.is_some())).any(|c| over(c, left))
|
||||
!matches!(n.kind, NodeKind::Envelope { .. }) && n.editing_children().iter().any(|c| over(c, left))
|
||||
}
|
||||
let mut left = OVERLAY_MAX_ANCHORS;
|
||||
nodes.into_iter().any(|n| over(n, &mut left))
|
||||
@@ -1929,7 +1928,7 @@ fn walk_drawn<'a>(n: &'a Node, f: &mut impl FnMut(&'a Node)) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
for c in n.children().into_iter().flatten().skip(usize::from(n.extras().shaper.is_some())) {
|
||||
for c in n.editing_children().iter() {
|
||||
walk_drawn(c, f);
|
||||
}
|
||||
}
|
||||
@@ -8245,4 +8244,40 @@ mod tests {
|
||||
assert!((after - want).length() < 0.5, "bar {k}: {before:?} -> {after:?}, want {want:?}");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn live_trace_selection_highlight_excludes_the_retained_source_rectangle() {
|
||||
use std::sync::Arc;
|
||||
use vectorcraft_doc::{Appearance, ImageObject, NodeId, TraceView};
|
||||
let mut source = Node::new(
|
||||
NodeId(2),
|
||||
NodeKind::Image(Box::new(ImageObject {
|
||||
key: "synthetic".into(),
|
||||
width: 200,
|
||||
height: 200,
|
||||
xf: Affine::IDENTITY,
|
||||
link: None,
|
||||
placement: Default::default(),
|
||||
})),
|
||||
);
|
||||
source.visible = false;
|
||||
let art =
|
||||
Node::path(NodeId(3), vectorcraft_geom::shapes::rectangle(vectorcraft_geom::Rect::new(20.0, 30.0, 80.0, 90.0)), Appearance::default());
|
||||
let mut group = Node::group(NodeId(1), vec![Arc::new(source), Arc::new(art)]);
|
||||
let doc = vectorcraft_doc::Document::new(300.0, 300.0);
|
||||
let symbols = doc.symbol_boxes();
|
||||
for view in TraceView::ALL {
|
||||
group.extras_mut().trace = Some(Box::new(json!({"view":view.id()})));
|
||||
let mut visited = vec![];
|
||||
walk_drawn(&group, &mut |n| visited.push(n.id));
|
||||
assert_eq!(visited.contains(&NodeId(2)), view.shows_source());
|
||||
let want = if view.shows_source() {
|
||||
vectorcraft_geom::Rect::new(0.0, 0.0, 200.0, 200.0)
|
||||
} else {
|
||||
vectorcraft_geom::Rect::new(20.0, 30.0, 80.0, 90.0)
|
||||
};
|
||||
assert_eq!(node_outline(&group, &symbols).bounding_box(), want);
|
||||
}
|
||||
group.extras_mut().trace = None;
|
||||
assert_eq!(node_outline(&group, &symbols).bounding_box(), vectorcraft_geom::Rect::new(0.0, 0.0, 200.0, 200.0));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
File diff suppressed because one or more lines are too long
+5
-4
File diff suppressed because one or more lines are too long
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