From 5e277bf915978ad1a175439814fc4b1be889d6cd Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 5 Feb 2026 19:40:42 +0100 Subject: [PATCH] Store click targets in Arc --- .../portfolio/document/document_message.rs | 3 ++- .../document/document_message_handler.rs | 12 ++++++++++-- .../document/utility_types/document_metadata.rs | 11 ++++++----- .../document/utility_types/network_interface.rs | 5 +++-- node-graph/libraries/rendering/src/renderer.rs | 16 ++++++++-------- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index b6bc9b63ae..c33e42b3dd 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use std::sync::Arc; use super::utility_types::misc::{GroupFolderType, SnappingState}; use crate::messages::input_mapper::utility_types::input_keyboard::Key; @@ -203,7 +204,7 @@ pub enum DocumentMessage { first_element_source_id: HashMap>, }, UpdateClickTargets { - click_targets: HashMap>, + click_targets: HashMap>>, }, UpdateClipTargets { clip_targets: HashSet, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index e38597a682..c807b60a36 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -42,6 +42,7 @@ use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2}; use graphene_std::vector::style::RenderMode; use kurbo::{Affine, CubicBez, Line, ParamCurve, PathSeg, QuadBez}; use std::path::PathBuf; +use std::sync::Arc; use std::time::Duration; #[derive(ExtractField)] @@ -3071,7 +3072,14 @@ impl<'a> ClickXRayIter<'a> { } /// 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 { + fn check_layer_area_target( + &mut self, + click_targets: Option<&[Arc]>, + clip: bool, + layer: LayerNodeIdentifier, + path: Vec, + transform: DAffine2, + ) -> XRayResult { // Convert back to Kurbo types for intersections let segment = |bezier: &path_bool_lib::PathSegment| match *bezier { path_bool_lib::PathSegment::Line(start, end) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))), @@ -3088,7 +3096,7 @@ impl<'a> ClickXRayIter<'a> { // 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 clip_path = click_targets_to_path_lib_segments(click_targets.iter().flat_map(|x| x.iter()).map(|x| x.as_ref()), transform); let subtracted = boolean_intersect(path, clip_path).into_iter().flatten().collect::>(); if subtracted.is_empty() { use_children = false; 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 4188eff338..6cea963ba1 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -13,6 +13,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::{PointId, Vector}; use std::collections::{HashMap, HashSet}; use std::num::NonZeroU64; +use std::sync::Arc; // ================ // DocumentMetadata @@ -26,7 +27,7 @@ pub struct DocumentMetadata { pub local_transforms: HashMap, pub first_element_source_ids: HashMap>, pub structure: HashMap, - pub click_targets: HashMap>, + pub click_targets: HashMap>>, pub clip_targets: HashSet, pub vector_modify: HashMap, /// Transform from document space to viewport space. @@ -46,8 +47,8 @@ impl DocumentMetadata { self.structure.contains_key(&layer) } - pub fn click_targets(&self, layer: LayerNodeIdentifier) -> Option<&Vec> { - self.click_targets.get(&layer) + pub fn click_targets(&self, layer: LayerNodeIdentifier) -> Option<&[Arc]> { + self.click_targets.get(&layer).map(|x| x.as_slice()) } /// Access the [`NodeRelations`] of a layer. @@ -206,7 +207,7 @@ impl DocumentMetadata { } pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator> { - static EMPTY: Vec = Vec::new(); + static EMPTY: Vec> = Vec::new(); let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY); click_targets.iter().filter_map(|target| match target.target_type() { ClickTargetType::Subpath(subpath) => Some(subpath), @@ -215,7 +216,7 @@ impl DocumentMetadata { } pub fn layer_with_free_points_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator { - static EMPTY: Vec = Vec::new(); + static EMPTY: Vec> = Vec::new(); let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY); click_targets.iter().map(|target| target.target_type()) } 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 23ab3c64f2..cf6e487bc4 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -31,6 +31,7 @@ use serde_json::{Value, json}; use std::collections::{HashMap, HashSet, VecDeque}; use std::hash::Hash; use std::ops::Deref; +use std::sync::Arc; /// All network modifications should be done through this API, so the fields cannot be public. However, all fields within this struct can be public since it it not possible to have a public mutable reference. #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] @@ -3078,7 +3079,7 @@ impl NodeNetworkInterface { self.document_metadata .click_targets .get(&layer) - .map(|click| click.iter().map(ClickTarget::target_type)) + .map(|click| click.iter().map(|x| x.target_type())) .map(|target_types| Vector::from_target_types(target_types, true)) } @@ -3180,7 +3181,7 @@ impl NodeNetworkInterface { } /// Update the cached click targets of the layers - pub fn update_click_targets(&mut self, new_click_targets: HashMap>) { + pub fn update_click_targets(&mut self, new_click_targets: HashMap>>) { self.document_metadata.click_targets = new_click_targets; } diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index 6da8a86081..56646b878a 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -247,7 +247,7 @@ pub struct RenderMetadata { pub upstream_footprints: HashMap, pub local_transforms: HashMap, pub first_element_source_id: HashMap>, - pub click_targets: HashMap>, + pub click_targets: HashMap>>, pub clip_targets: HashSet, } @@ -471,7 +471,7 @@ impl Render for Artboard { fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option) { if let Some(element_id) = element_id { let subpath = Subpath::new_rectangle(DVec2::ZERO, self.dimensions.as_dvec2()); - metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]); + metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]); metadata.upstream_footprints.insert(element_id, footprint); metadata.local_transforms.insert(element_id, DAffine2::from_translation(self.location.as_dvec2())); if self.clip { @@ -666,7 +666,7 @@ impl Render for Table { all_upstream_click_targets.extend(new_click_targets); } - metadata.click_targets.insert(element_id, all_upstream_click_targets); + metadata.click_targets.insert(element_id, all_upstream_click_targets.into_iter().map(|x| x.into()).collect()); } } @@ -1173,7 +1173,7 @@ impl Render for Table { let anchor = vector.point_domain.position_from_id(point_id).unwrap_or_default(); let point = FreePoint::new(point_id, anchor); - Some(ClickTarget::new_with_free_point(point)) + Some(ClickTarget::new_with_free_point(point).into()) } else { None } @@ -1182,9 +1182,9 @@ impl Render for Table { let click_targets = vector .stroke_bezier_paths() .map(fill) - .map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width)) + .map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width).into()) .chain(single_anchors_targets.into_iter()) - .collect::>(); + .collect::>(); metadata.click_targets.entry(element_id).or_insert(click_targets); } @@ -1366,7 +1366,7 @@ impl Render for Table> { let Some(element_id) = element_id else { return }; let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE); - metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]); + metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]); metadata.upstream_footprints.insert(element_id, footprint); // TODO: Find a way to handle more than one row of the raster table if let Some(raster) = self.iter().next() { @@ -1426,7 +1426,7 @@ impl Render for Table> { let Some(element_id) = element_id else { return }; let subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE); - metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]); + metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.).into()]); metadata.upstream_footprints.insert(element_id, footprint); // TODO: Find a way to handle more than one row of the raster table if let Some(raster) = self.iter().next() {