From 5987c449b2017ed60a24fc4a43be3b3c22f83999 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 26 Aug 2026 19:43:31 +0000 Subject: [PATCH] Serve bounding boxes through the lane source --- node-graph/libraries/core-types/src/bounds.rs | 58 +++++++++++++++++++ node-graph/libraries/core-types/src/list.rs | 41 +------------ node-graph/libraries/core-types/src/record.rs | 10 ++++ 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/node-graph/libraries/core-types/src/bounds.rs b/node-graph/libraries/core-types/src/bounds.rs index f4b673e850..e0efbec699 100644 --- a/node-graph/libraries/core-types/src/bounds.rs +++ b/node-graph/libraries/core-types/src/bounds.rs @@ -1,4 +1,5 @@ use crate::Color; +use crate::lane::LaneSource; use glam::{DAffine2, DVec2}; #[derive(Clone, Copy, Default, Debug, PartialEq)] @@ -51,3 +52,60 @@ impl BoundingBox for Color { RenderBoundingBox::Infinite } } + +/// Combined bounding box of a lane source's elements, composing each lane's +/// transform attribute with the given transform. +pub fn lane_bounding_box(source: &S, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox +where + S::Element: BoundingBox, +{ + let mut combined_bounds = None; + + for lane in 0..source.lane_count() { + let Some(element) = source.element(lane) else { continue }; + let lane_transform: DAffine2 = source.attr::(lane); + match element.bounding_box(transform * lane_transform, include_stroke) { + RenderBoundingBox::None => continue, + RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite, + RenderBoundingBox::Rectangle(bounds) => match combined_bounds { + Some(existing) => combined_bounds = Some(crate::math::quad::Quad::combine_bounds(existing, bounds)), + None => combined_bounds = Some(bounds), + }, + } + } + + match combined_bounds { + Some(bounds) => RenderBoundingBox::Rectangle(bounds), + None => RenderBoundingBox::None, + } +} + +/// As [`lane_bounding_box`], but `Infinite` lanes are skipped (rather than +/// propagating outward) so a finite sibling in a mixed group dictates the +/// framing. +pub fn lane_thumbnail_bounding_box(source: &S, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox +where + S::Element: BoundingBox, +{ + let mut combined_bounds = None; + let mut any_infinite = false; + + for lane in 0..source.lane_count() { + let Some(element) = source.element(lane) else { continue }; + let lane_transform: DAffine2 = source.attr::(lane); + match element.thumbnail_bounding_box(transform * lane_transform, include_stroke) { + RenderBoundingBox::None => continue, + RenderBoundingBox::Infinite => any_infinite = true, + RenderBoundingBox::Rectangle(bounds) => match combined_bounds { + Some(existing) => combined_bounds = Some(crate::math::quad::Quad::combine_bounds(existing, bounds)), + None => combined_bounds = Some(bounds), + }, + } + } + + match (combined_bounds, any_infinite) { + (Some(bounds), _) => RenderBoundingBox::Rectangle(bounds), + (None, true) => RenderBoundingBox::Infinite, + (None, false) => RenderBoundingBox::None, + } +} diff --git a/node-graph/libraries/core-types/src/list.rs b/node-graph/libraries/core-types/src/list.rs index d685c29fad..788273fab7 100644 --- a/node-graph/libraries/core-types/src/list.rs +++ b/node-graph/libraries/core-types/src/list.rs @@ -1,6 +1,5 @@ use crate::attribute::Attribute as _; use crate::bounds::{BoundingBox, RenderBoundingBox}; -use crate::math::quad::Quad; use crate::transform::ApplyTransform; use dyn_any::{StaticType, StaticTypeSized}; use glam::DAffine2; @@ -1105,48 +1104,12 @@ impl List { } impl BoundingBox for List { - /// Computes the combined bounding box of all items, composing each item's transform attribute with the given transform. fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox { - let mut combined_bounds = None; - - for (element, item_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_TRANSFORM)) { - match element.bounding_box(transform * item_transform, include_stroke) { - RenderBoundingBox::None => continue, - RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite, - RenderBoundingBox::Rectangle(bounds) => match combined_bounds { - Some(existing) => combined_bounds = Some(Quad::combine_bounds(existing, bounds)), - None => combined_bounds = Some(bounds), - }, - } - } - - match combined_bounds { - Some(bounds) => RenderBoundingBox::Rectangle(bounds), - None => RenderBoundingBox::None, - } + crate::bounds::lane_bounding_box(self, transform, include_stroke) } fn thumbnail_bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox { - // `Infinite` items are skipped here (rather than propagating outward as in `bounding_box`) so a finite sibling in a mixed group dictates the framing - let mut combined_bounds = None; - let mut any_infinite = false; - - for (element, item_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_TRANSFORM)) { - match element.thumbnail_bounding_box(transform * item_transform, include_stroke) { - RenderBoundingBox::None => continue, - RenderBoundingBox::Infinite => any_infinite = true, - RenderBoundingBox::Rectangle(bounds) => match combined_bounds { - Some(existing) => combined_bounds = Some(Quad::combine_bounds(existing, bounds)), - None => combined_bounds = Some(bounds), - }, - } - } - - match (combined_bounds, any_infinite) { - (Some(bounds), _) => RenderBoundingBox::Rectangle(bounds), - (None, true) => RenderBoundingBox::Infinite, - (None, false) => RenderBoundingBox::None, - } + crate::bounds::lane_thumbnail_bounding_box(self, transform, include_stroke) } } diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index 54cbb1986b..15021281bb 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -1919,6 +1919,16 @@ impl<'a, T: 'static> crate::lane::LaneSource for RunView<'a, T> { } } +impl crate::bounds::BoundingBox for RunView<'_, T> { + fn bounding_box(&self, transform: glam::DAffine2, include_stroke: bool) -> crate::bounds::RenderBoundingBox { + crate::bounds::lane_bounding_box(self, transform, include_stroke) + } + + fn thumbnail_bounding_box(&self, transform: glam::DAffine2, include_stroke: bool) -> crate::bounds::RenderBoundingBox { + crate::bounds::lane_thumbnail_bounding_box(self, transform, include_stroke) + } +} + /// The records a group stores: a single homogeneous run, or a list of /// segments. #[derive(Clone, Debug)]