From d6f46f73daddbe54852de276be26a9976e07e794 Mon Sep 17 00:00:00 2001 From: James Lindsay <78500760+0HyperCube@users.noreply.github.com> Date: Sun, 13 Oct 2024 00:38:17 +0100 Subject: [PATCH] Factor in artboard clipping to the click target x-ray function (#2028) * Add clipping to the click xray function * Clip area rays as well * Code review nits --------- Co-authored-by: Keavon Chambers --- editor/src/dispatcher.rs | 7 +- .../portfolio/document/document_message.rs | 3 + .../document/document_message_handler.rs | 168 ++++++++++++++++-- .../node_graph/node_graph_message_handler.rs | 6 +- .../utility_types/document_metadata.rs | 8 +- .../utility_types/network_interface.rs | 5 + .../tool/tool_messages/select_tool.rs | 6 +- .../gcore/src/graphic_element/renderer.rs | 32 ++-- node-graph/gcore/src/vector/vector_data.rs | 20 +-- node-graph/gstd/src/vector.rs | 3 +- node-graph/gstd/src/wasm_application_io.rs | 3 +- 11 files changed, 204 insertions(+), 57 deletions(-) diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 639391ea..b10aa32a 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -110,7 +110,11 @@ impl Dispatcher { self.message_queues.extend(buffered_queue); }; - let graphene_std::renderer::RenderMetadata { footprints, click_targets } = render_metadata; + let graphene_std::renderer::RenderMetadata { + footprints, + click_targets, + clip_targets, + } = render_metadata; let mut update_upstream_transform = VecDeque::new(); update_upstream_transform.push_back(DocumentMessage::UpdateUpstreamTransforms { upstream_transforms: footprints }.into()); @@ -118,6 +122,7 @@ impl Dispatcher { let mut update_click_targets = VecDeque::new(); update_click_targets.push_back(DocumentMessage::UpdateClickTargets { click_targets }.into()); + update_click_targets.push_back(DocumentMessage::UpdateClipTargets { clip_targets }.into()); self.message_queues.push(update_click_targets); } Message::NoOp => {} diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 7a7c87db..c23c2381 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -162,6 +162,9 @@ pub enum DocumentMessage { UpdateClickTargets { click_targets: HashMap>, }, + UpdateClipTargets { + clip_targets: HashSet, + }, UpdateVectorModify { vector_modify: HashMap, }, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 9dc270f0..b3451a75 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -29,6 +29,8 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeNetwork, OldNodeNetwork}; use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::vector::style::ViewMode; +use graphene_std::renderer::{ClickTarget, Quad}; +use graphene_std::vector::path_bool_lib; use glam::{DAffine2, DVec2, IVec2}; @@ -1109,6 +1111,9 @@ impl MessageHandler> for DocumentMessag .collect(); self.network_interface.update_click_targets(layer_click_targets); } + DocumentMessage::UpdateClipTargets { clip_targets } => { + self.network_interface.update_clip_targets(clip_targets); + } DocumentMessage::UpdateVectorModify { vector_modify } => { self.network_interface.update_vector_modify(vector_modify); } @@ -1333,31 +1338,19 @@ impl DocumentMessageHandler { let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); let document_quad = document_to_viewport.inverse() * viewport_quad; - self.metadata() - .all_layers() - .filter(|&layer| self.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &self.network_interface)) - .filter(|&layer| !self.network_interface.selected_nodes(&[]).unwrap().layer_locked(layer, &self.network_interface)) - .filter(|&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) - .filter_map(|layer| self.metadata().click_targets(layer).map(|targets| (layer, targets))) - .filter(move |(layer, target)| { - target - .iter() - .any(move |target| target.intersect_rectangle(document_quad, self.metadata().transform_to_document(*layer))) - }) - .map(|(layer, _)| layer) + ClickXRayIter::new(&self.network_interface, XRayTarget::Quad(document_quad)) + } + + /// Runs an intersection test with all layers and a viewport space quad; ignoring artboards + pub fn intersect_quad_no_artboards<'a>(&'a self, viewport_quad: graphene_core::renderer::Quad, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + 'a { + self.intersect_quad(viewport_quad, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) } /// Find all of the layers that were clicked on from a viewport space location pub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator + '_ { let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz); let point = document_to_viewport.inverse().transform_point2(ipp.mouse.position); - self.metadata() - .all_layers() - .filter(|&layer| self.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &self.network_interface)) - .filter(|&layer| !self.network_interface.selected_nodes(&[]).unwrap().layer_locked(layer, &self.network_interface)) - .filter_map(|layer| self.metadata().click_targets(layer).map(|targets| (layer, targets))) - .filter(move |(layer, target)| target.iter().any(|target| target.intersect_point(point, self.metadata().transform_to_document(*layer)))) - .map(|(layer, _)| layer) + ClickXRayIter::new(&self.network_interface, XRayTarget::Point(point)) } /// Find the deepest layer given in the sorted array (by returning the one which is not a folder from the list of layers under the click location). @@ -2099,3 +2092,140 @@ fn default_document_network_interface() -> NodeNetworkInterface { network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "".to_string(), &[]); network_interface } + +/// Targets for the [`ClickXRayIter`]. In order to reduce computation, we prefer just a point/path test where possible. +#[derive(Clone)] +enum XRayTarget { + Point(DVec2), + Quad(Quad), + Path(Vec), +} + +/// The result for the [`ClickXRayIter`] on the layer +struct XRayResult { + clicked: bool, + use_children: bool, +} + +/// An iterator for finding layers within an [`XRayTarget`]. Constructed by [`DocumentMessageHandler::intersect_quad`] and [`DocumentMessageHandler::click_xray`]. +#[derive(Clone)] +pub struct ClickXRayIter<'a> { + next_layer: Option, + network_interface: &'a NodeNetworkInterface, + parent_targets: Vec<(LayerNodeIdentifier, XRayTarget)>, +} + +fn quad_to_path_lib_segments(quad: Quad) -> Vec { + quad.edges().into_iter().map(|[start, end]| path_bool_lib::PathSegment::Line(start, end)).collect() +} + +fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator, transform: DAffine2) -> Vec { + let segment = |bezier: bezier_rs::Bezier| match bezier.handles { + bezier_rs::BezierHandles::Linear => path_bool_lib::PathSegment::Line(bezier.start, bezier.end), + bezier_rs::BezierHandles::Quadratic { handle } => path_bool_lib::PathSegment::Quadratic(bezier.start, handle, bezier.end), + bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path_bool_lib::PathSegment::Cubic(bezier.start, handle_start, handle_end, bezier.end), + }; + click_targets + .flat_map(|target| target.subpath().iter()) + .map(|bezier| segment(bezier.apply_transformation(|x| transform.transform_point2(x)))) + .collect() +} + +impl<'a> ClickXRayIter<'a> { + fn new(network_interface: &'a NodeNetworkInterface, target: XRayTarget) -> Self { + Self { + next_layer: LayerNodeIdentifier::ROOT_PARENT.first_child(network_interface.document_metadata()), + network_interface, + parent_targets: vec![(LayerNodeIdentifier::ROOT_PARENT, target)], + } + } + + /// Handles the checking of the layer where the target is a rect or path + fn check_layer_area_target(&mut self, click_targets: Option<&Vec>, clip: bool, layer: LayerNodeIdentifier, path: Vec, transform: DAffine2) -> XRayResult { + // Convert back to Bezier-rs types for intersections + let segment = |bezier: &path_bool_lib::PathSegment| match *bezier { + path_bool_lib::PathSegment::Line(start, end) => bezier_rs::Bezier::from_linear_dvec2(start, end), + path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => bezier_rs::Bezier::from_cubic_dvec2(start, h1, h2, end), + path_bool_lib::PathSegment::Quadratic(start, h1, end) => bezier_rs::Bezier::from_quadratic_dvec2(start, h1, end), + path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(), + }; + let get_clip = || path.iter().map(segment); + + let intersects = click_targets.map_or(false, |targets| targets.iter().any(|target| target.intersect_path(get_clip, transform))); + let clicked = intersects; + let mut use_children = !clip || intersects; + + // In the case of a clip path where the area partially intersects, it is necessary to do a boolean operation. + // We do this on this using the target area to reduce computation (as the target area is usually very simple). + if clip && intersects { + let clip_path = click_targets_to_path_lib_segments(click_targets.iter().flat_map(|x| x.iter()), transform); + let subtracted = graphene_std::vector::boolean_intersect(path, clip_path).into_iter().flatten().collect::>(); + if subtracted.is_empty() { + use_children = false; + } else { + // All child layers will use the new clipped target area + self.parent_targets.push((layer, XRayTarget::Path(subtracted))); + } + } + XRayResult { clicked, use_children } + } + + /// Handles the checking of the layer to find if it has been clicked + fn check_layer(&mut self, layer: LayerNodeIdentifier) -> XRayResult { + let selected_layers = self.network_interface.selected_nodes(&[]).unwrap(); + // Discard invisible and locked layers + if !selected_layers.layer_visible(layer, self.network_interface) || selected_layers.layer_locked(layer, self.network_interface) { + return XRayResult { clicked: false, use_children: false }; + } + + let click_targets = self.network_interface.document_metadata().click_targets(layer); + let transform = self.network_interface.document_metadata().transform_to_document(layer); + let target = &self.parent_targets.last().expect("In `check_layer()`: there should be a `target`").1; + let clip = self.network_interface.document_metadata().is_clip(layer.to_node()); + + match target { + // Single points are much cheaper than paths so have their own special case + XRayTarget::Point(point) => { + let intersects = click_targets.map_or(false, |targets| targets.iter().any(|target| target.intersect_point(*point, transform))); + XRayResult { + clicked: intersects, + use_children: !clip || intersects, + } + } + XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform), + XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform), + } + } +} + +impl<'a> Iterator for ClickXRayIter<'a> { + type Item = LayerNodeIdentifier; + + fn next(&mut self) -> Option { + // While there are still layers in the layer tree + while let Some(layer) = self.next_layer.take() { + let XRayResult { clicked, use_children } = self.check_layer(layer); + let metadata = self.network_interface.document_metadata(); + // If we should use the children and also there is a child, that child is the next layer. + self.next_layer = use_children.then(|| layer.first_child(metadata)).flatten(); + + // If we aren't using children, iterate up the ancestors until there is a layer with a sibling + for ancestor in layer.ancestors(metadata) { + if self.next_layer.is_some() { + break; + } + // If there is a clipped area for this ancestor (that we are now exiting), discard it. + if self.parent_targets.last().is_some_and(|(id, _)| *id == ancestor) { + self.parent_targets.pop(); + } + self.next_layer = ancestor.next_sibling(metadata) + } + + if clicked { + return Some(layer); + } + } + assert!(self.parent_targets.is_empty(), "The parent targets should always be empty (since we have left all layers)"); + None + } +} diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 4349081d..fe0871d3 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1339,10 +1339,8 @@ impl<'a> MessageHandler> for NodeGrap log::error!("Could not get transient metadata for node {node_id}"); continue; }; - if click_targets - .node_click_target - .intersect_rectangle(Quad::from_box([box_selection_start, box_selection_end_graph]), DAffine2::IDENTITY) - { + let quad = Quad::from_box([box_selection_start, box_selection_end_graph]); + if click_targets.node_click_target.intersect_path(|| quad.bezier_lines(), DAffine2::IDENTITY) { nodes.insert(node_id); } } diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 2b1ae67d..5a61d28e 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -7,7 +7,7 @@ use graphene_std::vector::PointId; use graphene_std::vector::VectorData; use glam::{DAffine2, DVec2}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::num::NonZeroU64; // ================ @@ -21,6 +21,7 @@ pub struct DocumentMetadata { pub upstream_transforms: HashMap, pub structure: HashMap, pub click_targets: HashMap>, + pub clip_targets: HashSet, pub vector_modify: HashMap, /// Transform from document space to viewport space. pub document_to_viewport: DAffine2, @@ -33,6 +34,7 @@ impl Default for DocumentMetadata { structure: HashMap::new(), vector_modify: HashMap::new(), click_targets: HashMap::new(), + clip_targets: HashSet::new(), document_to_viewport: DAffine2::IDENTITY, } } @@ -149,6 +151,10 @@ impl DocumentMetadata { let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY); click_targets.iter().map(ClickTarget::subpath) } + + pub fn is_clip(&self, node: NodeId) -> bool { + self.clip_targets.contains(&node) + } } // =================== diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index e0e284a6..d6aae866 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -2930,6 +2930,11 @@ impl NodeNetworkInterface { self.document_metadata.click_targets = new_click_targets; } + /// Update the cached clip targets of the layers + pub fn update_clip_targets(&mut self, new_clip_targets: HashSet) { + self.document_metadata.clip_targets = new_clip_targets; + } + /// Update the vector modify of the layers pub fn update_vector_modify(&mut self, new_vector_modify: HashMap) { self.document_metadata.vector_modify = new_vector_modify; diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index d5c88f7f..ec938b40 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -471,7 +471,7 @@ impl Fsm for SelectToolFsmState { let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); // Draw outline visualizations on the layers to be selected - for layer in document.intersect_quad(quad, input) { + for layer in document.intersect_quad_no_artboards(quad, input) { overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); } @@ -914,7 +914,7 @@ impl Fsm for SelectToolFsmState { if !tool_data.has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() { let quad = tool_data.selection_quad(); - let intersection = document.intersect_quad(quad, input); + let intersection = document.intersect_quad_no_artboards(quad, input); if let Some(path) = intersection.last() { let replacement_selected_layers: Vec<_> = document @@ -1007,7 +1007,7 @@ impl Fsm for SelectToolFsmState { } (SelectToolFsmState::DrawingBox { .. }, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => { let quad = tool_data.selection_quad(); - let new_selected: HashSet<_> = document.intersect_quad(quad, input).collect(); + let new_selected: HashSet<_> = document.intersect_quad_no_artboards(quad, input).collect(); let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect(); if new_selected != current_selected { tool_data.layers_dragging = new_selected.into_iter().collect(); diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 1be37613..dca3923a 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -17,7 +17,7 @@ use dyn_any::DynAny; use base64::Engine; use glam::{DAffine2, DVec2}; use num_traits::Zero; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Write; #[cfg(feature = "vello")] use vello::*; @@ -57,34 +57,28 @@ impl ClickTarget { self.bounding_box = self.subpath.bounding_box(); } - /// Does the click target intersect the rectangle - pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool { + /// Does the click target intersect the path + pub fn intersect_path>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool { // Check if the matrix is not invertible if layer_transform.matrix2.determinant().abs() <= f64::EPSILON { return false; } - let quad = layer_transform.inverse() * document_quad; + let inverse = layer_transform.inverse(); + let mut bezier_iter = || bezier_iter().map(|bezier| bezier.apply_transformation(|point| inverse.transform_point2(point))); // Check if outlines intersect - if self - .subpath - .iter() - .any(|path_segment| quad.bezier_lines().any(|line| !path_segment.intersections(&line, None, None).is_empty())) - { + let outline_intersects = |path_segment: bezier_rs::Bezier| bezier_iter().any(|line| !path_segment.intersections(&line, None, None).is_empty()); + if self.subpath.iter().any(outline_intersects) { return true; } // Check if selection is entirely within the shape - if self.subpath.closed() && self.subpath.contains_point(quad.center()) { + if self.subpath.closed() && bezier_iter().next().is_some_and(|bezier| self.subpath.contains_point(bezier.start)) { return true; } // Check if shape is entirely within selection - self.subpath - .manipulator_groups() - .first() - .map(|group| group.anchor) - .map(|shape_point| quad.contains(shape_point)) - .unwrap_or_default() + let any_point_from_subpath = self.subpath.manipulator_groups().first().map(|group| group.anchor); + any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(shape_point)).sum::() != 0) } /// Does the click target intersect the point (accounting for stroke size) @@ -102,7 +96,7 @@ impl ClickTarget { // Allows for selecting lines // TODO: actual intersection of stroke let inflated_quad = Quad::from_box(target_bounds); - self.intersect_rectangle(inflated_quad, layer_transform) + self.intersect_path(|| inflated_quad.bezier_lines(), layer_transform) } /// Does the click target intersect the point (not accounting for stroke size) @@ -277,6 +271,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform { pub struct RenderMetadata { pub footprints: HashMap, pub click_targets: HashMap>, + pub clip_targets: HashSet, } pub trait GraphicElementRendered { @@ -650,6 +645,9 @@ impl GraphicElementRendered for Artboard { let subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2()); metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]); metadata.footprints.insert(element_id, (footprint, DAffine2::from_translation(self.location.as_dvec2()))); + if self.clip { + metadata.clip_targets.insert(element_id); + } } footprint.transform *= self.transform(); self.graphic_group.collect_metadata(metadata, footprint, None); diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index e2e67b1a..f6d847ce 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -435,9 +435,9 @@ fn construct_closed_subpath() { let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE); let vector_data = VectorData::from_subpath(&circle); assert_eq!(vector_data.point_domain.ids().len(), 4); - let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::>(); - assert_eq!(bézier_paths.len(), 4); - assert!(bézier_paths.iter().all(|&bézier| circle.iter().any(|original_bézier| original_bézier == bézier))); + let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::>(); + assert_eq!(bezier_paths.len(), 4); + assert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier))); let generated = vector_data.stroke_bezier_paths().collect::>(); assert_subpath_eq(&generated, &[circle]); @@ -445,12 +445,12 @@ fn construct_closed_subpath() { #[test] fn construct_open_subpath() { - let bézier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X); - let subpath = bezier_rs::Subpath::from_bezier(&bézier); + let bezier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X); + let subpath = bezier_rs::Subpath::from_bezier(&bezier); let vector_data = VectorData::from_subpath(&subpath); assert_eq!(vector_data.point_domain.ids().len(), 2); - let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::>(); - assert_eq!(bézier_paths, vec![bézier]); + let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::>(); + assert_eq!(bezier_paths, vec![bezier]); let generated = vector_data.stroke_bezier_paths().collect::>(); assert_subpath_eq(&generated, &[subpath]); @@ -465,9 +465,9 @@ fn construct_many_subpath() { let vector_data = VectorData::from_subpaths([&curve, &circle], false); assert_eq!(vector_data.point_domain.ids().len(), 6); - let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::>(); - assert_eq!(bézier_paths.len(), 5); - assert!(bézier_paths.iter().all(|&bézier| circle.iter().chain(curve.iter()).any(|original_bézier| original_bézier == bézier))); + let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::>(); + assert_eq!(bezier_paths.len(), 5); + assert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier))); let generated = vector_data.stroke_bezier_paths().collect::>(); assert_subpath_eq(&generated, &[curve, circle]); diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 760bb9bc..54c1b2c5 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -5,6 +5,7 @@ use graphene_core::transform::Transform; use graphene_core::vector::misc::BooleanOperation; pub use graphene_core::vector::*; use graphene_core::{Color, GraphicElement, GraphicGroup}; +pub use path_bool as path_bool_lib; use path_bool::FillRule; use path_bool::PathBooleanOperation; @@ -342,6 +343,6 @@ fn boolean_subtract(a: Path, b: Path) -> Vec { path_bool(a, b, PathBooleanOperation::Difference) } -fn boolean_intersect(a: Path, b: Path) -> Vec { +pub fn boolean_intersect(a: Path, b: Path) -> Vec { path_bool(a, b, PathBooleanOperation::Intersection) } diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 0140d43c..96d021dc 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -19,7 +19,7 @@ use graphene_core::{Color, WasmNotSend}; use base64::Engine; #[cfg(target_arch = "wasm32")] use glam::DAffine2; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; #[cfg(target_arch = "wasm32")] use wasm_bindgen::Clamped; @@ -225,6 +225,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( let mut metadata = RenderMetadata { footprints: HashMap::new(), click_targets: HashMap::new(), + clip_targets: HashSet::new(), }; data.collect_metadata(&mut metadata, footprint, None);