From 88c059a607f3911785aee7f37a2cc7b5e672967a Mon Sep 17 00:00:00 2001 From: celyk <50609684+celyk@users.noreply.github.com> Date: Tue, 8 Jul 2025 07:48:22 +1000 Subject: [PATCH 01/11] Add Normalize and Length nodes (#2837) * Add vector length node * Add normalize node * Add comments * Make normalize node safer for the user --- node-graph/gmath-nodes/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/node-graph/gmath-nodes/src/lib.rs b/node-graph/gmath-nodes/src/lib.rs index c4d1b9a1b6..1019506bdc 100644 --- a/node-graph/gmath-nodes/src/lib.rs +++ b/node-graph/gmath-nodes/src/lib.rs @@ -612,6 +612,20 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 { vector_a.dot(vector_b) } +/// Gets the length or magnitude of a vector. +#[node_macro::node(category("Math: Vector"))] +fn length(_: impl Ctx, vector: DVec2) -> f64 { + vector.length() +} + +/// Scales the input vector to unit length while preserving it's direction. This is equivalent to dividing the input vector by it's own magnitude. +/// +/// Returns zero when the input vector is zero. +#[node_macro::node(category("Math: Vector"))] +fn normalize(_: impl Ctx, vector: DVec2) -> DVec2 { + vector.normalize_or_zero() +} + #[cfg(test)] mod test { use super::*; @@ -625,6 +639,12 @@ mod test { assert_eq!(dot_product((), vector_a, vector_b), 11.); } + #[test] + pub fn length_function() { + let vector = DVec2::new(3., 4.); + assert_eq!(length((), vector), 5.); + } + #[test] fn test_basic_expression() { let result = math((), 0., "2 + 2".to_string(), 0.); From 4a83067081e39d204c68f2ed7fe731b38cf32772 Mon Sep 17 00:00:00 2001 From: James Lindsay <78500760+0HyperCube@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:09:21 +0100 Subject: [PATCH 02/11] Fix Path tool's Path node transform calculation by skipping local transform based on first instance source ID (#2843) * First instance source id * Set source node id and migrations --- editor/src/dispatcher.rs | 2 + .../portfolio/document/document_message.rs | 1 + .../document/document_message_handler.rs | 2 + .../node_graph/document_node_definitions.rs | 6 ++- .../document/overlays/utility_functions.rs | 4 +- .../utility_types/document_metadata.rs | 49 ++++++++++++------ .../utility_types/network_interface.rs | 26 ++++++++++ .../messages/portfolio/document_migration.rs | 2 + .../tool/common_functionality/shape_editor.rs | 50 +++++++++++-------- .../messages/tool/tool_messages/path_tool.rs | 19 ++++--- node-graph/gcore/src/context.rs | 2 +- .../src/vector/vector_data/modification.rs | 9 +++- node-graph/graph-craft/src/document/value.rs | 2 +- node-graph/gstd/src/wasm_application_io.rs | 8 +-- node-graph/gsvg-renderer/src/renderer.rs | 2 + 15 files changed, 126 insertions(+), 58 deletions(-) diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index a8891033ee..9408d7d927 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -140,6 +140,7 @@ impl Dispatcher { let graphene_std::renderer::RenderMetadata { upstream_footprints: footprints, local_transforms, + first_instance_source_id, click_targets, clip_targets, } = render_metadata; @@ -149,6 +150,7 @@ impl Dispatcher { DocumentMessage::UpdateUpstreamTransforms { upstream_footprints: footprints, local_transforms, + first_instance_source_id, }, DocumentMessage::UpdateClickTargets { click_targets }, DocumentMessage::UpdateClipTargets { clip_targets }, diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 18ede64be8..ae3576d2a1 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -182,6 +182,7 @@ pub enum DocumentMessage { UpdateUpstreamTransforms { upstream_footprints: HashMap, local_transforms: HashMap, + first_instance_source_id: HashMap>, }, UpdateClickTargets { click_targets: HashMap>, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 81f631dfe0..f306663a0e 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1305,8 +1305,10 @@ impl MessageHandler> for DocumentMessag DocumentMessage::UpdateUpstreamTransforms { upstream_footprints, local_transforms, + first_instance_source_id, } => { self.network_interface.update_transforms(upstream_footprints, local_transforms); + self.network_interface.update_first_instance_source_id(first_instance_source_id); } DocumentMessage::UpdateClickTargets { click_targets } => { // TODO: Allow non layer nodes to have click targets diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 346969c5ea..e2fa3128af 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -1472,7 +1472,11 @@ fn static_nodes() -> Vec { ..Default::default() }, DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1)], + inputs: vec![ + NodeInput::node(NodeId(0), 0), + NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1), + NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), + ], manual_composition: Some(generic!(T)), implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_data::modification::PathModifyNode")), ..Default::default() diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 1e012b867a..463971c65c 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; - let transform = document.metadata().transform_to_viewport(layer); + let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); if display_path { overlay_context.outline_vector(&vector_data, transform); } @@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: & continue; }; //let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); - let transform = document.metadata().transform_to_viewport(layer); + let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); let selected = shape_editor.selected_shape_state.get(&layer); let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point)); 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 23887db3e5..0b6f65bc87 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -1,6 +1,8 @@ use super::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::graph_operation::transform_utils; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; +use crate::messages::portfolio::document::utility_types::network_interface::FlowType; +use crate::messages::tool::common_functionality::graph_modification_utils; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; use graphene_std::math::quad::Quad; @@ -16,10 +18,11 @@ use std::num::NonZeroU64; // TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync), // TODO: it might be better to have a system that can query the state of the node network on demand. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct DocumentMetadata { pub upstream_footprints: HashMap, pub local_transforms: HashMap, + pub first_instance_source_ids: HashMap>, pub structure: HashMap, pub click_targets: HashMap>, pub clip_targets: HashSet, @@ -28,20 +31,6 @@ pub struct DocumentMetadata { pub document_to_viewport: DAffine2, } -impl Default for DocumentMetadata { - fn default() -> Self { - Self { - upstream_footprints: HashMap::new(), - local_transforms: HashMap::new(), - structure: HashMap::new(), - vector_modify: HashMap::new(), - click_targets: HashMap::new(), - clip_targets: HashSet::new(), - document_to_viewport: DAffine2::IDENTITY, - } - } -} - // ================================= // DocumentMetadata: Layer iterators // ================================= @@ -91,6 +80,36 @@ impl DocumentMetadata { footprint * local_transform } + pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { + // We're not allowed to convert the root parent to a node id + if layer == LayerNodeIdentifier::ROOT_PARENT { + return self.document_to_viewport; + } + + let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); + + let mut use_local = true; + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface); + if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") { + if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) { + if !network_interface + .upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow) + .any(|upstream| Some(upstream) == source) + { + use_local = false; + info!("Local transform is invalid — using the identity for the local transform instead") + } + } + } + let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default(); + + footprint * local_transform + } + + pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { + self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface) + } + pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); let local_transform = self.local_transforms.get(&layer.to_node()).copied(); 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 c4255120af..389557c047 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -60,6 +60,27 @@ impl PartialEq for NodeNetworkInterface { } } +impl NodeNetworkInterface { + /// Add DocumentNodePath input to the PathModifyNode protonode + pub fn migrate_path_modify_node(&mut self) { + fix_network(&mut self.network); + fn fix_network(network: &mut NodeNetwork) { + for node in network.nodes.values_mut() { + if let Some(network) = node.implementation.get_network_mut() { + fix_network(network); + } + if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation { + if protonode.name.contains("PathModifyNode") { + if node.inputs.len() < 3 { + node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)); + } + } + } + } + } + } +} + // Public immutable getters for the network interface impl NodeNetworkInterface { // TODO: Make private and use .field_name getter methods @@ -3513,6 +3534,11 @@ impl NodeNetworkInterface { self.document_metadata.local_transforms = local_transforms; } + /// Update the cached first instance source id of the layers + pub fn update_first_instance_source_id(&mut self, new: HashMap>) { + self.document_metadata.first_instance_source_ids = new; + } + /// Update the cached click targets of the layers pub fn update_click_targets(&mut self, new_click_targets: HashMap>) { self.document_metadata.click_targets = new_click_targets; diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 20ce8ea8e0..7567883c5b 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -190,6 +190,8 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st } pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) { + document.network_interface.migrate_path_modify_node(); + let network = document.network_interface.document_network().clone(); // Apply string replacements to each node diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 14ae8898a3..581c89908f 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -212,15 +212,15 @@ impl ClosestSegment { self.bezier_point_to_viewport } - pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 { - let transform = document_metadata.transform_to_viewport(self.layer); + pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 { + let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface); let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t)); transform.transform_point2(bezier_point) } /// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position. - pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) { - let transform = document_metadata.transform_to_viewport(self.layer); + pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) { + let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface); let layer_mouse_pos = transform.inverse().transform_point2(mouse_position); let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.); @@ -239,9 +239,9 @@ impl ClosestSegment { tolerance.powi(2) < self.distance_squared(mouse_position) } - pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option, Option) { + pub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option, Option) { // Transform to viewport space - let transform = document_metadata.transform_to_viewport(self.layer); + let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface); // Split the Bezier at the parameter `t` let [first, second] = self.bezier.split(TValue::Parametric(self.t)); @@ -307,7 +307,7 @@ impl ClosestSegment { } pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 { - let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata()) { + let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) { (handle1 - handle2).try_normalize() } else { let [first_point, last_point] = self.points(); @@ -339,7 +339,7 @@ impl ClosestSegment { break_colinear_molding: bool, temporary_adjacent_handles_while_molding: Option<[Option; 2]>, ) -> Option<[Option; 2]> { - let transform = document.metadata().transform_to_viewport(self.layer); + let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface); let start = self.bezier.start; let end = self.bezier.end; @@ -507,7 +507,7 @@ impl ShapeState { continue; }; - let to_document = document.metadata().transform_to_document(*layer); + let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface); for &selected in &state.selected_points { let source = match selected { @@ -564,7 +564,11 @@ impl ShapeState { let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); // Offset to snap the selected point to the cursor - let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); + let offset = mouse_position + - network_interface + .document_metadata() + .transform_to_viewport_if_feeds(layer, network_interface) + .transform_point2(point_position); // This is selecting the manipulator only for now, next to generalize to points @@ -621,7 +625,11 @@ impl ShapeState { let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); // Offset to snap the selected point to the cursor - let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); + let offset = mouse_position + - network_interface + .document_metadata() + .transform_to_viewport_if_feeds(layer, network_interface) + .transform_point2(point_position); // Gather current selection information let points = self @@ -653,7 +661,7 @@ impl ShapeState { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return; }; - let to_viewport = document.metadata().transform_to_viewport(layer); + let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); let layer_mouse = to_viewport.inverse().transform_point2(mouse); let state = self.selected_shape_state.entry(layer).or_default(); @@ -875,7 +883,7 @@ impl ShapeState { } let vector_data = network_interface.compute_modified_vector(layer)?; - let transform = network_interface.document_metadata().transform_to_document(layer).inverse(); + let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse(); let position = transform.transform_point2(new_position); let current_position = point.get_position(&vector_data)?; let delta = position - current_position; @@ -1026,7 +1034,7 @@ impl ShapeState { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue; }; - let transform = document.metadata().transform_to_document(layer); + let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); for &point in layer_state.selected_points.iter() { let Some(handles) = point.get_handle_pair(&vector_data) else { continue }; @@ -1116,8 +1124,8 @@ impl ShapeState { let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer)); - let transform_to_viewport_space = document.metadata().transform_to_viewport(layer); - let transform_to_document_space = document.metadata().transform_to_document(layer); + let transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); + let transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); let delta_transform = if in_viewport_space { transform_to_viewport_space } else { @@ -1210,7 +1218,7 @@ impl ShapeState { .iter() .filter_map(|(&layer, state)| { let vector_data = document.network_interface.compute_modified_vector(layer)?; - let transform = document.metadata().transform_to_document(layer); + let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); let opposing_handle_lengths = vector_data .colinear_manipulators .iter() @@ -1575,7 +1583,7 @@ impl ShapeState { let mut manipulator_point = None; let vector_data = network_interface.compute_modified_vector(layer)?; - let viewspace = network_interface.document_metadata().transform_to_viewport(layer); + let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); // Handles for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() { @@ -1611,7 +1619,7 @@ impl ShapeState { /// Find the `t` value along the path segment we have clicked upon, together with that segment ID. fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option { - let transform = network_interface.document_metadata().transform_to_viewport(layer); + let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); let layer_pos = transform.inverse().transform_point2(position); let tolerance = tolerance + 0.5; @@ -1785,7 +1793,7 @@ impl ShapeState { pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque) -> bool { let mut process_layer = |layer| { let vector_data = network_interface.compute_modified_vector(layer)?; - let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer); + let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); let mut result = None; let mut closest_distance_squared = tolerance * tolerance; @@ -1889,7 +1897,7 @@ impl ShapeState { let vector_data = network_interface.compute_modified_vector(layer); let Some(vector_data) = vector_data else { continue }; - let transform = network_interface.document_metadata().transform_to_viewport(layer); + let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count()); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count()); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index dbce924fc9..54a6ceb4c5 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -718,7 +718,7 @@ impl PathToolData { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue; }; - let transform = document.metadata().transform_to_document(layer); + let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder); for point in state.selected_points() { @@ -828,7 +828,7 @@ impl PathToolData { let selected_handle = selection.selected_points().next()?.as_handle()?; let handle_id = selected_handle.to_manipulator_point(); - let layer_to_document = document.metadata().transform_to_document(*layer); + let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface); let vector_data = document.network_interface.compute_modified_vector(*layer)?; let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?; @@ -870,7 +870,7 @@ impl PathToolData { let anchor = handle_id.get_anchor(&vector_data); let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data); - let layer_to_document = document.metadata().transform_to_document(*layer); + let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface); self.adjacent_anchor_offset = handle_id .get_anchor_position(&vector_data) @@ -1017,7 +1017,7 @@ impl PathToolData { } // If already hovering on a segment, then recalculate its closest point else if let Some(closest_segment) = &mut self.segment { - closest_segment.update_closest_point(document.metadata(), position); + closest_segment.update_closest_point(document.metadata(), &document.network_interface, position); if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) { self.segment = None; @@ -1084,7 +1084,7 @@ impl PathToolData { let layer = sliding_point_info.layer; let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return }; - let transform = network_interface.document_metadata().transform_to_viewport(layer); + let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); let layer_pos = transform.inverse().transform_point2(target_position); let segments = sliding_point_info.connected_segments; @@ -1412,7 +1412,7 @@ impl Fsm for PathToolFsmState { if let Some(closest_segment) = &tool_data.segment { if tool_options.path_editing_mode.segment_editing_mode { - let transform = document.metadata().transform_to_viewport(closest_segment.layer()); + let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface); overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform); @@ -1430,7 +1430,7 @@ impl Fsm for PathToolFsmState { } } else { let perp = closest_segment.calculate_perp(document); - let point = closest_segment.closest_point(document.metadata()); + let point = closest_segment.closest_point(document.metadata(), &document.network_interface); // Draw an X on the segment if tool_data.delete_segment_pressed { @@ -2280,7 +2280,10 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: & return SelectionStatus::None; }; - let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position); + let coordinates = network_interface + .document_metadata() + .transform_to_document_if_feeds(layer, network_interface) + .transform_point2(local_position); let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }; return SelectionStatus::One(SingleSelectedPoint { diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 3adb839b0b..4e8854c901 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -356,7 +356,7 @@ pub struct ContextImpl<'a> { } impl<'a> ContextImpl<'a> { - pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f> + pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f> where 'a: 'f, { diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 0f06c643a2..fd7162a6a8 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -1,7 +1,7 @@ use super::*; use crate::Ctx; use crate::instances::Instance; -use crate::uuid::generate_uuid; +use crate::uuid::{NodeId, generate_uuid}; use bezier_rs::BezierHandles; use dyn_any::DynAny; use kurbo::{BezPath, PathEl, Point}; @@ -420,12 +420,17 @@ impl Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] -async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box) -> VectorDataTable { +async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box, node_path: Vec) -> VectorDataTable { if vector_data.is_empty() { vector_data.push(Instance::default()); } let vector_data_instance = vector_data.get_mut(0).expect("push should give one item"); modification.apply(vector_data_instance.instance); + + // Update the source node id + let this_node_path = node_path.iter().rev().nth(1).copied(); + *vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path); + if vector_data.len() > 1 { warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len()); } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index d3b82eae1c..00402776fb 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -97,7 +97,7 @@ macro_rules! tagged_value { } } /// Attempts to downcast the dynamic type to a tagged value - pub fn try_from_std_any_ref(input: &(dyn std::any::Any)) -> Result { + pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result { use std::any::TypeId; match input.type_id() { diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 82b526f18e..ae03edd425 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -18,7 +18,6 @@ use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegme use base64::Engine; #[cfg(target_arch = "wasm32")] use glam::DAffine2; -use std::collections::{HashMap, HashSet}; use std::sync::Arc; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsCast; @@ -278,12 +277,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( #[cfg(all(feature = "vello", not(test)))] let use_vello = use_vello && surface_handle.is_some(); - let mut metadata = RenderMetadata { - upstream_footprints: HashMap::new(), - local_transforms: HashMap::new(), - click_targets: HashMap::new(), - clip_targets: HashSet::new(), - }; + let mut metadata = RenderMetadata::default(); data.collect_metadata(&mut metadata, footprint, None); let output_format = render_config.export_format; diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 95e9890a86..70f7c18206 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -198,6 +198,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform { pub struct RenderMetadata { pub upstream_footprints: HashMap, pub local_transforms: HashMap, + pub first_instance_source_id: HashMap>, pub click_targets: HashMap>, pub clip_targets: HashSet, } @@ -1090,6 +1091,7 @@ impl GraphicElementRendered for GraphicElement { metadata.upstream_footprints.insert(element_id, footprint); // TODO: Find a way to handle more than one row of the graphical data table if let Some(vector_data) = vector_data.instance_ref_iter().next() { + metadata.first_instance_source_id.insert(element_id, *vector_data.source_node_id); metadata.local_transforms.insert(element_id, *vector_data.transform); } } From 69ed80b79b62e52b46f7ec3094e54b5518862104 Mon Sep 17 00:00:00 2001 From: Firestar99 Date: Tue, 8 Jul 2025 01:29:59 +0200 Subject: [PATCH 03/11] Add `::IDENTITY` to node macro to return a `ProtoNodeIdentifier` that is always a `&'static str` (#2842) * fix warnings on master * make the `ProtoNodeIdentifier` of a Node be accessible and always a borrowed `&'static str` * always generate `node_name::identifier()`, even with `skip_impl` * make `FrontendNodeType` use Cow * remove broken `DocumentNodeDefinition`s for old GPU nodes * don't reexport `graphic_element` in it's entirety, only data structures * adjust everything to use the new `node_name::identifier()` * fixup imports for wasm * turn identifier fn into a constant --- .../node_graph/document_node_definitions.rs | 459 +++--------------- .../document_node_derive.rs | 4 +- .../document/node_graph/node_properties.rs | 2 +- .../document/node_graph/utility_types.rs | 27 +- .../graph_modification_utils.rs | 8 +- .../shapes/ellipse_shape.rs | 2 +- .../tool/tool_messages/artboard_tool.rs | 2 +- editor/src/node_graph_executor.rs | 9 +- editor/src/node_graph_executor/runtime.rs | 6 +- frontend/src-tauri/src/main.rs | 4 - node-graph/gcore/src/lib.rs | 6 +- node-graph/gcore/src/memo.rs | 14 +- node-graph/gcore/src/registry.rs | 6 +- node-graph/gcore/src/types.rs | 49 +- node-graph/graph-craft/src/document.rs | 11 +- node-graph/graph-craft/src/document/value.rs | 4 +- node-graph/interpreted-executor/src/lib.rs | 2 +- node-graph/interpreted-executor/src/util.rs | 4 +- node-graph/node-macro/src/codegen.rs | 109 +++-- node-graph/preprocessor/src/lib.rs | 8 +- 20 files changed, 213 insertions(+), 523 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index e2fa3128af..ce9378857f 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -21,6 +21,7 @@ use graphene_std::extract_xy::XY; use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::text::{Font, TypesettingConfig}; +#[allow(unused_imports)] use graphene_std::transform::Footprint; use graphene_std::vector::VectorDataTable; use graphene_std::*; @@ -88,7 +89,7 @@ fn static_nodes() -> Vec { category: "General", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::None, true)], ..Default::default() }, @@ -107,7 +108,7 @@ fn static_nodes() -> Vec { category: "Debug", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::None, true)], manual_composition: Some(generic!(T)), skip_deduplication: true, @@ -148,19 +149,19 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -230,21 +231,21 @@ fn static_nodes() -> Vec { // Secondary (left) input type coercion DocumentNode { inputs: vec![NodeInput::network(generic!(T), 1)], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_element::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, // Primary (bottom) input type coercion DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, // The monitor node is used to display a thumbnail in the UI DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() @@ -256,7 +257,7 @@ fn static_nodes() -> Vec { NodeInput::node(NodeId(2), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER), ..Default::default() }, ] @@ -337,7 +338,7 @@ fn static_nodes() -> Vec { // Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias DocumentNode { manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER), inputs: vec![ NodeInput::network(concrete!(TaggedValue), 1), NodeInput::value(TaggedValue::String(String::from("Artboard")), false), @@ -352,7 +353,7 @@ fn static_nodes() -> Vec { // TODO: Check if thumbnail is reversed DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(generic!(T)), skip_deduplication: true, ..Default::default() @@ -364,7 +365,7 @@ fn static_nodes() -> Vec { NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::AppendArtboardNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER), ..Default::default() }, ] @@ -466,13 +467,13 @@ fn static_nodes() -> Vec { DocumentNode { inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER), ..Default::default() }, ] @@ -522,6 +523,7 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Loads an image from a given URL"), properties: None, }, + #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Create Canvas", category: "Debug: GPU", @@ -532,14 +534,14 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), skip_deduplication: true, ..Default::default() }, DocumentNode { manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), ..Default::default() }, ] @@ -587,99 +589,7 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Creates a new canvas object."), properties: None, }, - DocumentNodeDefinition { - identifier: "Draw Canvas", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(3), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, RasterDataTable>")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), - skip_deduplication: true, - ..Default::default() - }, - DocumentNode { - manual_composition: Some(concrete!(Context)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DrawImageFrameNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("In", "TODO").into()], - output_names: vec!["Canvas".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Into".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Canvas".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Draw Canvas".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("Draws raster data to a canvas element."), - properties: None, - }, + #[cfg(all(feature = "gpu", target_arch = "wasm32"))] DocumentNodeDefinition { identifier: "Rasterize", category: "Raster", @@ -690,20 +600,20 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, @@ -778,7 +688,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_raster_nodes::std_nodes::NoisePatternNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER), inputs: vec![ NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::Bool(true), false), @@ -843,7 +753,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -852,7 +762,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -861,7 +771,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -870,7 +780,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -948,13 +858,13 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::XY(XY::X), false)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), + implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), + implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -1024,7 +934,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(BrushCache), 2), ], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_brush::brush::BrushNode")), + implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER), ..Default::default() }] .into_iter() @@ -1072,7 +982,7 @@ fn static_nodes() -> Vec { category: "Debug", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], manual_composition: Some(concrete!(Context)), ..Default::default() @@ -1091,7 +1001,7 @@ fn static_nodes() -> Vec { category: "Debug", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], manual_composition: Some(concrete!(Context)), ..Default::default() @@ -1105,164 +1015,6 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("TODO"), properties: None, }, - DocumentNodeDefinition { - identifier: "Storage", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::network(concrete!(Vec), 0), NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode")), - ..Default::default() - }, - DocumentNode { - manual_composition: Some(concrete!(Context)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::None, true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("In", "TODO").into()], - output_names: vec!["Storage".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Extract Executor".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Storage".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, - DocumentNodeDefinition { - identifier: "Create Output Buffer", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode")), - ..Default::default() - }, - DocumentNode { - manual_composition: Some(concrete!(Context)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::None, true), NodeInput::value(TaggedValue::None, true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("In", "TODO").into(), ("In", "TODO").into()], - output_names: vec!["Output Buffer".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Extract Executor".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Output Buffer".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Create GPU Surface", @@ -1275,13 +1027,13 @@ fn static_nodes() -> Vec { DocumentNode { manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER), ..Default::default() }, DocumentNode { manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), ..Default::default() }, ] @@ -1329,87 +1081,6 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("TODO"), properties: None, }, - #[cfg(feature = "gpu")] - DocumentNodeDefinition { - identifier: "Upload Texture", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::node(NodeId(0), 0)], - manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")), - ..Default::default() - }, - DocumentNode { - manual_composition: Some(generic!(T)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("In", "TODO").into()], - output_names: vec!["Texture".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Extract Executor".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Upload Texture".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, DocumentNodeDefinition { identifier: "Extract", category: "Debug", @@ -1466,7 +1137,7 @@ fn static_nodes() -> Vec { nodes: vec![ DocumentNode { inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(generic!(T)), skip_deduplication: true, ..Default::default() @@ -1478,7 +1149,7 @@ fn static_nodes() -> Vec { NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_data::modification::PathModifyNode")), + implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER), ..Default::default() }, ] @@ -1536,7 +1207,7 @@ fn static_nodes() -> Vec { category: "Text", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"), + implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER), manual_composition: Some(concrete!(Context)), inputs: vec![ NodeInput::scope("editor-api"), @@ -1644,7 +1315,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(generic!(T)), skip_deduplication: true, ..Default::default() @@ -1659,7 +1330,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(DVec2), 5), ], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::TransformNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER), ..Default::default() }, ] @@ -1743,25 +1414,25 @@ fn static_nodes() -> Vec { nodes: vec![ DocumentNode { inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_path_bool::BooleanOperationNode")), + implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -1841,7 +1512,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")), + implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -1856,25 +1527,25 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(bool), 6), NodeInput::node(NodeId(0), 0), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")), + implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(3), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -2012,24 +1683,24 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(u32), 2), ], manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::proto("graphene_core::vector::PoissonDiskPointsNode"), + implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -2649,11 +2320,11 @@ pub fn resolve_document_node_type(identifier: &str) -> Option<&DocumentNodeDefin pub fn collect_node_types() -> Vec { // Create a mapping from registry ID to document node identifier - let id_to_identifier_map: HashMap = DOCUMENT_NODE_TYPES + let id_to_identifier_map: HashMap = DOCUMENT_NODE_TYPES .iter() .filter_map(|definition| { - if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = &definition.node_template.document_node.implementation { - Some((name.to_string(), definition.identifier)) + if let DocumentNodeImplementation::ProtoNode(name) = &definition.node_template.document_node.implementation { + Some((name.clone(), definition.identifier)) } else { None } @@ -2661,28 +2332,28 @@ pub fn collect_node_types() -> Vec { .collect(); let mut extracted_node_types = Vec::new(); - let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap(); - let node_metadata = graphene_std::registry::NODE_METADATA.lock().unwrap(); + let node_registry = registry::NODE_REGISTRY.lock().unwrap(); + let node_metadata = registry::NODE_METADATA.lock().unwrap(); for (id, metadata) in node_metadata.iter() { if let Some(implementations) = node_registry.get(id) { let identifier = match id_to_identifier_map.get(id) { - Some(&id) => id.to_string(), + Some(&id) => id, None => continue, }; // Extract category from metadata (already creates an owned String) - let category = metadata.category.unwrap_or_default().to_string(); + let category = metadata.category.unwrap_or_default(); // Extract input types (already creates owned Strings) let input_types = implementations .iter() - .flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_string())) - .collect::>() + .flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_cow_string())) + .collect::>>() .into_iter() - .collect::>(); + .collect::>>(); // Create a FrontendNodeType - let node_type = FrontendNodeType::with_owned_strings_and_input_types(identifier, category, input_types); + let node_type = FrontendNodeType::with_input_types(identifier, category, input_types); // Store the created node_type extracted_node_types.push(node_type); @@ -2698,8 +2369,8 @@ pub fn collect_node_types() -> Vec { .document_node .inputs .iter() - .filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_string())) - .collect::>(); + .filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_cow_string())) + .collect::>>(); FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types) }) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index 8fcec559a7..85ce4b5162 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -21,7 +21,7 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec }; } - let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); + let node_registry = NODE_REGISTRY.lock().unwrap(); 'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { for node in custom.iter() { let DocumentNodeDefinition { @@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec .. } = node; match implementation { - DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer, + DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer, _ => (), } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index bc40dd36e4..18a7f82909 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1436,7 +1436,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper if let Some(field) = graphene_std::registry::NODE_METADATA .lock() .unwrap() - .get(&proto_node_identifier.name.clone().into_owned()) + .get(&proto_node_identifier) .and_then(|metadata| metadata.fields.get(input_index)) { number_options = (field.number_min, field.number_max, field.number_mode_range); diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index b743cb1fc6..0cfd49282f 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp use graph_craft::document::NodeId; use graph_craft::document::value::TaggedValue; use graphene_std::Type; +use std::borrow::Cow; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum FrontendGraphDataType { @@ -98,33 +99,25 @@ pub struct FrontendNode { #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct FrontendNodeType { - pub name: String, - pub category: String, + pub name: Cow<'static, str>, + pub category: Cow<'static, str>, #[serde(rename = "inputTypes")] - pub input_types: Option>, + pub input_types: Option>>, } impl FrontendNodeType { - pub fn new(name: &'static str, category: &'static str) -> Self { + pub fn new(name: impl Into>, category: impl Into>) -> Self { Self { - name: name.to_string(), - category: category.to_string(), + name: name.into(), + category: category.into(), input_types: None, } } - pub fn with_input_types(name: &'static str, category: &'static str, input_types: Vec) -> Self { + pub fn with_input_types(name: impl Into>, category: impl Into>, input_types: Vec>) -> Self { Self { - name: name.to_string(), - category: category.to_string(), - input_types: Some(input_types), - } - } - - pub fn with_owned_strings_and_input_types(name: String, category: String, input_types: Vec) -> Self { - Self { - name, - category, + name: name.into(), + category: category.into(), input_types: Some(input_types), } } diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 765bfc1eac..22c53f1117 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -5,9 +5,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo use crate::messages::prelude::*; use bezier_rs::Subpath; use glam::DVec2; -use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::Color; use graphene_std::NodeInputDecleration; use graphene_std::raster::BlendMode; @@ -416,14 +416,14 @@ impl<'a> NodeGraphLayer<'a> { } /// Node id of a protonode if it exists in the layer's primary flow - pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option { + pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option { self.horizontal_layer_flow() // Take until a different layer is reached .take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[])) - .find(move |node_id| { + .find(|node_id| { self.network_interface .implementation(node_id, &[]) - .is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier)) + .is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone())) }) } diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index fe97c22318..700f3c2779 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -77,7 +77,7 @@ mod test_ellipse { layers .filter_map(|layer| { let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); - let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; + let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?; Some(ResolvedEllipse { radius_x: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), radius_y: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index d72e28df51..0362b7f148 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -567,7 +567,7 @@ mod test_artboard { Ok(instrumented) => instrumented, Err(e) => panic!("Failed to evaluate graph: {}", e), }; - instrumented.grab_all_input::(&editor.runtime).collect() + instrumented.grab_all_input::(&editor.runtime).collect() } #[tokio::test] diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index dbb87c07f0..ba2f52d7b4 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -413,6 +413,7 @@ mod test { use super::*; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::test_utils::test_prelude::{self, NodeGraphLayer}; + use graph_craft::ProtoNodeIdentifier; use graph_craft::document::NodeNetwork; use graphene_std::Context; use graphene_std::NodeInputDecleration; @@ -422,7 +423,7 @@ mod test { /// Stores all of the monitor nodes that have been attached to a graph #[derive(Default)] pub struct Instrumented { - protonodes_by_name: HashMap>>>, + protonodes_by_name: HashMap>>>, protonodes_by_path: HashMap, Vec>>, } @@ -449,7 +450,7 @@ mod test { } if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation { path.push(*id); - self.protonodes_by_name.entry(identifier.name.to_string()).or_default().push(monitor_node_ids.clone()); + self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone()); self.protonodes_by_path.insert(path.clone(), monitor_node_ids); path.pop(); } @@ -457,7 +458,7 @@ mod test { for (input, monitor_id) in monitor_nodes { let monitor_node = DocumentNode { inputs: vec![input], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), manual_composition: Some(graph_craft::generic!(T)), skip_deduplication: true, ..Default::default() @@ -495,7 +496,7 @@ mod test { Input::Result: Send + Sync + Clone + 'static, { self.protonodes_by_name - .get(Input::identifier()) + .get(&Input::identifier()) .map_or([].as_slice(), |x| x.as_slice()) .iter() .filter_map(|inputs| inputs.get(Input::INDEX)) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index a93f73546c..227999143c 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -1,12 +1,12 @@ use super::*; use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use glam::{DAffine2, DVec2}; -use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::graphene_compiler::Compiler; use graph_craft::proto::GraphErrors; use graph_craft::wasm_application_io::EditorPreferences; +use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::Context; use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_std::instances::Instance; @@ -46,7 +46,7 @@ pub struct NodeRuntime { inspect_state: Option, /// Mapping of the fully-qualified node paths to their preprocessor substitutions. - substitutions: HashMap, + substitutions: HashMap, // TODO: Remove, it doesn't need to be persisted anymore /// The current renders of the thumbnails for layer nodes. @@ -435,7 +435,7 @@ impl InspectState { let monitor_node = DocumentNode { inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), manual_composition: Some(graph_craft::generic!(T)), skip_deduplication: true, ..Default::default() diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index edc544daef..64c78a79be 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -3,10 +3,6 @@ use axum::routing::get; use axum::Router; use fern::colors::{Color, ColoredLevelConfig}; -use graphite_editor::application::Editor; -use graphite_editor::messages::prelude::*; -use graphite_editor::node_graph_executor::GraphRuntimeRequest; -use graphite_editor::node_graph_executor::NODE_RUNTIME; use graphite_editor::node_graph_executor::*; use std::sync::Mutex; diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 973b2f4d24..1333ec7433 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -12,7 +12,7 @@ pub mod debug; pub mod extract_xy; pub mod generic; pub mod gradient; -mod graphic_element; +pub mod graphic_element; pub mod instances; pub mod logic; pub mod math; @@ -35,7 +35,7 @@ pub use blending::*; pub use context::*; pub use ctor; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; -pub use graphic_element::*; +pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; pub use memo::MemoHash; pub use num_traits; pub use raster::Color; @@ -161,7 +161,7 @@ where pub trait NodeInputDecleration { const INDEX: usize; - fn identifier() -> &'static str; + fn identifier() -> ProtoNodeIdentifier; type Result; } diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 66464eef4f..1a124d2068 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -2,6 +2,7 @@ use crate::{Node, WasmNotSend}; use dyn_any::DynFuture; use std::future::Future; use std::hash::DefaultHasher; +use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::sync::Arc; use std::sync::Mutex; @@ -49,6 +50,10 @@ impl MemoNode { } } +pub mod memo { + pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode"); +} + /// Caches the output of a given Node and acts as a proxy. /// In contrast to the regular `MemoNode`. This node ignores all input. /// Using this node might result in the document not updating properly, @@ -98,6 +103,10 @@ impl ImpureMemoNode { } } +pub mod impure_memo { + pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode"); +} + /// Stores both what a node was called with and what it returned. #[derive(Clone, Debug)] pub struct IORecord { @@ -142,7 +151,10 @@ impl MonitorNode { } } -use std::hash::{Hash, Hasher}; +pub mod monitor { + pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode"); +} + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct MemoHash { hash: u64, diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 8dd53ba91a..5d405df093 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -1,4 +1,4 @@ -use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend}; +use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend}; use dyn_any::{DynAny, StaticType}; use std::borrow::Cow; use std::collections::HashMap; @@ -103,11 +103,11 @@ pub enum RegistryValueSource { Scope(&'static str), } -type NodeRegistry = LazyLock>>>; +type NodeRegistry = LazyLock>>>; pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new())); -pub static NODE_METADATA: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); +pub static NODE_METADATA: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); #[cfg(not(target_arch = "wasm32"))] pub type DynFuture<'n, T> = Pin + 'n + Send>>; diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 156d9a85d2..48ee2d804c 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -1,6 +1,7 @@ use std::any::TypeId; pub use std::borrow::Cow; +use std::ops::Deref; #[macro_export] macro_rules! concrete { @@ -128,12 +129,37 @@ impl std::fmt::Debug for NodeIOTypes { pub struct ProtoNodeIdentifier { pub name: Cow<'static, str>, } + impl From for ProtoNodeIdentifier { fn from(value: String) -> Self { Self { name: Cow::Owned(value) } } } +impl From<&'static str> for ProtoNodeIdentifier { + fn from(s: &'static str) -> Self { + ProtoNodeIdentifier { name: Cow::Borrowed(s) } + } +} + +impl ProtoNodeIdentifier { + pub const fn new(name: &'static str) -> Self { + ProtoNodeIdentifier { name: Cow::Borrowed(name) } + } + + pub const fn with_owned_string(name: String) -> Self { + ProtoNodeIdentifier { name: Cow::Owned(name) } + } +} + +impl Deref for ProtoNodeIdentifier { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.name.as_ref() + } +} + fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { use serde::Deserialize; @@ -306,6 +332,13 @@ impl Type { Self::Future(output) => output.replace_nested(f), } } + + pub fn to_cow_string(&self) -> Cow<'static, str> { + match self { + Type::Generic(name) => name.clone(), + _ => Cow::Owned(self.to_string()), + } + } } fn format_type(ty: &str) -> String { @@ -343,19 +376,3 @@ impl std::fmt::Display for Type { write!(f, "{}", result) } } - -impl From<&'static str> for ProtoNodeIdentifier { - fn from(s: &'static str) -> Self { - ProtoNodeIdentifier { name: Cow::Borrowed(s) } - } -} - -impl ProtoNodeIdentifier { - pub const fn new(name: &'static str) -> Self { - ProtoNodeIdentifier { name: Cow::Borrowed(name) } - } - - pub const fn with_owned_string(name: String) -> Self { - ProtoNodeIdentifier { name: Cow::Owned(name) } - } -} diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 9830052147..08679ce417 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -486,10 +486,6 @@ impl DocumentNodeImplementation { } } - pub const fn proto(name: &'static str) -> Self { - Self::ProtoNode(ProtoNodeIdentifier::new(name)) - } - pub fn output_count(&self) -> usize { match self { DocumentNodeImplementation::Network(network) => network.exports.len(), @@ -1268,7 +1264,6 @@ impl<'a> Iterator for RecursiveNodeIter<'a> { mod test { use super::*; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; - use graphene_core::ProtoNodeIdentifier; use std::sync::atomic::AtomicU64; fn gen_node_id() -> NodeId { @@ -1540,7 +1535,7 @@ mod test { NodeId(1), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), ..Default::default() }, ), @@ -1548,7 +1543,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 1)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), ..Default::default() }, ), @@ -1575,7 +1570,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![result_node_input], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), ..Default::default() }, ), diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 00402776fb..c8c896290c 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -190,9 +190,9 @@ tagged_value! { VectorData(graphene_core::vector::VectorDataTable), #[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code RasterData(graphene_core::raster_types::RasterDataTable), - #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code + #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code GraphicGroup(graphene_core::GraphicGroupTable), - #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code + #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code ArtboardGroup(graphene_core::ArtboardGroupTable), // ============ // STRUCT TYPES diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 097653c2a9..5c05ef62ba 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -20,7 +20,7 @@ mod tests { NodeId(0), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), ..Default::default() }, ), diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index ab4c744e36..e0f52dae20 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -39,7 +39,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc syn::Result quote!(stringify!(#path).replace(' ', "")), - None => quote!(std::module_path!().rsplit_once("::").unwrap().0), + + let identifier = format_ident!("{}_proto_ident", fn_name); + let identifier_path = match parsed.attributes.path.as_ref() { + Some(path) => { + let path = path.to_token_stream().to_string().replace(' ', ""); + quote!(#path) + } + None => quote!(std::module_path!()), }; - let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name))); let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?; let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake)); @@ -354,6 +358,11 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result #graphene_core::ProtoNodeIdentifier { + #graphene_core::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name))) + } + #[doc(inline)] pub use #mod_name::#struct_name; @@ -418,67 +427,63 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result TokenStream2 { - if parsed.attributes.skip_impl { - return quote! {}; - } +fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &Ident) -> TokenStream2 { let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake)); - let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics); - let mut generated_input_accessor = Vec::new(); - for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() { - let mut ty = match parsed_input { - ParsedField::Regular { ty, .. } => ty, - ParsedField::Node { output_type, .. } => output_type, + if !parsed.attributes.skip_impl { + let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics); + + for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() { + let mut ty = match parsed_input { + ParsedField::Regular { ty, .. } => ty, + ParsedField::Node { output_type, .. } => output_type, + } + .clone(); + + // We only want the necessary generics. + let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty); + // TODO: figure out a better name that doesn't conflict with so many types + let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal)); + let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter()); + + // Only create structs with phantom data where necessary. + generated_input_accessor.push(if phantom_data_declerations.is_empty() { + quote! { + pub struct #struct_name; + } + } else { + quote! { + pub struct #struct_name <#(#used),*>{ + #(#phantom_data_declerations,)* + } + } + }); + generated_input_accessor.push(quote! { + impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> { + const INDEX: usize = #input_index; + fn identifier() -> #graphene_core::ProtoNodeIdentifier { + #inputs_module_name::IDENTIFIER.clone() + } + type Result = #ty; + } + }) } - .clone(); - - // We only want the necessary generics. - let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty); - // TODO: figure out a better name that doesn't conflict with so many types - let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal)); - let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter()); - - // Only create structs with phantom data where necessary. - generated_input_accessor.push(if phantom_data_declerations.is_empty() { - quote! { - pub struct #struct_name; - } - } else { - quote! { - pub struct #struct_name <#(#used),*>{ - #(#phantom_data_declerations,)* - } - } - }); - generated_input_accessor.push(quote! { - impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> { - const INDEX: usize = #input_index; - fn identifier() -> &'static str { - protonode_identifier() - } - type Result = #ty; - } - }) } quote! { pub mod #inputs_module_name { use super::*; - pub fn protonode_identifier() -> &'static str { - // Storing the string in a once lock should reduce allocations (since we call this in a loop)? - static NODE_NAME: std::sync::OnceLock = std::sync::OnceLock::new(); - NODE_NAME.get_or_init(|| #identifier ) - } + /// The `ProtoNodeIdentifier` of this node without any generics attached to it + pub const IDENTIFIER: #graphene_core::ProtoNodeIdentifier = #identifier(); #(#generated_input_accessor)* } } @@ -511,7 +516,7 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator Result { +fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &Ident) -> Result { if parsed.attributes.skip_impl { return Ok(quote!()); } @@ -604,7 +609,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st fn register_node() { let mut registry = NODE_REGISTRY.lock().unwrap(); registry.insert( - #identifier, + #identifier(), vec![ #(#constructors,)* ] diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 50c265f92f..e0b4a01685 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -6,7 +6,7 @@ use graphene_std::registry::*; use graphene_std::*; use std::collections::{HashMap, HashSet}; -pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap) { +pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap) { if network.generated { return; } @@ -15,7 +15,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap expand_network(node_network, substitutions), DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { - if let Some(new_node) = substitutions.get(proto_node_identifier.name.as_ref()) { + if let Some(new_node) = substitutions.get(proto_node_identifier) { node.implementation = new_node.implementation.clone(); } } @@ -24,7 +24,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap HashMap { +pub fn generate_node_substitutions() -> HashMap { let mut custom = HashMap::new(); let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() { @@ -49,7 +49,7 @@ pub fn generate_node_substitutions() -> HashMap { let input_count = inputs.len(); let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); - let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); + let identity_node = ops::identity::IDENTIFIER; let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; From a1d93da532d0ffa88a2235ab6faecbdee32d6d2d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 7 Jul 2025 18:39:04 -0700 Subject: [PATCH 04/11] Fix 'Grid' node migrations breaking up-to-date documents --- editor/src/messages/portfolio/document_migration.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 7567883c5b..f3b018a6cf 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -639,6 +639,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap(); let index_3_value = old_inputs.get(3).cloned(); + let mut upgraded = false; + if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { if matches!(*tagged_value, TaggedValue::DVec2(_)) { // Move index 3 to the end @@ -648,11 +650,14 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - } else { - // Swap it back if we're not changing anything - let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); + + upgraded = true; } } + + if !upgraded { + let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); + } } // Ensure layers are positioned as stacks if they are upstream siblings of another layer From 8a68683a9339bf230579e3faa33c42c0932bad22 Mon Sep 17 00:00:00 2001 From: mTvare Date: Tue, 8 Jul 2025 14:54:54 +0530 Subject: [PATCH 05/11] Add pivot type selection with Custom Pivot, Origin (Average Point), and Origin (Active Object) to the Select tool (#2730) * add origin * cleanup pivot * a lot of stuff * reset pivot * fix transform with pivot issues * fixes * some more cleanup * fixes * finally works * origin fixes * fix spaces * fix using dragged_layers * simplify pivot logic * fix bugs * fix the final bug * fix in select_tool * fix updates * some more refactors to fix misunderstanding and refactor * add checkboxes * fix labels * fix stuff which broke at merge * update * cargo fmt * fix serde crash * fix pivot not updating on move * fix pivot not becoming last active refernce * fix redraw issues * add: active pivot * cargo fmt * fix pivot showing up in default mode * add: pivot pin * fix: use pin icons * cargo: cargo lock update? * fix: use checkbox instead of Overlays * refactor: add dot to path_tool * add: active origins * UI tweaks * add: add all of the stuff for path tool * remove: unused layer * fix: pivot pinning and origin angle * fix: pin only if moved in first place * cargo: fmt * fix: pivot use disabled method * fix: remove redudant NoOp * fix: 3 stuff * fix: select from elsewhere * fix: compass rose wobbling around * add: move pivot on grab * add: move pivot on nudge * add: move pivot on Grab * Code review and tooltips * fixes * fixes * fixes * fix: skipping artboard on bounds calculation * fix: by default have origin * Fix prior fix --------- Co-authored-by: Keavon Chambers --- Cargo.lock | 1751 +++++++++-------- editor/src/consts.rs | 3 +- .../utility_types/widgets/input_widgets.rs | 2 + .../document/document_message_handler.rs | 28 + .../graph_operation_message.rs | 6 +- .../graph_operation_message_handler.rs | 9 - .../document/graph_operation/utility_types.rs | 8 +- .../node_graph/document_node_definitions.rs | 3 - .../document/node_graph/utility_types.rs | 2 +- .../document/overlays/utility_types.rs | 62 +- .../portfolio/document/utility_types/nodes.rs | 18 + .../document/utility_types/transformation.rs | 12 - .../tool/common_functionality/compass_rose.rs | 42 +- .../graph_modification_utils.rs | 22 +- .../tool/common_functionality/pivot.rs | 311 ++- .../common_functionality/utility_functions.rs | 36 +- .../src/messages/tool/tool_message_handler.rs | 5 + .../messages/tool/tool_messages/path_tool.rs | 96 +- .../tool/tool_messages/select_tool.rs | 271 ++- .../messages/tool/tool_messages/shape_tool.rs | 2 +- .../messages/tool/tool_messages/text_tool.rs | 3 - .../transform_layer_message.rs | 2 + .../transform_layer_message_handler.rs | 299 +-- .../widgets/inputs/ReferencePointInput.svelte | 3 +- frontend/src/messages.ts | 3 + node-graph/gcore/src/transform_nodes.rs | 1 - node-graph/gcore/src/vector/vector_nodes.rs | 3 +- 27 files changed, 1864 insertions(+), 1139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index da16bddb21..5487e27d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "Inflector" @@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,9 +20,9 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" [[package]] name = "addr2line" @@ -35,21 +35,21 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -63,9 +63,12 @@ dependencies = [ [[package]] name = "aligned-vec" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] [[package]] name = "alloc-no-stdlib" @@ -95,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 2.9.0", + "bitflags 2.9.1", "cc", "cesu8", "jni", @@ -138,9 +141,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -153,44 +156,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" @@ -206,7 +209,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -233,7 +236,7 @@ version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.8.6", + "libloading 0.8.8", ] [[package]] @@ -267,15 +270,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "av1-grain" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" dependencies = [ "anyhow", "arrayvec", @@ -287,18 +290,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" +checksum = "19135c0c7a60bfee564dbe44ab5ce0557c6bf3884e5291a50be76a15640c4fbd" dependencies = [ "arrayvec", ] [[package]] name = "axum" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", "bytes", @@ -309,7 +312,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "itoa 1.0.15", + "itoa", "matchit", "memchr", "mime", @@ -330,12 +333,12 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", @@ -350,9 +353,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -434,9 +437,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -501,9 +504,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -512,9 +515,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -528,9 +531,9 @@ checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" @@ -543,13 +546,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -579,7 +582,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cairo-sys-rs", "glib", "libc", @@ -604,7 +607,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "log", "polling", "rustix 0.38.44", @@ -626,9 +629,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -674,9 +677,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -712,9 +715,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -730,9 +733,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -772,9 +775,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -782,9 +785,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -794,21 +797,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "codespan-reporting" @@ -842,9 +845,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -931,9 +934,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -964,8 +967,8 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", @@ -988,8 +991,8 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "libc", ] @@ -1058,9 +1061,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -1092,9 +1095,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -1108,15 +1111,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", + "itoa", "matches", - "phf 0.8.0", + "phf 0.10.1", "proc-macro2", "quote", "smallvec", @@ -1130,7 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1140,14 +1143,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "cursor-icon" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "darling" @@ -1170,7 +1173,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1181,7 +1184,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1213,15 +1216,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.19" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1258,7 +1261,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1273,7 +1276,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", ] @@ -1285,7 +1288,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1294,7 +1297,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.6", + "libloading 0.8.8", ] [[package]] @@ -1311,13 +1314,13 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1337,9 +1340,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" dependencies = [ "serde", ] @@ -1381,7 +1384,7 @@ dependencies = [ "dyn-any", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1398,9 +1401,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embed-resource" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" +checksum = "0963f530273dc3022ab2bdc3fcd6d488e850256f2284a82b7413cb9481ee85dd" dependencies = [ "cc", "memchr", @@ -1437,17 +1440,37 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1466,12 +1489,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1556,9 +1579,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -1578,15 +1601,15 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "font-types" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d868ec188a98bb014c606072edd47e52e7ab7297db943b0b28503121e1d037bd" +checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf" dependencies = [ "bytemuck", ] @@ -1612,9 +1635,9 @@ dependencies = [ [[package]] name = "fontconfig-parser" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ "roxmltree", ] @@ -1683,7 +1706,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1784,7 +1807,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1958,34 +1981,36 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] name = "gif" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -2042,9 +2067,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" dependencies = [ "serde", ] @@ -2055,7 +2080,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "futures-channel", "futures-core", "futures-executor", @@ -2083,7 +2108,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2140,7 +2165,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-alloc-types", ] @@ -2150,7 +2175,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2167,11 +2192,11 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-descriptor-types", "hashbrown 0.15.4", ] @@ -2182,7 +2207,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2281,7 +2306,7 @@ dependencies = [ "num-traits", "parley", "petgraph 0.7.1", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "rustc-hash 2.1.1", "serde", @@ -2302,7 +2327,7 @@ dependencies = [ "log", "math-parser", "node-macro", - "rand 0.9.0", + "rand 0.9.1", ] [[package]] @@ -2334,7 +2359,7 @@ dependencies = [ "image", "ndarray", "node-macro", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "serde", "specta", @@ -2363,7 +2388,7 @@ dependencies = [ "log", "ndarray", "node-macro", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "reqwest", "tokio", @@ -2413,7 +2438,7 @@ name = "graphite-editor" version = "0.0.0" dependencies = [ "bezier-rs", - "bitflags 2.9.0", + "bitflags 2.9.1", "derivative", "dyn-any", "env_logger", @@ -2451,7 +2476,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2521,7 +2546,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2536,9 +2561,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -2546,7 +2571,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.1", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -2555,9 +2580,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "bytemuck", "cfg-if", @@ -2596,15 +2621,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hermit-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -2620,27 +2639,25 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "html5ever" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", + "match_token", ] [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -2655,12 +2672,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -2678,12 +2695,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "1.6.0" @@ -2698,7 +2709,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.15", + "itoa", "pin-project-lite", "smallvec", "tokio", @@ -2707,11 +2718,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", @@ -2741,21 +2751,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2780,7 +2797,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2794,16 +2811,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.2", ] [[package]] @@ -2838,16 +2856,30 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap 0.8.0", + "tinystr 0.8.1", + "writeable 0.6.1", + "zerovec", +] + [[package]] name = "icu_locid" version = "1.5.0" @@ -2855,37 +2887,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "litemap 0.7.5", + "tinystr 0.7.6", + "writeable 0.5.5", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -2893,67 +2904,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", - "tinystr", - "writeable", + "tinystr 0.8.1", + "writeable 0.6.1", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2973,9 +2971,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -2983,16 +2981,16 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", - "image-webp 0.2.1", + "image-webp 0.2.3", "num-traits", "png", "qoi", @@ -3016,9 +3014,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b" dependencies = [ "byteorder-lite", "quick-error", @@ -3049,9 +3047,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -3081,7 +3079,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3108,6 +3106,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -3123,7 +3131,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.5.0", + "hermit-abi", "libc", "windows-sys 0.59.0", ] @@ -3162,12 +3170,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.15" @@ -3197,6 +3199,30 @@ dependencies = [ "system-deps", ] +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "jni" version = "0.21.1" @@ -3221,18 +3247,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -3272,7 +3299,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "unicode-segmentation", ] @@ -3284,7 +3311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.6", + "libloading 0.8.8", "pkg-config", ] @@ -3296,14 +3323,13 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kuchikiki" -version = "0.8.2" +version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 1.9.3", - "matches", + "indexmap 2.10.0", "selectors", ] @@ -3356,9 +3382,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.170" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libfuzzer-sys" @@ -3382,29 +3408,29 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.2", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", ] [[package]] @@ -3415,9 +3441,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -3425,6 +3451,12 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "litrs" version = "0.4.1" @@ -3433,9 +3465,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -3443,9 +3475,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" @@ -3456,6 +3488,12 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -3473,18 +3511,29 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf 0.11.3", + "phf_codegen 0.11.3", "string_cache", "string_cache_codegen", "tendril", ] +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "matches" version = "0.1.10" @@ -3511,9 +3560,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ "autocfg", "rawpointer", @@ -3531,9 +3580,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -3559,7 +3608,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -3582,9 +3631,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -3592,20 +3641,20 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "muda" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" +checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" dependencies = [ "crossbeam-channel", "dpi", @@ -3630,11 +3679,11 @@ checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.7.1", + "indexmap 2.10.0", "log", "petgraph 0.6.5", "rustc-hash 1.1.0", @@ -3682,7 +3731,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.5.0+25.2.9519653", @@ -3697,7 +3746,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -3747,7 +3796,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3805,7 +3854,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3839,23 +3888,24 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3905,11 +3955,11 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "libc", "objc2 0.6.1", @@ -3919,27 +3969,27 @@ dependencies = [ "objc2-core-graphics", "objc2-core-image", "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.0", + "objc2-quartz-core 0.3.1", ] [[package]] name = "objc2-cloud-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] [[package]] name = "objc2-core-data" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] @@ -3950,18 +4000,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dispatch2", "objc2 0.6.1", ] [[package]] name = "objc2-core-graphics" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", + "dispatch2", "objc2 0.6.1", "objc2-core-foundation", "objc2-io-surface", @@ -3969,9 +4020,9 @@ dependencies = [ [[package]] name = "objc2-core-image" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" dependencies = [ "objc2 0.6.1", "objc2-foundation 0.3.1", @@ -3983,7 +4034,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ba833d4a1cb1aac330f8c973fd92b6ff1858e4aef5cdd00a255eefb28022fb5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2-core-foundation", ] @@ -4014,7 +4065,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -4026,7 +4077,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "libc", "objc2 0.6.1", @@ -4035,11 +4086,11 @@ dependencies = [ [[package]] name = "objc2-io-surface" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-core-foundation", ] @@ -4050,7 +4101,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4062,7 +4113,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4071,22 +4122,22 @@ dependencies = [ [[package]] name = "objc2-quartz-core" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] [[package]] name = "objc2-ui-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -4094,11 +4145,11 @@ dependencies = [ [[package]] name = "objc2-web-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "objc2 0.6.1", "objc2-app-kit", @@ -4117,9 +4168,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "oorandom" @@ -4141,11 +4198,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types 0.3.2", "libc", @@ -4162,7 +4219,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4173,9 +4230,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -4200,9 +4257,9 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" dependencies = [ "libc", "windows-sys 0.59.0", @@ -4244,9 +4301,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -4254,13 +4311,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", "smallvec", "windows-targets 0.52.6", ] @@ -4333,9 +4390,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -4344,9 +4401,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -4354,24 +4411,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -4383,7 +4439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.7.1", + "indexmap 2.10.0", ] [[package]] @@ -4393,7 +4449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap 2.7.1", + "indexmap 2.10.0", ] [[package]] @@ -4402,9 +4458,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros 0.8.0", "phf_shared 0.8.0", - "proc-macro-hack", ] [[package]] @@ -4413,7 +4467,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ + "phf_macros 0.10.0", "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] @@ -4438,12 +4494,12 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] @@ -4478,12 +4534,12 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro-hack", "proc-macro2", "quote", @@ -4500,7 +4556,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4556,13 +4612,13 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" dependencies = [ "base64 0.22.1", - "indexmap 2.7.1", - "quick-xml 0.32.0", + "indexmap 2.10.0", + "quick-xml", "serde", "time", ] @@ -4610,24 +4666,24 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", - "rustix 0.38.44", + "rustix 1.0.7", "tracing", "windows-sys 0.59.0", ] [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -4638,6 +4694,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -4646,11 +4711,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -4709,7 +4774,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.24", + "toml_edit 0.22.27", ] [[package]] @@ -4755,7 +4820,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4766,30 +4831,30 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4825,29 +4890,21 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.32.0" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", + "cfg_aliases 0.2.1", "pin-project-lite", "quinn-proto", "quinn-udp", @@ -4857,17 +4914,19 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", + "web-time 1.1.0", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", "ring", "rustc-hash 2.1.1", "rustls", @@ -4881,9 +4940,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases 0.2.1", "libc", @@ -4895,13 +4954,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.7.3" @@ -4929,13 +4994,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.23", ] [[package]] @@ -4983,7 +5047,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -4992,7 +5056,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", ] [[package]] @@ -5056,9 +5120,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.11" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" dependencies = [ "avif-serialize", "imgref", @@ -5108,7 +5172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f" dependencies = [ "bytemuck", - "font-types 0.8.3", + "font-types 0.8.4", ] [[package]] @@ -5142,11 +5206,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -5155,11 +5219,31 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "regex" version = "1.11.1" @@ -5197,9 +5281,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", @@ -5217,28 +5301,25 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", @@ -5246,7 +5327,6 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "windows-registry", ] [[package]] @@ -5277,13 +5357,13 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -5296,7 +5376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "serde_derive", ] @@ -5309,9 +5389,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -5340,7 +5420,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5349,22 +5429,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.2", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "once_cell", "ring", @@ -5374,29 +5454,21 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time 1.1.0", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", @@ -5405,9 +5477,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rustybuzz" @@ -5415,7 +5487,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "core_maths", "log", @@ -5466,6 +5538,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -5475,7 +5571,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5509,7 +5605,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -5528,22 +5624,20 @@ dependencies = [ [[package]] name = "selectors" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", "log", - "matches", "phf 0.8.0", "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] @@ -5594,7 +5688,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5605,7 +5699,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5614,7 +5708,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "itoa 1.0.15", + "itoa", "memchr", "ryu", "serde", @@ -5626,7 +5720,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ - "itoa 1.0.15", + "itoa", "serde", ] @@ -5638,14 +5732,14 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -5657,22 +5751,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.15", + "itoa", "ryu", "serde", ] [[package]] name = "serde_with" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.3", "serde", "serde_derive", "serde_json", @@ -5682,14 +5778,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5716,9 +5812,9 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" dependencies = [ "nodrop", "stable_deref_trait", @@ -5726,9 +5822,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -5737,12 +5833,13 @@ dependencies = [ [[package]] name = "shared_child" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +checksum = "c2778001df1384cf20b6dc5a5a90f48da35539885edaaefd887f8d744e939c0b" dependencies = [ "libc", - "windows-sys 0.59.0", + "sigchld", + "windows-sys 0.60.2", ] [[package]] @@ -5751,6 +5848,36 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sigchld" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1219ef50fc0fdb04fcc243e6aa27f855553434ffafe4fa26554efb78b5b4bf89" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -5819,12 +5946,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "slotmap" @@ -5850,7 +5974,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -5880,9 +6004,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5904,7 +6028,7 @@ dependencies = [ "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", "raw-window-handle", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", "wasm-bindgen", "web-sys", "windows-sys 0.59.0", @@ -5956,7 +6080,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5974,7 +6098,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -6043,9 +6167,9 @@ checksum = "94afda9cd163c04f6bee8b4bf2501c91548deae308373c436f36aeff3cf3c4a3" [[package]] name = "svg_fmt" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] name = "svgtypes" @@ -6092,9 +6216,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -6112,13 +6236,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6127,7 +6251,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -6157,12 +6281,12 @@ dependencies = [ [[package]] name = "tao" -version = "0.32.8" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1" +checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-graphics 0.24.0", "crossbeam-channel", "dispatch", @@ -6188,8 +6312,8 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -6202,7 +6326,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6213,17 +6337,16 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.4.1" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d08db1ff9e011e04014e737ec022610d756c0eae0b3b3a9037bccaf3003173a" +checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" dependencies = [ "anyhow", "bytes", "dirs", "dunce", "embed_plist", - "futures-util", - "getrandom 0.2.15", + "getrandom 0.3.3", "glob", "gtk", "heck 0.5.0", @@ -6236,6 +6359,7 @@ dependencies = [ "objc2 0.6.1", "objc2-app-kit", "objc2-foundation 0.3.1", + "objc2-ui-kit", "percent-encoding", "plist", "raw-window-handle", @@ -6258,14 +6382,14 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.60.0", + "windows 0.61.3", ] [[package]] name = "tauri-build" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd20e4661c2cce65343319e6e8da256958f5af958cafc47c0d0af66a55dcd17" +checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" dependencies = [ "anyhow", "cargo_toml", @@ -6273,7 +6397,7 @@ dependencies = [ "glob", "heck 0.5.0", "json-patch", - "schemars", + "schemars 0.8.22", "semver", "serde", "serde_json", @@ -6285,9 +6409,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458258b19032450ccf975840116ecf013e539eadbb74420bd890e8c56ab2b1a4" +checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" dependencies = [ "base64 0.22.1", "brotli", @@ -6301,7 +6425,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.99", + "syn 2.0.104", "tauri-utils", "thiserror 2.0.12", "time", @@ -6312,28 +6436,28 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.1.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d402813d3b9c773a0fa58697c457c771f10e735498fdcb7b343264d18e5a601f" +checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "tauri-codegen", "tauri-utils", ] [[package]] name = "tauri-plugin" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4190775d6ff73fe66d9af44c012739a2659720efd9c0e1e56a918678038699d" +checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" dependencies = [ "anyhow", "glob", "plist", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "tauri-utils", @@ -6343,15 +6467,15 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.2.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88371e340ad2f07409a3b68294abe73f20bc9c1bc1b631a31dc37a3d0161f682" +checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f" dependencies = [ "anyhow", "dunce", "glob", "percent-encoding", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_repr", @@ -6361,14 +6485,13 @@ dependencies = [ "thiserror 2.0.12", "toml", "url", - "uuid", ] [[package]] name = "tauri-plugin-http" -version = "2.4.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40dcd6c922a1885e1f0bcebc6768fec6e005bd4b9001c5d90a2f5d4cab297729" +checksum = "b0c1a38da944b357ffa23bafd563b1579f18e6fbd118fcd84769406d35dcc5c7" dependencies = [ "bytes", "cookie_store", @@ -6376,7 +6499,7 @@ dependencies = [ "http", "regex", "reqwest", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "tauri", @@ -6390,16 +6513,16 @@ dependencies = [ [[package]] name = "tauri-plugin-shell" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d5eb3368b959937ad2aeaf6ef9a8f5d11e01ffe03629d3530707bbcb27ff5d" +checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25" dependencies = [ "encoding_rs", "log", "open", "os_pipe", "regex", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "shared_child", @@ -6411,29 +6534,31 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.5.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00ada7ac2f9276f09b8c3afffd3215fd5d9bff23c22df8a7c70e7ef67cacd532" +checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" dependencies = [ "cookie", "dpi", "gtk", "http", "jni", + "objc2 0.6.1", + "objc2-ui-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror 2.0.12", "url", - "windows 0.60.0", + "windows 0.61.3", ] [[package]] name = "tauri-runtime-wry" -version = "2.5.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2e5842c57e154af43a20a49c7efee0ce2578c20b4c2bdf266852b422d2e421" +checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" dependencies = [ "gtk", "http", @@ -6452,15 +6577,15 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.60.0", + "windows 0.61.3", "wry", ] [[package]] name = "tauri-utils" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f037e66c7638cc0a2213f61566932b9a06882b8346486579c90e4b019bac447" +checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" dependencies = [ "anyhow", "brotli", @@ -6479,7 +6604,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "schemars", + "schemars 0.8.22", "semver", "serde", "serde-untagged", @@ -6496,25 +6621,25 @@ dependencies = [ [[package]] name = "tauri-winres" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0" +checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" dependencies = [ "embed-resource", + "indexmap 2.10.0", "toml", ] [[package]] name = "tempfile" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.1", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -6538,12 +6663,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.69" @@ -6570,7 +6689,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6581,7 +6700,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6602,7 +6721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa 1.0.15", + "itoa", "num-conv", "powerfmt", "serde", @@ -6657,6 +6776,15 @@ name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -6689,9 +6817,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -6711,7 +6839,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6736,9 +6864,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -6749,21 +6877,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.24", + "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] @@ -6774,7 +6902,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "toml_datetime", "winnow 0.5.40", ] @@ -6785,24 +6913,31 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.3", + "toml_write", + "winnow 0.7.11", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.2" @@ -6819,6 +6954,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -6844,18 +6997,18 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "tray-icon" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5" +checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" dependencies = [ "crossbeam-channel", "dirs", @@ -7076,12 +7229,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -7096,19 +7243,21 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] name = "v_frame" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" dependencies = [ "aligned-vec", "num-traits", @@ -7221,15 +7370,15 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -7256,7 +7405,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -7291,7 +7440,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7320,9 +7469,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", @@ -7334,11 +7483,11 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "rustix 0.38.44", "wayland-backend", "wayland-scanner", @@ -7350,16 +7499,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" dependencies = [ "rustix 0.38.44", "wayland-client", @@ -7372,7 +7521,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -7384,7 +7533,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7397,7 +7546,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7411,7 +7560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", - "quick-xml 0.37.2", + "quick-xml", "quote", ] @@ -7503,24 +7652,24 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.8" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] [[package]] name = "webview2-com" -version = "0.36.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d606f600e5272b514dbb66539dd068211cc20155be8d3958201b4b5bd79ed3" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.60.0", - "windows-core 0.60.1", - "windows-implement 0.59.0", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.0", "windows-interface 0.59.1", ] @@ -7532,25 +7681,25 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "webview2-com-sys" -version = "0.36.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb27fccd3c27f68e9a6af1bcf48c2d82534b8675b83608a4d81446d095a17ac" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ "thiserror 2.0.12", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" @@ -7585,11 +7734,11 @@ checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "cfg_aliases 0.1.1", "document-features", - "indexmap 2.7.1", + "indexmap 2.10.0", "log", "naga", "once_cell", @@ -7631,7 +7780,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.0", + "bitflags 2.9.1", "block", "bytemuck", "cfg_aliases 0.1.1", @@ -7644,7 +7793,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.6", + "libloading 0.8.8", "log", "metal", "naga", @@ -7672,7 +7821,7 @@ version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "js-sys", "web-sys", ] @@ -7735,12 +7884,12 @@ dependencies = [ [[package]] name = "windows" -version = "0.60.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.60.1", + "windows-core 0.61.2", "windows-future", "windows-link", "windows-numerics", @@ -7748,20 +7897,11 @@ dependencies = [ [[package]] name = "windows-collections" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.60.1", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", + "windows-core 0.61.2", ] [[package]] @@ -7779,25 +7919,26 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.60.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.59.0", + "windows-implement 0.60.0", "windows-interface 0.59.1", "windows-link", - "windows-result 0.3.1", - "windows-strings 0.3.1", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] name = "windows-future" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.60.1", + "windows-core 0.61.2", "windows-link", + "windows-threading", ] [[package]] @@ -7808,18 +7949,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "windows-implement" -version = "0.59.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7830,7 +7971,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7841,34 +7982,34 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.60.1", + "windows-core 0.61.2", "windows-link", ] [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -7882,9 +8023,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] @@ -7901,9 +8042,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -7944,6 +8085,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -7983,7 +8133,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -7991,10 +8141,35 @@ dependencies = [ ] [[package]] -name = "windows-version" -version = "0.1.3" +name = "windows-targets" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfbcc4996dd183ff1376a20ade1242da0d2dcaff83cc76710a588d24fd4c5db" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" dependencies = [ "windows-link", ] @@ -8017,6 +8192,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8035,6 +8216,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8053,12 +8240,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -8077,6 +8276,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -8095,6 +8300,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -8113,6 +8324,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -8131,6 +8348,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winit" version = "0.29.15" @@ -8140,7 +8363,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "calloop", "cfg_aliases 0.1.1", @@ -8190,38 +8413,32 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" version = "0.5.5" @@ -8229,10 +8446,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] -name = "wry" -version = "0.50.5" +name = "writeable" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wry" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" dependencies = [ "base64 0.22.1", "block2 0.6.1", @@ -8266,8 +8489,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -8302,7 +8525,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.6", + "libloading 0.8.8", "once_cell", "rustix 0.38.44", "x11rb-protocol", @@ -8316,9 +8539,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xkbcommon-dl" @@ -8326,7 +8549,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dlib", "log", "once_cell", @@ -8341,9 +8564,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xmlwriter" @@ -8365,9 +8588,9 @@ checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -8377,13 +8600,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "synstructure", ] @@ -8395,43 +8618,22 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" -dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8451,7 +8653,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "synstructure", ] @@ -8462,10 +8664,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -8474,13 +8687,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8500,9 +8713,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +checksum = "7384255a918371b5af158218d131530f694de9ad3815ebdd0453a940485cb0fa" dependencies = [ "zune-core", ] diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 9adba6d908..58585e2dab 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.; pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.; pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; pub const PIVOT_DIAMETER: f64 = 5.; +pub const DOWEL_PIN_RADIUS: f64 = 4.; // COMPASS ROSE pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; @@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5; // COLORS pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff"; -pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)"; pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848"; +pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b"; pub const COLOR_OVERLAY_GREEN: &str = "#63ce63"; pub const COLOR_OVERLAY_RED: &str = "#ef5454"; pub const COLOR_OVERLAY_GRAY: &str = "#cccccc"; diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs index b76f23a067..893b301cd8 100644 --- a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -471,6 +471,8 @@ pub struct ReferencePointInput { pub disabled: bool, + pub tooltip: String, + // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index f306663a0e..89e0aaac92 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -750,6 +750,7 @@ impl MessageHandler> for DocumentMessag // Nudge translation without resizing if !resize { let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y))); + responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation }); for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) { responses.add(GraphOperationMessage::TransformChange { @@ -1185,6 +1186,7 @@ impl MessageHandler> for DocumentMessag OverlaysType::HoverOutline => visibility_settings.hover_outline = visible, OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible, OverlaysType::Pivot => visibility_settings.pivot = visible, + OverlaysType::Origin => visibility_settings.origin = visible, OverlaysType::Path => visibility_settings.path = visible, OverlaysType::Anchors => { visibility_settings.anchors = visible; @@ -1716,6 +1718,14 @@ impl DocumentMessageHandler { .reduce(graphene_std::renderer::Quad::combine_bounds) } + pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> { + self.network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&self.network_interface) + .map(|layer| self.metadata().nonzero_bounding_box(layer)) + .reduce(graphene_std::renderer::Quad::combine_bounds) + } + pub fn document_network(&self) -> &NodeNetwork { self.network_interface.document_network() } @@ -2269,6 +2279,24 @@ impl DocumentMessageHandler { ] }, }, + LayoutGroup::Row { + widgets: { + let mut checkbox_id = CheckboxId::default(); + vec![ + CheckboxInput::new(self.overlays_visibility_settings.pivot) + .on_update(|optional_input: &CheckboxInput| { + DocumentMessage::SetOverlaysVisibility { + visible: optional_input.checked, + overlays_type: Some(OverlaysType::Origin), + } + .into() + }) + .for_label(checkbox_id.clone()) + .widget_holder(), + TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), + ] + }, + }, LayoutGroup::Row { widgets: { let mut checkbox_id = CheckboxId::default(); diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index abb1aa8594..31ea831218 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::prelude::*; use bezier_rs::Subpath; -use glam::{DAffine2, DVec2, IVec2}; +use glam::{DAffine2, IVec2}; use graph_craft::document::NodeId; use graphene_std::Artboard; use graphene_std::brush::brush_stroke::BrushStroke; @@ -52,10 +52,6 @@ pub enum GraphOperationMessage { transform_in: TransformIn, skip_rerender: bool, }, - TransformSetPivot { - layer: LayerNodeIdentifier, - pivot: DVec2, - }, Vector { layer: LayerNodeIdentifier, modification_type: VectorModificationType, diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 375ae5ef0f..40653e4af4 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -89,15 +89,6 @@ impl MessageHandler> for Gr modify_inputs.transform_set(transform, transform_in, skip_rerender); } } - GraphOperationMessage::TransformSetPivot { layer, pivot } => { - if layer == LayerNodeIdentifier::ROOT_PARENT { - log::error!("Cannot run TransformSetPivot on ROOT_PARENT"); - return; - } - if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { - modify_inputs.pivot_set(pivot); - } - } GraphOperationMessage::Vector { layer, modification_type } => { if layer == LayerNodeIdentifier::ROOT_PARENT { log::error!("Cannot run Vector on ROOT_PARENT"); diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 216dc8bf27..23a878f044 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector}; use crate::messages::prelude::*; use bezier_rs::Subpath; -use glam::{DAffine2, DVec2, IVec2}; +use glam::{DAffine2, IVec2}; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; @@ -458,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> { } } - pub fn pivot_set(&mut self, new_pivot: DVec2) { - let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return }; - - self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false); - } - pub fn vector_modify(&mut self, modification_type: VectorModificationType) { let Some(path_node_id) = self.existing_node_id("Path", true) else { return }; self.network_interface.vector_modify(&path_node_id, modification_type); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index ce9378857f..594f8125ee 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -1308,7 +1308,6 @@ fn static_nodes() -> Vec { NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), - NodeInput::value(TaggedValue::DVec2(DVec2::splat(0.5)), false), ], implementation: DocumentNodeImplementation::Network(NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0)], @@ -1327,7 +1326,6 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(f64), 2), NodeInput::network(concrete!(DVec2), 3), NodeInput::network(concrete!(DVec2), 4), - NodeInput::network(concrete!(DVec2), 5), ], manual_composition: Some(concrete!(Context)), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER), @@ -1395,7 +1393,6 @@ fn static_nodes() -> Vec { }), ), InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())), - InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden), ], output_names: vec!["Data".to_string()], ..Default::default() diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 0cfd49282f..e752c9d7ce 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -191,7 +191,7 @@ pub struct FrontendClickTargets { pub modify_import_export: Vec, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum Direction { Up, Down, diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index a17d711a38..6e25bb77b3 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,12 +1,12 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, - COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, + COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, }; use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; use core::borrow::Borrow; -use core::f64::consts::{FRAC_PI_2, TAU}; +use core::f64::consts::{FRAC_PI_2, PI, TAU}; use glam::{DAffine2, DVec2}; use graphene_std::Color; use graphene_std::math::quad::Quad; @@ -33,12 +33,14 @@ pub enum OverlaysType { HoverOutline, SelectionOutline, Pivot, + Origin, Path, Anchors, Handles, } #[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[serde(default)] pub struct OverlaysVisibilitySettings { pub all: bool, pub artboard_name: bool, @@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings { pub hover_outline: bool, pub selection_outline: bool, pub pivot: bool, + pub origin: bool, pub path: bool, pub anchors: bool, pub handles: bool, @@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings { hover_outline: true, selection_outline: true, pivot: true, + origin: true, path: true, anchors: true, handles: true, @@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings { self.all && self.pivot } + pub fn origin(&self) -> bool { + self.all && self.origin + } + pub fn path(&self) -> bool { self.all && self.path } @@ -423,10 +431,7 @@ impl OverlayContext { pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) { let sign = scale.signum(); - let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()) - .unwrap() - .with_alpha(0.05) - .to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb(); fill_color.insert(0, '#'); let fill_color = Some(fill_color.as_str()); self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None); @@ -463,10 +468,7 @@ impl OverlayContext { // Hover ring if show_hover_ring { - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) - .unwrap() - .with_alpha(0.5) - .to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb(); fill_color.insert(0, '#'); self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH); @@ -550,6 +552,36 @@ impl OverlayContext { self.end_dpi_aware_transform(); } + pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) { + let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL); + + self.start_dpi_aware_transform(); + + // Draw the background circle with a white fill and blue outline + self.render_context.begin_path(); + self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle"); + self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE); + self.render_context.fill(); + self.render_context.set_stroke_style_str(color); + self.render_context.stroke(); + + // Draw the two blue filled sectors + self.render_context.begin_path(); + // Top-left sector + self.render_context.move_to(x, y); + self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc"); + self.render_context.close_path(); + // Bottom-right sector + self.render_context.move_to(x, y); + self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc"); + self.render_context.close_path(); + self.render_context.set_fill_style_str(color); + self.render_context.fill(); + + self.end_dpi_aware_transform(); + } + /// Used by the Pen and Path tools to outline the path of the shape. pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) { self.start_dpi_aware_transform(); @@ -599,9 +631,11 @@ impl OverlayContext { pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) { self.start_dpi_aware_transform(); + let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb(); + self.render_context.begin_path(); self.bezier_command(bezier, transform, true); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50); + self.render_context.set_stroke_style_str(&color); self.render_context.set_line_width(4.); self.render_context.stroke(); @@ -731,11 +765,11 @@ impl OverlayContext { // └──┴──┴──┴──┘ let pixels = [(0, 0), (2, 2)]; for &(x, y) in &pixels { - let index = (x + y * PATTERN_WIDTH as usize) * 4; + let index = (x + y * PATTERN_WIDTH) * 4; data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb()); } - let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&mut data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap(); + let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap(); pattern_context.put_image_data(&image_data, 0., 0.).unwrap(); let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap(); diff --git a/editor/src/messages/portfolio/document/utility_types/nodes.rs b/editor/src/messages/portfolio/document/utility_types/nodes.rs index d81f1693d7..66369026b3 100644 --- a/editor/src/messages/portfolio/document/utility_types/nodes.rs +++ b/editor/src/messages/portfolio/document/utility_types/nodes.rs @@ -1,5 +1,7 @@ use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use super::network_interface::NodeNetworkInterface; +use crate::messages::tool::common_functionality::graph_modification_utils; +use glam::DVec2; use graph_craft::document::{NodeId, NodeNetwork}; use serde::ser::SerializeStruct; @@ -98,6 +100,22 @@ impl SelectedNodes { .filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface)) } + pub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 { + let (sum, count) = self + .selected_visible_and_unlocked_layers(network_interface) + .map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface)) + .fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1)); + if count == 0 { DVec2::ZERO } else { sum / count as f64 } + } + + pub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 { + let (sum, count) = self + .selected_visible_and_unlocked_layers(network_interface) + .map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface)) + .fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1)); + if count == 0 { DVec2::ZERO } else { sum / count as f64 } + } + pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator + 'a { metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node())) } diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index e0149939a5..cefcb61418 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -4,7 +4,6 @@ use crate::messages::portfolio::document::graph_operation::transform_utils; use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::prelude::*; -use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::utility_types::ToolType; use glam::{DAffine2, DMat2, DVec2}; @@ -537,17 +536,6 @@ impl<'a> Selected<'a> { } } - pub fn mean_average_of_pivots(&mut self) -> DVec2 { - let xy_summation = self - .selected - .iter() - .map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface)) - .reduce(|a, b| a + b) - .unwrap_or_default(); - - xy_summation / self.selected.len() as f64 - } - pub fn center_of_aabb(&mut self) -> DVec2 { let [min, max] = self .selected diff --git a/editor/src/messages/tool/common_functionality/compass_rose.rs b/editor/src/messages/tool/common_functionality/compass_rose.rs index 8a7a2f6b2a..bedc1ca230 100644 --- a/editor/src/messages/tool/common_functionality/compass_rose.rs +++ b/editor/src/messages/tool/common_functionality/compass_rose.rs @@ -1,5 +1,4 @@ use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; -use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::DocumentMessageHandler; use glam::{DAffine2, DVec2}; use std::f64::consts::FRAC_PI_2; @@ -10,25 +9,32 @@ pub struct CompassRose { } impl CompassRose { - fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 { - let [min, max] = document.metadata().nonzero_bounding_box(layer); - - let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - let layer_transform = document.metadata().transform_to_viewport(layer); - layer_transform * bounds_transform - } pub fn refresh_position(&mut self, document: &DocumentMessageHandler) { - let selected_nodes = document.network_interface.selected_nodes(); - let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); + let selected = document.network_interface.selected_nodes(); - let Some(first) = layers.next() else { return }; - let count = layers.count() + 1; - let transform = if count == 1 { - Self::get_layer_pivot_transform(first, document) - } else { - let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); - DAffine2::from_translation(min) * DAffine2::from_scale(max - min) - }; + if !selected.has_selected_nodes() { + return; + } + + let transform = selected + .selected_visible_and_unlocked_layers(&document.network_interface) + .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface)) + .unwrap_or_default(); + + let bounds = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter_map(|layer| { + document + .metadata() + .bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer)) + }) + .reduce(graphene_std::renderer::Quad::combine_bounds); + + let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]); + let transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min); self.compass_center = transform.transform_point2(DVec2::splat(0.5)); } diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 22c53f1117..33c19d0c0d 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -243,20 +243,26 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN LayerNodeIdentifier::new_unchecked(id) } -/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work) -pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { - let pivot_node_input_index = 5; - if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? { - Some(*pivot) +/// Locate the origin of the transform node +pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + use graphene_std::transform_nodes::transform::TranslateInput; + + if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", TranslateInput::INDEX)? { + Some(*origin) } else { None } } -pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { +pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { + let origin = get_origin(layer, network_interface).unwrap_or_default(); + network_interface.document_metadata().document_to_viewport.transform_point2(origin) +} + +pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer); - let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5)); - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot) + let center = DVec2::splat(0.5); + network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center) } /// Get the current gradient of a layer from the closest "Fill" node. diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 67ce7f5dc5..10ea38371c 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -1,26 +1,184 @@ -//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. +//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale. -use super::graph_modification_utils; use crate::consts::PIVOT_DIAMETER; -use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate; +use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate; +use crate::messages::tool::tool_messages::tool_prelude::*; use glam::{DAffine2, DVec2}; -use graphene_std::transform::ReferencePoint; -use std::collections::VecDeque; +use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId}; +use std::fmt; -#[derive(Clone, Debug)] +pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder { + IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24) + .tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.") + .disabled(!enabled) + .on_update(move |_| match source { + PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(), + PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(), + }) + .widget_holder() +} + +pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder { + ReferencePointInput::new(reference_point) + .tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.") + .disabled(disabled) + .on_update(move |pivot_input: &ReferencePointInput| match source { + PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(), + PivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(), + }) + .widget_holder() +} + +pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec { + let gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active] + .iter() + .map(|gizmo_type| { + MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({ + let value = source.clone(); + move |_| match value { + PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(), + PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(), + } + }) + }) + .collect(); + + vec![ + CheckboxInput::new(!state.disabled) + .tooltip( + "Pivot Gizmo\n\ + \n\ + Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\ + Disabled: rotation and scaling occurs about the center of the selection bounds.", + ) + .on_update(move |optional_input: &CheckboxInput| match source { + PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(), + PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(), + }) + .widget_holder(), + Separator::new(SeparatorType::Related).widget_holder(), + DropdownInput::new(vec![gizmo_type_entries]) + .selected_index(Some(match state.gizmo_type { + PivotGizmoType::Pivot => 0, + PivotGizmoType::Average => 1, + PivotGizmoType::Active => 2, + })) + .tooltip( + "Pivot Gizmo Type\n\ + \n\ + Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\ + \n\ + Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\ + Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\ + Origin (Active Object): rotates and scales about the origin of the most recently selected layer.", + ) + .disabled(state.disabled) + .widget_holder(), + ] +} + +#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub enum PivotToolSource { + Path, + #[default] + Select, +} + +#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct PivotGizmo { + pub pivot: Pivot, + pub state: PivotGizmoState, + pub layer: Option, + pub point: Option, +} + +impl PivotGizmo { + pub fn position(&self, document: &DocumentMessageHandler) -> DVec2 { + let network = &document.network_interface; + (!self.state.disabled) + .then_some({ + match self.state.gizmo_type { + PivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)), + PivotGizmoType::Pivot => self.pivot.pivot, + PivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)), + } + }) + .flatten() + .unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5))) + } + + pub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 { + self.pivot.recalculate_pivot(document); + self.pivot.transform_from_normalized + } + + pub fn pin_active(&self) -> bool { + self.pivot.pinned && self.state.is_pivot_type() + } + + pub fn pivot_disconnected(&self) -> bool { + self.pivot.old_pivot_position == ReferencePoint::None + } +} + +#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum PivotGizmoType { + // Pivot + #[default] + Pivot, + // Origin + Average, + Active, + // TODO: Add "Individual" +} + +#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct PivotGizmoState { + pub disabled: bool, + pub gizmo_type: PivotGizmoType, +} + +impl PivotGizmoState { + pub fn is_pivot_type(&self) -> bool { + self.gizmo_type == PivotGizmoType::Pivot || self.disabled + } + + pub fn is_pivot(&self) -> bool { + self.gizmo_type == PivotGizmoType::Pivot && !self.disabled + } +} + +impl fmt::Display for PivotGizmoType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PivotGizmoType::Pivot => write!(f, "Custom Pivot"), + PivotGizmoType::Average => write!(f, "Origin (Average Point)"), + PivotGizmoType::Active => write!(f, "Origin (Active Object)"), + // TODO: Add "Origin (Individual)" + } + } +} + +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Pivot { /// Pivot between (0,0) and (1,1) normalized_pivot: DVec2, /// Transform to get from normalized pivot to viewspace - transform_from_normalized: DAffine2, - /// The viewspace pivot position (if applicable) - pivot: Option, + pub transform_from_normalized: DAffine2, + /// The viewspace pivot position + pub pivot: Option, /// The old pivot position in the GUI, used to reduce refreshes of the document bar - old_pivot_position: ReferencePoint, + pub old_pivot_position: ReferencePoint, + /// The last ReferencePoint which wasn't none + pub last_non_none_reference_point: ReferencePoint, /// Used to enable and disable the pivot - active: bool, + pub pinned: bool, + /// Had selected_visible_and_unlocked_layers + pub empty: bool, } impl Default for Pivot { @@ -30,84 +188,62 @@ impl Default for Pivot { transform_from_normalized: Default::default(), pivot: Default::default(), old_pivot_position: ReferencePoint::Center, - active: true, + last_non_none_reference_point: ReferencePoint::Center, + pinned: false, + empty: true, } } } impl Pivot { - /// Calculates the transform that gets from normalized pivot to viewspace. - fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 { - let [min, max] = document.metadata().nonzero_bounding_box(layer); - - let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - let layer_transform = document.metadata().transform_to_viewport(layer); - layer_transform * bounds_transform - } - /// Recomputes the pivot position and transform. - fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { - if !self.active { + pub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { + let selected = document.network_interface.selected_nodes(); + self.empty = !selected.has_selected_nodes(); + if !selected.has_selected_nodes() { return; } - let selected_nodes = document.network_interface.selected_nodes(); - let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); - let Some(first) = layers.next() else { - // If no layers are selected then we revert things back to default + let transform = selected + .selected_visible_and_unlocked_layers(&document.network_interface) + .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface)) + .unwrap_or_default(); + + let bounds = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter_map(|layer| { + document + .metadata() + .bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer)) + }) + .reduce(graphene_std::renderer::Quad::combine_bounds); + + let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]); + self.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min); + + if self.old_pivot_position != ReferencePoint::None { + self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot)); + } + } + + pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) { + let selected = document.network_interface.selected_nodes(); + if !selected.has_selected_nodes() { self.normalized_pivot = DVec2::splat(0.5); self.pivot = None; return; }; - // Add one because the first item is consumed above. - let selected_layers_count = layers.count() + 1; - - // If just one layer is selected we can use its inner transform (as it accounts for rotation) - if selected_layers_count == 1 { - let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5)); - self.normalized_pivot = normalized_pivot; - self.transform_from_normalized = Self::get_layer_pivot_transform(first, document); - self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot)); - } else { - // If more than one layer is selected we use the AABB with the mean of the pivots - let xy_summation = document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface)) - .reduce(|a, b| a + b) - .unwrap_or_default(); - - let pivot = xy_summation / selected_layers_count as f64; - self.pivot = Some(pivot); - let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); - self.normalized_pivot = (pivot - min) / (max - min); - - self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - } - } - - pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) { - if !overlay_context.visibility_settings.pivot() { - self.active = false; - return; - } else { - self.active = true; - } - - self.recalculate_pivot(document); - if let (Some(pivot), Some(data)) = (self.pivot, draw_data) { - overlay_context.pivot(pivot, data.0); - } + let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]); + self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); + self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot)); } /// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas). pub fn should_refresh_pivot_position(&mut self) -> bool { - if !self.active { - return false; - } - let new = self.to_pivot_position(); let should_refresh = new != self.old_pivot_position; self.old_pivot_position = new; @@ -118,37 +254,24 @@ impl Pivot { self.normalized_pivot.into() } - /// Sets the viewport position of the pivot for all selected layers. - pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - if !self.active { + /// Sets the viewport position of the pivot. + pub fn set_viewport_position(&mut self, position: DVec2) { + if self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON { return; - } + }; - for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { - let transform = Self::get_layer_pivot_transform(layer, document); - // Only update the pivot when computed position is finite. - if transform.matrix2.determinant().abs() <= f64::EPSILON { - return; - }; - let pivot = transform.inverse().transform_point2(position); - responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); - } + self.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position); + self.pivot = Some(position); } - /// Set the pivot using the normalized transform that is set above. - pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - if !self.active { - return; - } - - self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses); + /// Set the pivot using a normalized position. + pub fn set_normalized_position(&mut self, position: DVec2) { + self.normalized_pivot = position; + self.pivot = Some(self.transform_from_normalized.transform_point2(position)); } /// Answers if the pointer is currently positioned over the pivot. pub fn is_over(&self, mouse: DVec2) -> bool { - if !self.active { - return false; - } self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some() } } diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 23829271b0..6ae3f130b5 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -393,6 +393,7 @@ pub fn transforming_transform_cage( input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, layers_dragging: &mut Vec, + center_of_transformation: Option, ) -> (bool, bool, bool) { let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| { let edges = bounding_box.check_selected_edges(input.mouse.position); @@ -429,17 +430,12 @@ pub fn transforming_transform_cage( } }); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - layers_dragging, - responses, - &document.network_interface, - None, - &ToolType::Select, - None, - ); - bounds.center_of_transformation = selected.mean_average_of_pivots(); + bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| { + document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface) + }); // Check if we're hovering over a skew triangle let edges = bounds.check_selected_edges(input.mouse.position); @@ -469,18 +465,12 @@ pub fn transforming_transform_cage( } }); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - &selected, - responses, - &document.network_interface, - None, - &ToolType::Select, - None, - ); - - bounds.center_of_transformation = selected.mean_average_of_pivots(); + bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| { + document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface) + }); } *layers_dragging = selected; diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 9413495488..e20d0989b1 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -181,6 +181,11 @@ impl MessageHandler> for ToolMessageHandler { send: Box::new(TransformLayerMessage::SelectionChanged.into()), }); + responses.add(BroadcastMessage::SubscribeEvent { + on: BroadcastEvent::SelectionChanged, + send: Box::new(SelectToolMessage::SyncHistory.into()), + }); + self.tool_is_active = true; let tool_data = &mut self.tool_state.tool_data; diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 54a6ceb4c5..0642613cf0 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -11,6 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget}; use crate::messages::tool::common_functionality::shape_editor::{ ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, }; @@ -18,6 +19,7 @@ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandi use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate}; use bezier_rs::{Bezier, BezierHandles, TValue}; use graphene_std::renderer::Quad; +use graphene_std::transform::ReferencePoint; use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData}; use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType}; use std::vec; @@ -106,6 +108,9 @@ pub enum PathToolMessage { SelectedPointYChanged { new_y: f64, }, + SetPivot { + position: ReferencePoint, + }, SwapSelectedHandles, UpdateOptions(PathOptionsUpdate), UpdateSelectedPointsStatus { @@ -141,6 +146,9 @@ pub enum PathOptionsUpdate { OverlayModeType(PathOverlayMode), PointEditingMode { enabled: bool }, SegmentEditingMode { enabled: bool }, + PivotGizmoType(PivotGizmoType), + TogglePivotGizmoType(bool), + TogglePivotPinned, } impl ToolMetadata for PathTool { @@ -255,6 +263,20 @@ impl LayoutHolder for PathTool { .selected_index(Some(self.options.path_overlay_mode as u32)) .widget_holder(); + let [_checkbox, _dropdown] = { + let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path); + [pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()] + }; + + let has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty(); + let _pivot_reference = pivot_reference_point_widget( + has_something || !self.tool_data.pivot_gizmo.state.is_pivot(), + self.tool_data.pivot_gizmo.pivot.to_pivot_position(), + PivotToolSource::Path, + ); + + let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ x_location, @@ -268,8 +290,16 @@ impl LayoutHolder for PathTool { point_editing_mode, related_seperator.clone(), segment_editing_mode, - unrelated_seperator, + unrelated_seperator.clone(), path_overlay_mode_widget, + unrelated_seperator.clone(), + // checkbox.clone(), + // related_seperator.clone(), + // dropdown.clone(), + // unrelated_seperator, + // pivot_reference, + // related_seperator.clone(), + // pin_pivot, ], }])) } @@ -293,6 +323,29 @@ impl<'a> MessageHandler> for PathToo self.options.path_editing_mode.segment_editing_mode = enabled; responses.add(OverlaysMessage::Draw); } + PathOptionsUpdate::PivotGizmoType(gizmo_type) => { + if !self.tool_data.pivot_gizmo.state.disabled { + self.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type; + responses.add(ToolMessage::UpdateHints); + let pivot_gizmo = self.tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(NodeGraphMessage::RunDocumentGraph); + self.send_layout(responses, LayoutTarget::ToolOptions); + } + } + PathOptionsUpdate::TogglePivotGizmoType(state) => { + self.tool_data.pivot_gizmo.state.disabled = !state; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + self.send_layout(responses, LayoutTarget::ToolOptions); + } + + PathOptionsUpdate::TogglePivotPinned => { + self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + self.send_layout(responses, LayoutTarget::ToolOptions); + } }, ToolMessage::Path(PathToolMessage::ClosePath) => { responses.add(DocumentMessage::AddTransaction); @@ -443,6 +496,8 @@ struct PathToolData { last_click_time: u64, dragging_state: DraggingState, angle: f64, + pivot_gizmo: PivotGizmo, + ordered_points: Vec, opposite_handle_position: Option, last_clicked_point_was_selected: bool, last_clicked_segment_was_selected: bool, @@ -1315,6 +1370,16 @@ impl PathToolData { } } } + + fn pivot_gizmo(&self) -> PivotGizmo { + self.pivot_gizmo.clone() + } + + fn sync_history(&mut self, points: &[ManipulatorPointId]) { + self.ordered_points.retain(|layer| points.contains(layer)); + self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer))); + self.pivot_gizmo.point = self.ordered_points.last().copied() + } } impl Fsm for PathToolFsmState { @@ -1327,6 +1392,10 @@ impl Fsm for PathToolFsmState { update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options); let ToolMessage::Path(event) = event else { return self }; + + // TODO(mTvare6): Remove once gizmos are implemented for path_tool + tool_data.pivot_gizmo.state.disabled = true; + match (self, event) { (_, PathToolMessage::SelectionChanged) => { // Set the newly targeted layers to visible @@ -1343,6 +1412,9 @@ impl Fsm for PathToolFsmState { shape_editor.update_selected_anchors_status(display_anchors); shape_editor.update_selected_handles_status(display_handles); + let new_points = shape_editor.selected_points().copied().collect::>(); + tool_data.sync_history(&new_points); + self } (_, PathToolMessage::Overlays(mut overlay_context)) => { @@ -1710,14 +1782,8 @@ impl Fsm for PathToolFsmState { } (PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => { tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize); - - if !tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() { - tool_data.saved_points_before_anchor_convert_smooth_sharp.clear(); - } - - if tool_data.adjacent_anchor_offset.is_some() { - tool_data.adjacent_anchor_offset = None; - } + tool_data.saved_points_before_anchor_convert_smooth_sharp.clear(); + tool_data.adjacent_anchor_offset = None; tool_data.stored_selection = None; responses.add(OverlaysMessage::Draw); @@ -2208,6 +2274,18 @@ impl Fsm for PathToolFsmState { responses.add(DocumentMessage::EndTransaction); PathToolFsmState::Ready } + (_, PathToolMessage::SetPivot { position }) => { + responses.add(DocumentMessage::StartTransaction); + + tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position; + let position: Option = position.into(); + tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap()); + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(NodeGraphMessage::RunDocumentGraph); + + self + } (_, _) => PathToolFsmState::Ready, } } diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 76158d08e5..9ae3459a7d 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -12,9 +12,10 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; use crate::messages::tool::common_functionality::measure; -use crate::messages::tool::common_functionality::pivot::Pivot; +use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget}; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::transformation_cage::*; @@ -43,6 +44,9 @@ pub struct SelectOptions { #[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum SelectOptionsUpdate { NestedSelectionBehavior(NestedSelectionBehavior), + PivotGizmoType(PivotGizmoType), + TogglePivotGizmoType(bool), + TogglePivotPinned, } #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] @@ -95,6 +99,14 @@ pub enum SelectToolMessage { SetPivot { position: ReferencePoint, }, + SyncHistory, + ShiftSelectedNodes { + offset: DVec2, + }, + PivotShift { + offset: Option, + flush: bool, + }, } impl ToolMetadata for SelectTool { @@ -122,14 +134,12 @@ impl SelectTool { DropdownInput::new(vec![layer_selection_behavior_entries]) .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32)) - .tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") - .widget_holder() - } - - fn pivot_reference_point_widget(&self, disabled: bool) -> WidgetHolder { - ReferencePointInput::new(self.tool_data.pivot.to_pivot_position()) - .on_update(|pivot_input: &ReferencePointInput| SelectToolMessage::SetPivot { position: pivot_input.value }.into()) - .disabled(disabled) + .tooltip( + "Selection Mode\n\ + \n\ + Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\ + Deep Select: clicks directly select the most-nested layers in the folder hierarchy.", + ) .widget_holder() } @@ -178,7 +188,7 @@ impl SelectTool { fn boolean_widgets(&self, selected_count: usize) -> impl Iterator + use<> { let list = ::list(); - list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| { + list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| { let mut tooltip = info.label.to_string(); if let Some(doc) = info.docstring.as_deref() { tooltip.push_str("\n\n"); @@ -203,9 +213,29 @@ impl LayoutHolder for SelectTool { // Select mode (Deep/Shallow) widgets.push(self.deep_selection_widget()); - // Pivot + // Pivot gizmo type (checkbox + dropdown for pivot/origin) widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(self.pivot_reference_point_widget(self.tool_data.selected_layers_count == 0)); + widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select)); + + if self.tool_data.pivot_gizmo.state.is_pivot_type() { + // Nine-position reference point widget + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + widgets.push(pivot_reference_point_widget( + self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(), + self.tool_data.pivot_gizmo.pivot.to_pivot_position(), + PivotToolSource::Select, + )); + + // Pivot pin button + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + + let pin_active = self.tool_data.pivot_gizmo.pin_active(); + let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled; + + if pin_active || pin_enabled { + widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select)); + } + } // Align let disabled = self.tool_data.selected_layers_count < 2; @@ -244,14 +274,43 @@ impl LayoutHolder for SelectTool { impl<'a> MessageHandler> for SelectTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message { - self.tool_data.nested_selection_behavior = nested_selection_behavior; - responses.add(ToolMessage::UpdateHints); + let mut redraw_reference_pivot = false; + + if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message { + match option_update { + SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => { + self.tool_data.nested_selection_behavior = *nested_selection_behavior; + responses.add(ToolMessage::UpdateHints); + } + SelectOptionsUpdate::PivotGizmoType(gizmo_type) => { + if !self.tool_data.pivot_gizmo.state.disabled { + self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type; + responses.add(ToolMessage::UpdateHints); + let pivot_gizmo = self.tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(NodeGraphMessage::RunDocumentGraph); + redraw_reference_pivot = true; + } + } + SelectOptionsUpdate::TogglePivotGizmoType(state) => { + self.tool_data.pivot_gizmo.state.disabled = !state; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + redraw_reference_pivot = true; + } + + SelectOptionsUpdate::TogglePivotPinned => { + self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + redraw_reference_pivot = true; + } + } } self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false); - if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed { + if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot { // Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) self.send_layout(responses, LayoutTarget::ToolOptions); self.tool_data.selected_layers_changed = false; @@ -323,7 +382,8 @@ struct SelectToolData { drag_current: ViewportPosition, lasso_polygon: Vec, selection_mode: Option, - layers_dragging: Vec, + layers_dragging: Vec, // Unordered, often used as temporary buffer + ordered_layers: Vec, // Ordered list of layers layer_selected_on_start: Option, select_single_layer: Option, axis_align: bool, @@ -331,7 +391,9 @@ struct SelectToolData { bounding_box_manager: Option, snap_manager: SnapManager, cursor: MouseCursorIcon, - pivot: Pivot, + pivot_gizmo: PivotGizmo, + pivot_gizmo_start: Option, + pivot_gizmo_shift: Option, compass_rose: CompassRose, line_center: DVec2, skew_edge: EdgeBool, @@ -497,6 +559,24 @@ impl SelectToolData { responses.add(NodeGraphMessage::SendGraph); self.layers_dragging = original; } + + fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option { + match self.pivot_gizmo.state.gizmo_type { + PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot), + _ => None, + } + } + + fn pivot_gizmo(&self) -> PivotGizmo { + self.pivot_gizmo.clone() + } + + fn sync_history(&mut self, document: &DocumentMessageHandler) { + let layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect(); + self.ordered_layers.retain(|layer| layers.contains(layer)); + self.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer))); + self.pivot_gizmo.layer = self.ordered_layers.last().copied() + } } impl Fsm for SelectToolFsmState { @@ -710,8 +790,63 @@ impl Fsm for SelectToolFsmState { .flatten() }); - // Update pivot - tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,))); + let mut active_origin = None; + let mut origin_angle = 0.; + if overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() { + let get_angle = |layer: LayerNodeIdentifier| -> f64 { + let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]); + let bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad; + (bounds.top_left() - bounds.top_right()).to_angle() + }; + if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average { + let mut count = 0_usize; + + let sum: f64 = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .map(get_angle) + .inspect(|_| count += 1) + .sum(); + if count > 0 { + origin_angle = sum / count as f64; + } + } else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active { + origin_angle = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer) + .iter() + .map(|&layer| get_angle(layer)) + .sum(); + } + + for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { + let origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface); + if Some(layer) == tool_data.pivot_gizmo.layer { + active_origin = Some(origin); + continue; + } + overlay_context.dowel_pin(origin, origin_angle, None); + } + } + if let Some(origin) = active_origin { + overlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW)); + } + + let has_layers = document.network_interface.selected_nodes().has_selected_nodes(); + let draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers; + tool_data.pivot_gizmo.pivot.recalculate_pivot(document); + let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten(); + if let Some(pivot) = pivot { + let offset = tool_data + .pivot_gizmo_start + .map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default()) + .unwrap_or_default(); + let shift = tool_data.pivot_gizmo_shift.unwrap_or_default(); + overlay_context.pivot(pivot + offset + shift, angle); + } // Update compass rose if overlay_context.visibility_settings.compass_rose() { @@ -837,6 +972,7 @@ impl Fsm for SelectToolFsmState { (SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color), } } + self } (_, SelectToolMessage::EditLayer) => { @@ -868,7 +1004,8 @@ impl Fsm for SelectToolFsmState { let intersection_list = document.click_list(input).collect::>(); let intersection = document.find_deepest(&intersection_list); - let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); + let position = tool_data.pivot_gizmo().position(document); + let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position)); // If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode. @@ -883,20 +1020,17 @@ impl Fsm for SelectToolFsmState { let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); let mouse_position = input.mouse.position; let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle); - let is_over_pivot = tool_data.pivot.is_over(mouse_position); let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none()); - let state = if is_over_pivot - // Dragging the pivot - { + let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) { responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); // tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - SelectToolFsmState::DraggingPivot + state } // Dragging one (or two, forming a corner) of the transform cage bounding box edges else if resize { @@ -917,12 +1051,14 @@ impl Fsm for SelectToolFsmState { } tool_data.layers_dragging = selected; - tool_data.get_snap_candidates(document, input); let (axis, using_compass) = { let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose); (axis_state.unwrap_or_default(), axis_state.is_some()) }; + + tool_data.pivot_gizmo_start = Some(tool_data.drag_current); + SelectToolFsmState::Dragging { axis, using_compass, @@ -941,6 +1077,12 @@ impl Fsm for SelectToolFsmState { let extend = input.keyboard.key(extend_selection); if !extend && !input.keyboard.key(remove_from_selection) { responses.add(DocumentMessage::DeselectAllLayers); + + if !tool_data.pivot_gizmo.pivot.pinned { + let position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point; + responses.add(SelectToolMessage::SetPivot { position }); + } + tool_data.layers_dragging.clear(); } @@ -955,6 +1097,9 @@ impl Fsm for SelectToolFsmState { tool_data.get_snap_candidates(document, input); responses.add(DocumentMessage::StartTransaction); + + tool_data.pivot_gizmo_start = Some(tool_data.drag_current); + SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false, @@ -1098,7 +1243,10 @@ impl Fsm for SelectToolFsmState { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => { let mouse_position = input.mouse.position; let snapped_mouse_position = mouse_position; - tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses); + + tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position); + + responses.add(NodeGraphMessage::RunDocumentGraph); // Auto-panning let messages = [ @@ -1143,7 +1291,7 @@ impl Fsm for SelectToolFsmState { .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); // Dragging the pivot overrules the other operations - if tool_data.pivot.is_over(input.mouse.position) { + if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() { cursor = MouseCursorIcon::Move; } @@ -1283,20 +1431,32 @@ impl Fsm for SelectToolFsmState { tool_data.snap_manager.cleanup(responses); tool_data.select_single_layer = None; + if let Some(start) = tool_data.pivot_gizmo_start { + let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default(); + if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() { + *v += offset; + } + } + tool_data.pivot_gizmo_start = None; + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } ( - SelectToolFsmState::ResizingBounds - | SelectToolFsmState::SkewingBounds { .. } - | SelectToolFsmState::RotatingBounds - | SelectToolFsmState::Dragging { .. } - | SelectToolFsmState::DraggingPivot, + SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter, ) => { let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON; let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction }; + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(response); + tool_data.axis_align = false; tool_data.snap_manager.cleanup(responses); @@ -1432,8 +1592,48 @@ impl Fsm for SelectToolFsmState { (_, SelectToolMessage::SetPivot { position }) => { responses.add(DocumentMessage::StartTransaction); + tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position; + tool_data.pivot_gizmo.pivot.pinned = false; + let pos: Option = position.into(); - tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses); + + tool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap()); + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + + self + } + (_, SelectToolMessage::SyncHistory) => { + tool_data.sync_history(document); + + self + } + (_, SelectToolMessage::ShiftSelectedNodes { offset }) => { + let offset = document.metadata().document_to_viewport.transform_vector2(offset); + if tool_data.pivot_gizmo.pivot_disconnected() { + if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() { + *v += offset; + } + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + } + + self + } + (_, SelectToolMessage::PivotShift { offset, flush }) => { + if flush { + tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default()); + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + return self; + } + if tool_data.pivot_gizmo.pivot_disconnected() { + tool_data.pivot_gizmo_shift = offset; + } self } @@ -1658,6 +1858,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque, selected: Vec, - pivot: Pivot, snap_candidates: Vec, // TODO: Handle multiple layers in the future layer_dragging: Option, @@ -526,7 +524,6 @@ impl Fsm for TextToolFsmState { } bounding_box_manager.render_overlays(&mut overlay_context, false); - tool_data.pivot.update_pivot(document, &mut overlay_context, None); } } else { tool_data.bounding_box_manager.take(); diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message.rs b/editor/src/messages/tool/transform_layer/transform_layer_message.rs index e68d2702c4..c819260e58 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message.rs @@ -2,6 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::transformation::TransformType; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::pivot::PivotGizmo; use glam::DVec2; #[impl_message(Message, ToolMessage, TransformLayer)] @@ -29,4 +30,5 @@ pub enum TransformLayerMessage { TypeDecimalPoint, TypeDigit { digit: u8 }, TypeNegate, + SetPivotGizmo { pivot_gizmo: PivotGizmo }, } diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index e449bbd3b8..7258163772 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::misc::PTZ; use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing}; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType}; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::{ToolData, ToolType}; @@ -34,8 +35,11 @@ pub struct TransformLayerMessageHandler { start_mouse: ViewportPosition, original_transforms: OriginalTransforms, + pivot_gizmo: PivotGizmo, pivot: ViewportPosition, + path_bounds: Option<[DVec2; 2]>, + local_pivot: DocumentPosition, local_mouse_start: DocumentPosition, grab_target: DocumentPosition, @@ -61,27 +65,64 @@ impl TransformLayerMessageHandler { } } -fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &VectorData, viewspace: DAffine2, get_location: impl Fn(&ManipulatorPointId) -> Option) -> Option<(DVec2, DVec2)> { +fn calculate_pivot( + document: &DocumentMessageHandler, + selected_points: &Vec<&ManipulatorPointId>, + vector_data: &VectorData, + viewspace: DAffine2, + get_location: impl Fn(&ManipulatorPointId) -> Option, + gizmo: &mut PivotGizmo, +) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) { + let average_position = || { + let mut point_count = 0_usize; + selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64 + }; + let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| { + if let Some([mut min, mut max]) = acc { + min.x = min.x.min(point.x); + min.y = min.y.min(point.y); + max.x = max.x.max(point.x); + max.y = max.y.max(point.y); + Some([min, max]) + } else { + Some([point, point]) + } + }); + gizmo.pivot.recalculate_pivot_for_layer(document, bounds); + let position = || { + (if !gizmo.state.disabled { + match gizmo.state.gizmo_type { + PivotGizmoType::Average => None, + PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)), + PivotGizmoType::Pivot => gizmo.pivot.pivot, + } + } else { + None + }) + .unwrap_or_else(average_position) + }; let [point] = selected_points.as_slice() else { // Handle the case where there are multiple points - let mut point_count = 0; - let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64; - - return Some((average_position, average_position)); + let position = position(); + return (Some((position, position)), bounds); }; match point { ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => { // Get the anchor position and transform it to the pivot - let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?; - let target = viewspace.transform_point2(point.get_position(vector_data)?); - Some((pivot_pos, target)) + let (Some(pivot_position), Some(position)) = ( + point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)), + point.get_position(vector_data), + ) else { + return (None, None); + }; + let target = viewspace.transform_point2(position); + (Some((pivot_position, target)), None) } _ => { // Calculate the average position of all selected points - let mut point_count = 0; - let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64; - Some((average_position, average_position)) + let position = position(); + (Some((position, position)), bounds) } } } @@ -177,18 +218,17 @@ impl MessageHandler> for TransformLayer } if !using_path_tool { - *selected.pivot = selected.mean_average_of_pivots(); + self.pivot_gizmo.recalculate_transform(document); + *selected.pivot = self.pivot_gizmo.position(document); self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot); - self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots()); + self.grab_target = self.local_pivot; } // Here vector data from all layers is not considered which can be a problem in pivot calculation else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { *selected.original_transforms = OriginalTransforms::default(); let viewspace = document.metadata().transform_to_viewport(selected_layers[0]); - let selected_segments = shape_editor.selected_segments().collect::>(); - let mut affected_points = shape_editor.selected_points().copied().collect::>(); for (segment_id, _, start, end) in vector_data.segment_bezier_iter() { @@ -201,8 +241,16 @@ impl MessageHandler> for TransformLayer let affected_point_refs = affected_points.iter().collect(); let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position)); - if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) { + if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot( + document, + &affected_point_refs, + &vector_data, + viewspace, + |point: &ManipulatorPointId| get_location(&point), + &mut self.pivot_gizmo, + ) { *selected.pivot = new_pivot; + self.path_bounds = bounds; self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot); self.grab_target = document_to_viewport.inverse().transform_point2(grab_target); @@ -228,116 +276,115 @@ impl MessageHandler> for TransformLayer return; } - for layer in document.metadata().all_layers() { - if !document.network_interface.is_artboard(&layer.to_node(), &[]) { - continue; - }; + let viewport_box = input.viewport_bounds.size(); + let axis_constraint = self.transform_operation.axis_constraint(); - let viewport_box = input.viewport_bounds.size(); - let axis_constraint = self.transform_operation.axis_constraint(); + let format_rounded = |value: f64, precision: usize| { + if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { + format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string() + } else { + self.typing.string.clone() + } + }; - let format_rounded = |value: f64, precision: usize| { - if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { - format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string() + // TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops + // responses.add(TransformLayerMessage::PointerMove { + // slow_key: SLOW_KEY, + // increments_key: INCREMENTS_KEY, + // }); + + match self.transform_operation { + TransformOperation::None => (), + TransformOperation::Grabbing(translation) => { + let translation = translation.to_dvec(self.initial_transform, self.increments); + let viewport_translate = document_to_viewport.transform_vector2(translation); + let pivot = document_to_viewport.transform_point2(self.grab_target); + let quad = Quad::from_box([pivot, pivot + viewport_translate]).0; + let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X); + + responses.add(SelectToolMessage::PivotShift { + offset: Some(viewport_translate), + flush: false, + }); + + if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { + let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; + overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); + + let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); + overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); + } + + if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. { + let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] }; + overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); + let x_parameter = viewport_translate.x.clamp(-1., 1.); + let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.); + let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End }; + if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { + overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]); + } + } + + if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. { + overlay_context.line(quad[1], quad[2], None, None); + overlay_context.line(quad[3], quad[2], None, None); + } + } + TransformOperation::Scaling(scale) => { + let scale = scale.to_f64(self.increments); + let text = format!("{}x", format_rounded(scale, 3)); + let pivot = document_to_viewport.transform_point2(self.local_pivot); + let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); + let local_edge = start_mouse - pivot; + let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint); + let boundary_point = pivot + local_edge * scale.min(1.); + let end_point = pivot + local_edge * scale.max(1.); + + if scale > 0. { + overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5)); + } + overlay_context.line(boundary_point, end_point, None, None); + + let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.); + overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); + } + TransformOperation::Rotating(rotation) => { + let angle = rotation.to_f64(self.increments); + let pivot = document_to_viewport.transform_point2(self.local_pivot); + let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); + let offset_angle = if self.grs_pen_handle { + self.handle - self.last_point + } else if using_path_tool { + start_mouse - pivot } else { - self.typing.string.clone() - } - }; - - // TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops - // responses.add(TransformLayerMessage::PointerMove { - // slow_key: SLOW_KEY, - // increments_key: INCREMENTS_KEY, - // }); - - match self.transform_operation { - TransformOperation::None => (), - TransformOperation::Grabbing(translation) => { - let translation = translation.to_dvec(self.initial_transform, self.increments); - let viewport_translate = document_to_viewport.transform_vector2(translation); - let pivot = document_to_viewport.transform_point2(self.grab_target); - let quad = Quad::from_box([pivot, pivot + viewport_translate]).0; - let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X); - - if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { - let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - - let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); - overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); - } - - if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. { - let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - let x_parameter = viewport_translate.x.clamp(-1., 1.); - let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.); - let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End }; - if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { - overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]); - } - } - - if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. { - overlay_context.line(quad[1], quad[2], None, None); - overlay_context.line(quad[3], quad[2], None, None); - } - } - TransformOperation::Scaling(scale) => { - let scale = scale.to_f64(self.increments); - let text = format!("{}x", format_rounded(scale, 3)); - let pivot = document_to_viewport.transform_point2(self.local_pivot); - let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); - let local_edge = start_mouse - pivot; - let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint); - let boundary_point = pivot + local_edge * scale.min(1.); - let end_point = pivot + local_edge * scale.max(1.); - - if scale > 0. { - overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5)); - } - overlay_context.line(boundary_point, end_point, None, None); - - let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.); - overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); - } - TransformOperation::Rotating(rotation) => { - let angle = rotation.to_f64(self.increments); - let pivot = document_to_viewport.transform_point2(self.local_pivot); - let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); - let offset_angle = if self.grs_pen_handle { - self.handle - self.last_point - } else if using_path_tool { - start_mouse - pivot - } else { - self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right() - }; - let tilt_offset = document.document_ptz.unmodified_tilt(); - let offset_angle = offset_angle.to_angle() + tilt_offset; - let width = viewport_box.max_element(); - let radius = start_mouse.distance(pivot); - let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width; - let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width); - let angle_in_degrees = angle.to_degrees(); - let display_angle = if angle_in_degrees.is_sign_positive() { - angle_in_degrees - (angle_in_degrees / 360.).floor() * 360. - } else if angle_in_degrees.is_sign_negative() { - angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360. - } else { - angle_in_degrees - }; - let text = format!("{}°", format_rounded(display_angle, 2)); - let text_texture_width = overlay_context.get_width(&text) / 2.; - let text_texture_height = 12.; - let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle); - let text_texture_position = DVec2::new( - (arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x, - (arc_radius + text_texture_height) * text_angle_on_unit_circle.y, - ); - let transform = DAffine2::from_translation(text_texture_position + pivot); - overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle); - overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); - } + self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right() + }; + let tilt_offset = document.document_ptz.unmodified_tilt(); + let offset_angle = offset_angle.to_angle() + tilt_offset; + let width = viewport_box.max_element(); + let radius = start_mouse.distance(pivot); + let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width; + let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width); + let angle_in_degrees = angle.to_degrees(); + let display_angle = if angle_in_degrees.is_sign_positive() { + angle_in_degrees - (angle_in_degrees / 360.).floor() * 360. + } else if angle_in_degrees.is_sign_negative() { + angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360. + } else { + angle_in_degrees + }; + let text = format!("{}°", format_rounded(display_angle, 2)); + let text_texture_width = overlay_context.get_width(&text) / 2.; + let text_texture_height = 12.; + let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle); + let text_texture_position = DVec2::new( + (arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x, + (arc_radius + text_texture_height) * text_angle_on_unit_circle.y, + ); + let transform = DAffine2::from_translation(text_texture_position + pivot); + overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle); + overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); } } } @@ -364,6 +411,8 @@ impl MessageHandler> for TransformLayer responses.add(NodeGraphMessage::RunDocumentGraph); } + responses.add(SelectToolMessage::PivotShift { offset: None, flush: true }); + if final_transform { responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); } @@ -487,6 +536,7 @@ impl MessageHandler> for TransformLayer responses.add(ToolMessage::UpdateHints); } + responses.add(SelectToolMessage::PivotShift { offset: None, flush: false }); responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); } TransformLayerMessage::ConstrainX => { @@ -694,6 +744,9 @@ impl MessageHandler> for TransformLayer self.initial_transform, ) } + TransformLayerMessage::SetPivotGizmo { pivot_gizmo } => { + self.pivot_gizmo = pivot_gizmo; + } } } diff --git a/frontend/src/components/widgets/inputs/ReferencePointInput.svelte b/frontend/src/components/widgets/inputs/ReferencePointInput.svelte index 14531e489b..fe1839d072 100644 --- a/frontend/src/components/widgets/inputs/ReferencePointInput.svelte +++ b/frontend/src/components/widgets/inputs/ReferencePointInput.svelte @@ -7,13 +7,14 @@ export let value: string; export let disabled = false; + export let tooltip: string | undefined = undefined; function setValue(newValue: ReferencePoint) { dispatch("value", newValue); } -
+
diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 53cd2a26e5..28b777f806 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -1340,6 +1340,9 @@ export class ReferencePointInput extends WidgetProps { value!: ReferencePoint; disabled!: boolean; + + @Transform(({ value }: { value: string }) => value || undefined) + tooltip!: string | undefined; } // WIDGET diff --git a/node-graph/gcore/src/transform_nodes.rs b/node-graph/gcore/src/transform_nodes.rs index 1c28a3087d..4cde6a7457 100644 --- a/node-graph/gcore/src/transform_nodes.rs +++ b/node-graph/gcore/src/transform_nodes.rs @@ -20,7 +20,6 @@ async fn transform( rotate: f64, scale: DVec2, skew: DVec2, - _pivot: DVec2, ) -> Instances { let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]); diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 118c2fbfb6..b3b47e48e7 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -352,8 +352,7 @@ async fn copy_to_points( let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation); for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) { - let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); - instance.transform = transform * local_matrix; + instance.transform = transform * instance.transform; result_table.push(instance); } From d2f8c9974422adf4f840f57e903084483ad5fdee Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 8 Jul 2025 18:13:05 -0700 Subject: [PATCH 06/11] Avoid panics during document migration --- .../utility_types/network_interface.rs | 2 +- .../messages/portfolio/document_migration.rs | 914 +++++++++--------- 2 files changed, 460 insertions(+), 456 deletions(-) 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 389557c047..404d3486a0 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -674,7 +674,7 @@ impl NodeNetworkInterface { let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0; // Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type. // For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData - node_io.inputs[iterator_index].clone().nested_type() == &input_type || node_io.inputs[iterator_index] == input_type + node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type) }); if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } }) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index f3b018a6cf..e4db26d2ec 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -7,6 +7,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp use crate::messages::prelude::DocumentMessageHandler; use bezier_rs::Subpath; use glam::IVec2; +use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graphene_std::text::TypesettingConfig; use graphene_std::uuid::NodeId; @@ -217,473 +218,476 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ .map(|(node_id, node, path)| (*node_id, node.clone(), path)) .collect::)>>(); for (node_id, node, network_path) in &nodes { - if reset_node_definitions_on_open { - if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) { - let Some(node_definition) = resolve_document_node_type(reference) else { continue }; - document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template()); - } + migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open); + } +} + +fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> { + if reset_node_definitions_on_open { + if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) { + let node_definition = resolve_document_node_type(reference)?; + document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template()); + } + } + + // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition + if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { + document + .network_interface + .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); + } + + // Only nodes that have not been modified and still refer to a definition can be updated + let reference = document.network_interface.reference(node_id, network_path).cloned().flatten()?; + let reference = &reference; + + let inputs_count = node.inputs.len(); + + // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) + if reference == "Stroke" && inputs_count == 8 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); + let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); + } + + // Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node + if reference == "Splines from Points" { + document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string())); + } + + // Upgrade the old "Spline" node to the new "Spline" node + if reference == "Spline" { + // Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node + let identifier = document + .network_interface + .implementation(node_id, network_path) + .and_then(|implementation| implementation.get_proto_node()); + if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) { + return None; } - // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition - if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { - document - .network_interface - .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); - } - - let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else { - // Only nodes that have not been modified and still refer to a definition can be updated - continue; + // Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points + let node = document.network_interface.document_node(node_id, network_path)?; + let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else { + log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2"); + return None; }; - let reference = &reference; + let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false)); - let inputs_count = node.inputs.len(); + // Retrieve the output connectors linked to the "Spline" node's output port + let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else { + log::error!("Vec of InputConnector Spline node is connected to its output port 0."); + return None; + }; - // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) - if reference == "Stroke" && inputs_count == 8 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); + // Get the node's current position in the graph + let Some(node_position) = document.network_interface.position(node_id, network_path) else { + log::error!("Could not get position of spline node."); + return None; + }; - let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); - let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); + // Get the "Path" node definition and fill it in with the vector data and default vector modification + let Some(path_node_type) = resolve_document_node_type("Path") else { + log::error!("Path node does not exist."); + return None; + }; + let path_node = path_node_type.node_template_input_override([ + Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)), + Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)), + ]); - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); + // Get the "Spline" node definition and wire it up with the "Path" node as input + let Some(spline_node_type) = resolve_document_node_type("Spline") else { + log::error!("Spline node does not exist."); + return None; + }; + let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]); + + // Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them + let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)]; + let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::>(); + let new_spline_id = *new_ids.get(&NodeId(0))?; + let new_path_id = *new_ids.get(&NodeId(1))?; + + // Remove the old "Spline" node from the document + document.network_interface.delete_nodes(vec![*node_id], false, network_path); + + // Insert the new "Path" and "Spline" nodes into the network interface with generated IDs + document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path); + + // Reposition the new "Spline" node to match the original "Spline" node's position + document.network_interface.shift_node(&new_spline_id, node_position, network_path); + + // Reposition the new "Path" node with an offset relative to the original "Spline" node's position + document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path); + + // Redirect each output connection from the old node to the new "Spline" node's output port + for input_connector in spline_outputs { + document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path); } + } - // Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node - if reference == "Splines from Points" { - document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string())); + // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 + if reference == "Text" && inputs_count != 9 { + let mut template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut template); + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 4), + if inputs_count == 6 { + old_inputs[4].clone() + } else { + NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 5), + if inputs_count == 6 { + old_inputs[5].clone() + } else { + NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 6), + if inputs_count >= 7 { + old_inputs[6].clone() + } else { + NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 7), + if inputs_count >= 8 { + old_inputs[7].clone() + } else { + NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 8), + if inputs_count >= 9 { + old_inputs[8].clone() + } else { + NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false) + }, + network_path, + ); + } + + // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default + if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); + } + + // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option + if reference == "Modulo" && inputs_count == 2 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); + } + + // Upgrade the Mirror node to add the `keep_original` boolean input + if reference == "Mirror" && inputs_count == 3 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path); + } + + // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` + if reference == "Mirror" && inputs_count == 4 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None }; + let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 1), + NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false), + network_path, + ); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path); + } + + // Upgrade artboard name being passed as hidden value input to "To Artboard" + if reference == "Artboard" && reset_node_definitions_on_open { + let label = document.network_interface.display_name(node_id, network_path); + document + .network_interface + .set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]); + } + + if reference == "Image" && inputs_count == 1 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + // Insert a new empty input for the image + document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]); + document.network_interface.set_reference(node_id, network_path, Some("Image".to_string())); + } + + if reference == "Noise Pattern" && inputs_count == 15 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document + .network_interface + .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); + for (i, input) in old_inputs.iter().enumerate() { + document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path); } + } - // Upgrade the old "Spline" node to the new "Spline" node - if reference == "Spline" { - // Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node - let identifier = document - .network_interface - .implementation(node_id, network_path) - .and_then(|implementation| implementation.get_proto_node()); - if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) { - continue; - } - - // Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points - let node = document.network_interface.document_node(node_id, network_path).unwrap(); - let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else { - log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2"); - continue; - }; - let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false)); - - // Retrieve the output connectors linked to the "Spline" node's output port - let spline_outputs = document - .network_interface - .outward_wires(network_path) - .unwrap() - .get(&OutputConnector::node(*node_id, 0)) - .expect("Vec of InputConnector Spline node is connected to its output port 0.") - .clone(); - - // Get the node's current position in the graph - let Some(node_position) = document.network_interface.position(node_id, network_path) else { - log::error!("Could not get position of spline node."); - continue; - }; - - // Get the "Path" node definition and fill it in with the vector data and default vector modification - let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist."); - let path_node = path_node_type.node_template_input_override([ - Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)), - Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)), - ]); - - // Get the "Spline" node definition and wire it up with the "Path" node as input - let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist."); - let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]); - - // Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them - let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)]; - let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::>(); - let new_spline_id = *new_ids.get(&NodeId(0)).unwrap(); - let new_path_id = *new_ids.get(&NodeId(1)).unwrap(); - - // Remove the old "Spline" node from the document - document.network_interface.delete_nodes(vec![*node_id], false, network_path); - - // Insert the new "Path" and "Spline" nodes into the network interface with generated IDs - document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path); - - // Reposition the new "Spline" node to match the original "Spline" node's position - document.network_interface.shift_node(&new_spline_id, node_position, network_path); - - // Reposition the new "Path" node with an offset relative to the original "Spline" node's position - document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path); - - // Redirect each output connection from the old node to the new "Spline" node's output port - for input_connector in spline_outputs { - document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path); - } - } - - // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 - if reference == "Text" && inputs_count != 9 { - let mut template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut template); - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 4), - if inputs_count == 6 { - old_inputs[4].clone() - } else { - NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false) - }, - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 5), - if inputs_count == 6 { - old_inputs[5].clone() - } else { - NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false) - }, - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 6), - if inputs_count >= 7 { - old_inputs[6].clone() - } else { - NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false) - }, - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 7), - if inputs_count >= 8 { - old_inputs[7].clone() - } else { - NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false) - }, - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 8), - if inputs_count >= 9 { - old_inputs[8].clone() - } else { - NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false) - }, - network_path, - ); - } - - // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default - if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); - } - - // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option - if reference == "Modulo" && inputs_count == 2 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); - } - - // Upgrade the Mirror node to add the `keep_original` boolean input - if reference == "Mirror" && inputs_count == 3 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path); - } - - // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` - if reference == "Mirror" && inputs_count == 4 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return }; - let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 1), - NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false), - network_path, - ); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path); - } - - // Upgrade artboard name being passed as hidden value input to "To Artboard" - if reference == "Artboard" && reset_node_definitions_on_open { - let label = document.network_interface.display_name(node_id, network_path); - document - .network_interface - .set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]); - } - - if reference == "Image" && inputs_count == 1 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - // Insert a new empty input for the image - document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]); - document.network_interface.set_reference(node_id, network_path, Some("Image".to_string())); - } - - if reference == "Noise Pattern" && inputs_count == 15 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document - .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); - for (i, input) in old_inputs.iter().enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path); - } - } - - if reference == "Instance on Points" && inputs_count == 2 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - } - - if reference == "Morph" && inputs_count == 4 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - // We have removed the last input, so we don't add index 3 - } - - if reference == "Brush" && inputs_count == 4 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path); - } - - if reference == "Flatten Vector Elements" { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string()); - } - - if reference == "Remove Handles" { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); - } - - if reference == "Generate Handles" { - let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); - } - - if reference == "Merge by Distance" && inputs_count == 2 { - let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 2), - NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false), - network_path, - ); - } - - if reference == "Spatial Merge by Distance" { - let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 2), - NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false), - network_path, - ); - - document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string()); - } - - if reference == "Sample Points" && inputs_count == 5 { - let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); - let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); - let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string()); - } - - // Make the "Quantity" parameter a u32 instead of f64 - if reference == "Sample Polyline" { - // Get the inputs, obtain the quantity value, and put the inputs back - let quantity_value = document - .network_interface - .input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path) - .unwrap(); - - if let NodeInput::Value { tagged_value, exposed } = quantity_value { - if let TaggedValue::F64(value) = **tagged_value { - let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); - } - } - } - - // Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows") - if reference == "Grid" && inputs_count == 6 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let mut new_node_template = node_definition.default_node_template(); - - let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap(); - let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap(); - let index_3_value = old_inputs.get(3).cloned(); - - let mut upgraded = false; - - if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { - if matches!(*tagged_value, TaggedValue::DVec2(_)) { - // Move index 3 to the end - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - - upgraded = true; - } - } - - if !upgraded { - let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); - } - } - - // Ensure layers are positioned as stacks if they are upstream siblings of another layer - document.network_interface.load_structure(); - let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); - for layer in all_layers { - let Some((downstream_node, input_index)) = document - .network_interface - .outward_wires(&[]) - .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0))) - .and_then(|outward_wires| outward_wires.first()) - .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index()))) - else { - continue; - }; - // If the downstream node is a layer and the input is the first input and the current layer is not in a stack - if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) { - // Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files - let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else { - log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node); - continue; - }; - if layer_position.x == downstream_position.x { - document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]); - } + if reference == "Instance on Points" && inputs_count == 2 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + } + + if reference == "Morph" && inputs_count == 4 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + // We have removed the last input, so we don't add index 3 + } + + if reference == "Brush" && inputs_count == 4 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path); + } + + if reference == "Flatten Vector Elements" { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string()); + } + + if reference == "Remove Handles" { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); + } + + if reference == "Generate Handles" { + let mut node_template = resolve_document_node_type("Auto-Tangents")?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); + } + + if reference == "Merge by Distance" && inputs_count == 2 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 2), + NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false), + network_path, + ); + } + + if reference == "Spatial Merge by Distance" { + let mut node_template = resolve_document_node_type("Merge by Distance")?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 2), + NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false), + network_path, + ); + + document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string()); + } + + if reference == "Sample Points" && inputs_count == 5 { + let mut node_template = resolve_document_node_type("Sample Polyline")?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); + let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string()); + } + + // Make the "Quantity" parameter a u32 instead of f64 + if reference == "Sample Polyline" { + // Get the inputs, obtain the quantity value, and put the inputs back + let quantity_value = document + .network_interface + .input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)?; + + if let NodeInput::Value { tagged_value, exposed } = quantity_value { + if let TaggedValue::F64(value) = **tagged_value { + let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); } } } + + // Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows") + if reference == "Grid" && inputs_count == 6 { + let node_definition = resolve_document_node_type(reference)?; + let mut new_node_template = node_definition.default_node_template(); + + let mut current_node_template = document.network_interface.create_node_template(node_id, network_path)?; + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template)?; + let index_3_value = old_inputs.get(3).cloned(); + + let mut upgraded = false; + + if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { + if matches!(*tagged_value, TaggedValue::DVec2(_)) { + // Move index 3 to the end + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); + + upgraded = true; + } + } + + if !upgraded { + let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); + } + } + + // Ensure layers are positioned as stacks if they are upstream siblings of another layer + document.network_interface.load_structure(); + let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); + for layer in all_layers { + let (downstream_node, input_index) = document + .network_interface + .outward_wires(&[]) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0))) + .and_then(|outward_wires| outward_wires.first()) + .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))?; + // If the downstream node is a layer and the input is the first input and the current layer is not in a stack + if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) { + // Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files + let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else { + log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node); + return None; + }; + if layer_position.x == downstream_position.x { + document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]); + } + } + } + + Some(()) } From d0e1d8982fee975d6b07471ba3b737c3f6c43b4d Mon Sep 17 00:00:00 2001 From: HWienhold Date: Wed, 9 Jul 2025 03:47:56 +0200 Subject: [PATCH 07/11] Allow tooling to treat near-zero-length handles as zero-length when converting to colinear (#2747) fix colinear handles on connected path Co-authored-by: Keavon Chambers --- editor/src/messages/tool/common_functionality/shape_editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 581c89908f..f98e50f079 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -1079,7 +1079,7 @@ impl ShapeState { let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize())); - if normalized.is_none() { + if normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) { handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.)); normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize())) } From 00236c8136bc318d0b639d79a9a64cc903d1c123 Mon Sep 17 00:00:00 2001 From: Mohd Mohsin <136162998+MohdMohsin97@users.noreply.github.com> Date: Wed, 9 Jul 2025 07:43:51 +0530 Subject: [PATCH 08/11] Generate a visualization of the editor's hierarchical message system tree (#2499) * Feat: implement the hierarchical tree for visualization * rename HierarchicalTree trait function * feat: change the HierarchicalTree from String to DebugMessageTree struct * Nits * feat: impliment proc macro to extract field from messagedata structs * update the hierarchical-tree for hanlder data * feat: added message handler struct to hierarchical tree * feat: add the line number to message handler struct * feat: added handler path to tree and NITS * clean the white spaces in type string * fixes some white spaces * feat: added path to message enum in hierarchical tree * feat: add file creation of hierarchical message system tree * cleanup * Code review * Add todo comment for deferred change --------- Co-authored-by: Keavon Chambers --- .gitignore | 1 + editor/src/lib.rs | 1 + .../animation/animation_message_handler.rs | 3 +- .../broadcast/broadcast_message_handler.rs | 3 +- .../messages/debug/debug_message_handler.rs | 3 +- .../messages/dialog/dialog_message_handler.rs | 4 +- .../export_dialog_message_handler.rs | 4 +- .../new_document_dialog_message_handler.rs | 3 +- .../preferences_dialog_message_handler.rs | 4 +- .../globals/globals_message_handler.rs | 3 +- .../input_mapper_message_handler.rs | 4 +- .../key_mapping_message_handler.rs | 4 +- .../input_preprocessor_message_handler.rs | 4 +- .../messages/layout/layout_message_handler.rs | 11 +- editor/src/messages/message.rs | 83 ++++++++++++ .../document/document_message_handler.rs | 4 +- .../graph_operation_message_handler.rs | 4 +- .../navigation/navigation_message_handler.rs | 4 +- .../node_graph/node_graph_message_handler.rs | 5 +- .../overlays/overlays_message_handler.rs | 4 +- .../properties_panel_message_handler.rs | 3 +- .../menu_bar/menu_bar_message_handler.rs | 3 +- .../portfolio/portfolio_message_handler.rs | 4 +- .../spreadsheet_message_handler.rs | 3 +- .../preferences_message_handler.rs | 3 +- editor/src/messages/prelude.rs | 4 +- .../src/messages/tool/tool_message_handler.rs | 4 +- .../tool/tool_messages/artboard_tool.rs | 3 +- .../messages/tool/tool_messages/brush_tool.rs | 3 +- .../tool/tool_messages/eyedropper_tool.rs | 3 +- .../messages/tool/tool_messages/fill_tool.rs | 3 +- .../tool/tool_messages/freehand_tool.rs | 3 +- .../tool/tool_messages/gradient_tool.rs | 3 +- .../tool/tool_messages/navigate_tool.rs | 3 +- .../messages/tool/tool_messages/path_tool.rs | 3 +- .../messages/tool/tool_messages/pen_tool.rs | 3 +- .../tool/tool_messages/select_tool.rs | 3 +- .../tool/tool_messages/spline_tool.rs | 3 +- .../messages/tool/tool_messages/text_tool.rs | 3 +- .../transform_layer_message_handler.rs | 22 +++- editor/src/messages/tool/utility_types.rs | 1 + .../workspace/workspace_message_handler.rs | 3 +- editor/src/utility_traits.rs | 16 +++ editor/src/utility_types.rs | 99 +++++++++++++++ proc-macros/src/combined_message_attrs.rs | 4 +- proc-macros/src/extract_fields.rs | 57 +++++++++ proc-macros/src/helpers.rs | 52 ++++++++ proc-macros/src/hierarchical_tree.rs | 73 +++++++++++ proc-macros/src/lib.rs | 21 ++++ proc-macros/src/message_handler_data_attr.rs | 118 ++++++++++++++++++ 50 files changed, 640 insertions(+), 42 deletions(-) create mode 100644 editor/src/utility_types.rs create mode 100644 proc-macros/src/extract_fields.rs create mode 100644 proc-macros/src/hierarchical_tree.rs create mode 100644 proc-macros/src/message_handler_data_attr.rs diff --git a/.gitignore b/.gitignore index 5700f4e89b..bdfa416126 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ profile.json flamegraph.svg .idea/ .direnv +hierarchical_message_system_tree.txt diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 80d80b5305..2c59945136 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -15,3 +15,4 @@ pub mod node_graph_executor; #[cfg(test)] pub mod test_utils; pub mod utility_traits; +pub mod utility_types; diff --git a/editor/src/messages/animation/animation_message_handler.rs b/editor/src/messages/animation/animation_message_handler.rs index eb7ceba2e0..211d22c87c 100644 --- a/editor/src/messages/animation/animation_message_handler.rs +++ b/editor/src/messages/animation/animation_message_handler.rs @@ -24,7 +24,7 @@ enum AnimationState { }, } -#[derive(Default, Debug, Clone, PartialEq)] +#[derive(Default, Debug, Clone, PartialEq, ExtractField)] pub struct AnimationMessageHandler { /// Used to re-send the UI on the next frame after playback starts live_preview_recently_zero: bool, @@ -57,6 +57,7 @@ impl AnimationMessageHandler { } } +#[message_handler_data] impl MessageHandler for AnimationMessageHandler { fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/broadcast/broadcast_message_handler.rs b/editor/src/messages/broadcast/broadcast_message_handler.rs index 51d64b5852..489df47ab7 100644 --- a/editor/src/messages/broadcast/broadcast_message_handler.rs +++ b/editor/src/messages/broadcast/broadcast_message_handler.rs @@ -1,10 +1,11 @@ use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct BroadcastMessageHandler { listeners: HashMap>, } +#[message_handler_data] impl MessageHandler for BroadcastMessageHandler { fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/debug/debug_message_handler.rs b/editor/src/messages/debug/debug_message_handler.rs index 6044ce9108..064ad2510c 100644 --- a/editor/src/messages/debug/debug_message_handler.rs +++ b/editor/src/messages/debug/debug_message_handler.rs @@ -1,11 +1,12 @@ use super::utility_types::MessageLoggingVerbosity; use crate::messages::prelude::*; -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct DebugMessageHandler { pub message_logging_verbosity: MessageLoggingVerbosity, } +#[message_handler_data] impl MessageHandler for DebugMessageHandler { fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/dialog/dialog_message_handler.rs b/editor/src/messages/dialog/dialog_message_handler.rs index 3e9c80e46a..8e6bbde2f8 100644 --- a/editor/src/messages/dialog/dialog_message_handler.rs +++ b/editor/src/messages/dialog/dialog_message_handler.rs @@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct DialogMessageData<'a> { pub portfolio: &'a PortfolioMessageHandler, pub preferences: &'a PreferencesMessageHandler, } /// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, ExtractField)] pub struct DialogMessageHandler { export_dialog: ExportDialogMessageHandler, new_document_dialog: NewDocumentDialogMessageHandler, preferences_dialog: PreferencesDialogMessageHandler, } +#[message_handler_data] impl MessageHandler> for DialogMessageHandler { fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque, data: DialogMessageData) { let DialogMessageData { portfolio, preferences } = data; diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 1bcf4b7003..179aab4f96 100644 --- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -3,12 +3,13 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct ExportDialogMessageData<'a> { pub portfolio: &'a PortfolioMessageHandler, } /// A dialog to allow users to customize their file export. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ExtractField)] pub struct ExportDialogMessageHandler { pub file_type: FileType, pub scale_factor: f64, @@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler { } } +#[message_handler_data] impl MessageHandler> for ExportDialogMessageHandler { fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque, data: ExportDialogMessageData) { let ExportDialogMessageData { portfolio } = data; diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index 2e94f876c7..539180117c 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -4,13 +4,14 @@ use glam::{IVec2, UVec2}; use graph_craft::document::NodeId; /// A dialog to allow users to set some initial options about a new document. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct NewDocumentDialogMessageHandler { pub name: String, pub infinite: bool, pub dimensions: UVec2, } +#[message_handler_data] impl MessageHandler for NewDocumentDialogMessageHandler { fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index 6e92d60bcf..882fc6db5c 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -4,14 +4,16 @@ use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct PreferencesDialogMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } /// A dialog to allow users to customize Graphite editor options -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct PreferencesDialogMessageHandler {} +#[message_handler_data] impl MessageHandler> for PreferencesDialogMessageHandler { fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque, data: PreferencesDialogMessageData) { let PreferencesDialogMessageData { preferences } = data; diff --git a/editor/src/messages/globals/globals_message_handler.rs b/editor/src/messages/globals/globals_message_handler.rs index 5651555f60..9a72ffa6dc 100644 --- a/editor/src/messages/globals/globals_message_handler.rs +++ b/editor/src/messages/globals/globals_message_handler.rs @@ -1,8 +1,9 @@ use crate::messages::prelude::*; -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct GlobalsMessageHandler {} +#[message_handler_data] impl MessageHandler for GlobalsMessageHandler { fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/input_mapper/input_mapper_message_handler.rs b/editor/src/messages/input_mapper/input_mapper_message_handler.rs index eba3867213..b26a1b4c40 100644 --- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; use std::fmt::Write; +#[derive(ExtractField)] pub struct InputMapperMessageData<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct InputMapperMessageHandler { mapping: Mapping, } +#[message_handler_data] impl MessageHandler> for InputMapperMessageHandler { fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque, data: InputMapperMessageData) { let InputMapperMessageData { input, actions } = data; diff --git a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs index 53c626bf54..81f249ec5e 100644 --- a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs +++ b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs @@ -2,16 +2,18 @@ use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMess use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct KeyMappingMessageData<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct KeyMappingMessageHandler { mapping_handler: InputMapperMessageHandler, } +#[message_handler_data] impl MessageHandler> for KeyMappingMessageHandler { fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque, data: KeyMappingMessageData) { let KeyMappingMessageData { input, actions } = data; diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index bf90e5881f..144652fdc5 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -6,11 +6,12 @@ use crate::messages::prelude::*; use glam::DVec2; use std::time::Duration; +#[derive(ExtractField)] pub struct InputPreprocessorMessageData { pub keyboard_platform: KeyboardPlatformLayout, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct InputPreprocessorMessageHandler { pub frame_time: FrameTimeInfo, pub time: u64, @@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler { pub viewport_bounds: ViewportBounds, } +#[message_handler_data] impl MessageHandler for InputPreprocessorMessageHandler { fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, data: InputPreprocessorMessageData) { let InputPreprocessorMessageData { keyboard_platform } = data; diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index ee8e506ca8..7beec22fe9 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -6,7 +6,7 @@ use graphene_std::text::Font; use graphene_std::vector::style::{FillChoice, GradientStops}; use serde_json::Value; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct LayoutMessageHandler { layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], } @@ -342,6 +342,15 @@ impl LayoutMessageHandler { } } +pub fn custom_data() -> MessageData { + // TODO: When is resolved and released, + // TODO: use to get + // TODO: the line number instead of hardcoding it to the magic number on the following line. + // TODO: Also, utilize the line number in the actual output, since it is currently unused. + MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option"), 350)], file!()) +} + +#[message_handler_data(CustomData)] impl Option> MessageHandler for LayoutMessageHandler { fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque, action_input_mapping: F) { match message { diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index 80323d9309..fbe4170023 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant { specta::DataType::Any } } + +#[cfg(test)] +mod test { + use super::*; + use std::io::Write; + + #[test] + fn generate_message_tree() { + let result = Message::build_message_tree(); + let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap(); + file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap(); + if let Some(variants) = result.variants() { + for (i, variant) in variants.iter().enumerate() { + let is_last = i == variants.len() - 1; + print_tree_node(variant, "", is_last, &mut file); + } + } + } + + fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) { + // Print the current node + let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() { + ("├── ", format!("{}│ ", prefix)) + } else { + if is_last { + ("└── ", format!("{} ", prefix)) + } else { + ("├── ", format!("{}│ ", prefix)) + } + }; + + if tree.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap(); + } + + // Print children if any + if let Some(variants) = tree.variants() { + let len = variants.len(); + for (i, variant) in variants.iter().enumerate() { + let is_last_child = i == len - 1; + print_tree_node(variant, &child_prefix, is_last_child, file); + } + } + + // Print handler field if any + if let Some(data) = tree.message_handler_fields() { + let len = data.fields().len(); + let (branch, child_prefix) = if tree.has_message_handler_data_fields() { + ("├── ", format!("{}│ ", prefix)) + } else { + ("└── ", format!("{} ", prefix)) + }; + if data.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap(); + } + for (i, field) in data.fields().iter().enumerate() { + let is_last_field = i == len - 1; + let branch = if is_last_field { "└── " } else { "├── " }; + + file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap(); + } + } + + // Print data field if any + if let Some(data) = tree.message_handler_data_fields() { + let len = data.fields().len(); + if data.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap(); + } + for (i, field) in data.fields().iter().enumerate() { + let is_last_field = i == len - 1; + let branch = if is_last_field { "└── " } else { "├── " }; + file.write_all(format!("{}{}{}\n", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap(); + } + } + } +} diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 89e0aaac92..784c3ba3ef 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::style::ViewMode; use std::time::Duration; +#[derive(ExtractField)] pub struct DocumentMessageData<'a> { pub document_id: DocumentId, pub ipp: &'a InputPreprocessorMessageHandler, @@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> { pub device_pixel_ratio: f64, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)] #[serde(default)] pub struct DocumentMessageHandler { // ====================== @@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler { } } +#[message_handler_data] impl MessageHandler> for DocumentMessageHandler { fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque, data: DocumentMessageData) { let DocumentMessageData { diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 40653e4af4..931a15e3bf 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -21,17 +21,19 @@ struct ArtboardInfo { merge_node: NodeId, } +#[derive(ExtractField)] pub struct GraphOperationMessageData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub collapsed: &'a mut CollapsedLayers, pub node_graph: &'a mut NodeGraphMessageHandler, } -#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)] pub struct GraphOperationMessageHandler {} // GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network. // For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network. +#[message_handler_data] impl MessageHandler> for GraphOperationMessageHandler { fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque, data: GraphOperationMessageData) { let network_interface = data.network_interface; diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 929c850160..0d732edd04 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; +#[derive(ExtractField)] pub struct NavigationMessageData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub breadcrumb_network_path: &'a [NodeId], @@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Debug, Clone, PartialEq, Default, ExtractField)] pub struct NavigationMessageHandler { navigation_operation: NavigationOperation, mouse_position: ViewportPosition, @@ -31,6 +32,7 @@ pub struct NavigationMessageHandler { abortable_pan_start: Option, } +#[message_handler_data] impl MessageHandler> for NavigationMessageHandler { fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque, data: NavigationMessageData) { let NavigationMessageData { 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 b3f1596cd2..828fb4a8ae 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 @@ -27,7 +27,7 @@ use graphene_std::*; use renderer::Quad; use std::cmp::Ordering; -#[derive(Debug)] +#[derive(Debug, ExtractField)] pub struct NodeGraphHandlerData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], @@ -41,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ExtractField)] pub struct NodeGraphMessageHandler { // TODO: Remove network and move to NodeNetworkInterface pub network: Vec, @@ -92,6 +92,7 @@ pub struct NodeGraphMessageHandler { } /// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network. +#[message_handler_data] impl<'a> MessageHandler> for NodeGraphMessageHandler { fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, data: NodeGraphHandlerData<'a>) { let NodeGraphHandlerData { diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index 0db542da75..d4bb518e68 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -1,13 +1,14 @@ use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings}; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct OverlaysMessageData<'a> { pub visibility_settings: OverlaysVisibilitySettings, pub ipp: &'a InputPreprocessorMessageHandler, pub device_pixel_ratio: f64, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct OverlaysMessageHandler { pub overlay_providers: HashSet, #[cfg(target_arch = "wasm32")] @@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler { context: Option, } +#[message_handler_data] impl MessageHandler> for OverlaysMessageHandler { fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque, data: OverlaysMessageData) { let OverlaysMessageData { visibility_settings, ipp, .. } = data; diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index a783b24aae..3ca9f350f3 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct PropertiesPanelMessageHandler {} +#[message_handler_data] impl MessageHandler)> for PropertiesPanelMessageHandler { fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) { let PropertiesPanelMessageHandlerData { diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index 50cfa632ac..26a9a739f3 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, use crate::messages::prelude::*; use graphene_std::path_bool::BooleanOperation; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct MenuBarMessageHandler { pub has_active_document: bool, pub canvas_tilted: bool, @@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler { pub reset_node_definitions_on_open: bool, } +#[message_handler_data] impl MessageHandler for MenuBarMessageHandler { fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index a6d9da338e..195768bd4b 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -25,6 +25,7 @@ use graphene_std::renderer::Quad; use graphene_std::text::Font; use std::vec; +#[derive(ExtractField)] pub struct PortfolioMessageData<'a> { pub ipp: &'a InputPreprocessorMessageHandler, pub preferences: &'a PreferencesMessageHandler, @@ -35,7 +36,7 @@ pub struct PortfolioMessageData<'a> { pub animation: &'a AnimationMessageHandler, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct PortfolioMessageHandler { menu_bar_message_handler: MenuBarMessageHandler, pub documents: HashMap, @@ -52,6 +53,7 @@ pub struct PortfolioMessageHandler { pub reset_node_definitions_on_open: bool, } +#[message_handler_data] impl MessageHandler> for PortfolioMessageHandler { fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, data: PortfolioMessageData) { let PortfolioMessageData { diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 378c8b1b52..72c44c5975 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -15,7 +15,7 @@ use std::any::Any; use std::sync::Arc; /// The spreadsheet UI allows for instance data to be previewed. -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, ExtractField)] pub struct SpreadsheetMessageHandler { /// Sets whether or not the spreadsheet is drawn. pub spreadsheet_view_open: bool, @@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler { viewing_vector_data_domain: VectorDataDomain, } +#[message_handler_data] impl MessageHandler for SpreadsheetMessageHandler { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index b52e4e49a8..1e52233fe3 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; use graph_craft::wasm_application_io::EditorPreferences; -#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] pub struct PreferencesMessageHandler { pub selection_mode: SelectionMode, pub zoom_with_scroll: bool, @@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler { } } +#[message_handler_data] impl MessageHandler for PreferencesMessageHandler { fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index d33b7c235c..7517c80f48 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -1,6 +1,6 @@ // Root -pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild}; - +pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild}; +pub use crate::utility_types::{DebugMessageTree, MessageData}; // Message, MessageData, MessageDiscriminant, MessageHandler pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler}; pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index e20d0989b1..6000301a18 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -12,6 +12,7 @@ use graphene_std::raster::color::Color; const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into(); +#[derive(ExtractField)] pub struct ToolMessageData<'a> { pub document_id: DocumentId, pub document: &'a mut DocumentMessageHandler, @@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct ToolMessageHandler { pub tool_state: ToolFsmState, pub transform_layer_handler: TransformLayerMessageHandler, @@ -29,6 +30,7 @@ pub struct ToolMessageHandler { pub tool_is_active: bool, } +#[message_handler_data] impl MessageHandler> for ToolMessageHandler { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, data: ToolMessageData) { let ToolMessageData { diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 0362b7f148..e14058c32c 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*; use graph_craft::document::NodeId; use graphene_std::renderer::Quad; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct ArtboardTool { fsm_state: ArtboardToolFsmState, data: ArtboardToolData, @@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool { } } +#[message_handler_data] impl<'a> MessageHandler> for ArtboardTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false); diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index b468603989..9bba3711cf 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -20,7 +20,7 @@ pub enum DrawMode { Restore, } -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct BrushTool { fsm_state: BrushToolFsmState, data: BrushToolData, @@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool { } } +#[message_handler_data] impl<'a> MessageHandler> for BrushTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index 3b3f324972..ce4b09d8e9 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -1,7 +1,7 @@ use super::tool_prelude::*; use crate::messages::tool::utility_types::DocumentToolData; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct EyedropperTool { fsm_state: EyedropperToolFsmState, data: EyedropperToolData, @@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool { } } +#[message_handler_data] impl<'a> MessageHandler> for EyedropperTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 5aeb063d87..94ad2b8c21 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use graphene_std::vector::style::Fill; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct FillTool { fsm_state: FillToolFsmState, } @@ -41,6 +41,7 @@ impl LayoutHolder for FillTool { } } +#[message_handler_data] impl<'a> MessageHandler> for FillTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index e2abddef2a..fb1539a818 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -13,7 +13,7 @@ use graphene_std::Color; use graphene_std::vector::VectorModificationType; use graphene_std::vector::{PointId, SegmentId}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct FreehandTool { fsm_state: FreehandToolFsmState, data: FreehandToolData, @@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool { } } +#[message_handler_data] impl<'a> MessageHandler> for FreehandTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 030da8ec1d..0e84f9585f 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node use crate::messages::tool::common_functionality::snapping::SnapManager; use graphene_std::vector::style::{Fill, Gradient, GradientType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct GradientTool { fsm_state: GradientToolFsmState, data: GradientToolData, @@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool { } } +#[message_handler_data] impl<'a> MessageHandler> for GradientTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 0a76a331dc..9f9ef0307b 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -1,6 +1,6 @@ use super::tool_prelude::*; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct NavigateTool { fsm_state: NavigateToolFsmState, tool_data: NavigateToolData, @@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool { } } +#[message_handler_data] impl<'a> MessageHandler> for NavigateTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 0642613cf0..d54ce54274 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -24,7 +24,7 @@ use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, Vector use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType}; use std::vec; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct PathTool { fsm_state: PathToolFsmState, tool_data: PathToolData, @@ -305,6 +305,7 @@ impl LayoutHolder for PathTool { } } +#[message_handler_data] impl<'a> MessageHandler> for PathTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 14f95a48f7..53a9e4d17c 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -17,7 +17,7 @@ use graphene_std::Color; use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData}; use graphene_std::vector::{PointId, VectorModificationType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct PenTool { fsm_state: PenToolFsmState, tool_data: PenToolData, @@ -186,6 +186,7 @@ impl LayoutHolder for PenTool { } } +#[message_handler_data] impl<'a> MessageHandler> for PenTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 9ae3459a7d..6c8498317f 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -29,7 +29,7 @@ use graphene_std::renderer::Rect; use graphene_std::transform::ReferencePoint; use std::fmt; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct SelectTool { fsm_state: SelectToolFsmState, tool_data: SelectToolData, @@ -272,6 +272,7 @@ impl LayoutHolder for SelectTool { } } +#[message_handler_data] impl<'a> MessageHandler> for SelectTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let mut redraw_reference_pivot = false; diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 2ac541d702..2de6747f01 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput}; use graphene_std::Color; use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct SplineTool { fsm_state: SplineToolFsmState, tool_data: SplineToolData, @@ -123,6 +123,7 @@ impl LayoutHolder for SplineTool { } } +#[message_handler_data] impl<'a> MessageHandler> for SplineTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index 6be22719cb..52430f09ca 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -20,7 +20,7 @@ use graphene_std::renderer::Quad; use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font}; use graphene_std::vector::style::Fill; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct TextTool { fsm_state: TextToolFsmState, tool_data: TextToolData, @@ -170,6 +170,7 @@ impl LayoutHolder for TextTool { } } +#[message_handler_data] impl<'a> MessageHandler> for TextTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 7258163772..a6221f729d 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -21,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer const SLOW_KEY: Key = Key::Shift; const INCREMENTS_KEY: Key = Key::Control; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct TransformLayerMessageHandler { pub transform_operation: TransformOperation, @@ -175,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D } type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState); + +pub fn custom_data() -> MessageData { + MessageData::new( + String::from("TransformData<'a>"), + // TODO: When is resolved and released, + // TODO: use to get + // TODO: the line number instead of hardcoding it to the magic number on the following lines + // TODO: which points to the line of the `type TransformData<'a> = ...` definition above. + // TODO: Also, utilize the line number in the actual output, since it is currently unused. + vec![ + (String::from("&'a DocumentMessageHandler"), 177), + (String::from("&'a InputPreprocessorMessageHandler"), 177), + (String::from("&'a ToolData"), 177), + (String::from("&'a mut ShapeState"), 177), + ], + file!(), + ) +} + +#[message_handler_data(CustomData)] impl MessageHandler> for TransformLayerMessageHandler { fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, input, tool_data, shape_editor): TransformData) { let using_path_tool = tool_data.active_tool_type == ToolType::Path; diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index fbbec768e4..fc06385f43 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -18,6 +18,7 @@ use graphene_std::text::FontCache; use std::borrow::Cow; use std::fmt::{self, Debug}; +#[derive(ExtractField)] pub struct ToolActionHandlerData<'a> { pub document: &'a mut DocumentMessageHandler, pub document_id: DocumentId, diff --git a/editor/src/messages/workspace/workspace_message_handler.rs b/editor/src/messages/workspace/workspace_message_handler.rs index 397e7cf00b..47c81aab0e 100644 --- a/editor/src/messages/workspace/workspace_message_handler.rs +++ b/editor/src/messages/workspace/workspace_message_handler.rs @@ -1,10 +1,11 @@ use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct WorkspaceMessageHandler { node_graph_visible: bool, } +#[message_handler_data] impl MessageHandler for WorkspaceMessageHandler { fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/utility_traits.rs b/editor/src/utility_traits.rs index 711a8cc34b..850d730b13 100644 --- a/editor/src/utility_traits.rs +++ b/editor/src/utility_traits.rs @@ -45,3 +45,19 @@ pub trait TransitiveChild: Into + Into { pub trait Hint { fn hints(&self) -> HashMap; } + +pub trait HierarchicalTree { + fn build_message_tree() -> DebugMessageTree; + + fn message_handler_data_str() -> MessageData { + MessageData::new(String::new(), Vec::new(), "") + } + + fn message_handler_str() -> MessageData { + MessageData::new(String::new(), Vec::new(), "") + } + + fn path() -> &'static str { + "" + } +} diff --git a/editor/src/utility_types.rs b/editor/src/utility_types.rs new file mode 100644 index 0000000000..6b5dc6de6b --- /dev/null +++ b/editor/src/utility_types.rs @@ -0,0 +1,99 @@ +#[derive(Debug)] +pub struct MessageData { + name: String, + fields: Vec<(String, usize)>, + path: &'static str, +} + +impl MessageData { + pub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str) -> MessageData { + MessageData { name, fields, path } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &Vec<(String, usize)> { + &self.fields + } + + pub fn path(&self) -> &'static str { + self.path + } +} + +#[derive(Debug)] +pub struct DebugMessageTree { + name: String, + variants: Option>, + message_handler: Option, + message_handler_data: Option, + path: &'static str, +} + +impl DebugMessageTree { + pub fn new(name: &str) -> DebugMessageTree { + DebugMessageTree { + name: name.to_string(), + variants: None, + message_handler: None, + message_handler_data: None, + path: "", + } + } + + pub fn set_path(&mut self, path: &'static str) { + self.path = path; + } + + pub fn add_variant(&mut self, variant: DebugMessageTree) { + if let Some(variants) = &mut self.variants { + variants.push(variant); + } else { + self.variants = Some(vec![variant]); + } + } + + pub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) { + self.message_handler_data = Some(message_handler_data); + } + + pub fn add_message_handler_field(&mut self, message_handler: MessageData) { + self.message_handler = Some(message_handler); + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn path(&self) -> &'static str { + self.path + } + + pub fn variants(&self) -> Option<&Vec> { + self.variants.as_ref() + } + + pub fn message_handler_data_fields(&self) -> Option<&MessageData> { + self.message_handler_data.as_ref() + } + + pub fn message_handler_fields(&self) -> Option<&MessageData> { + self.message_handler.as_ref() + } + + pub fn has_message_handler_data_fields(&self) -> bool { + match self.message_handler_data_fields() { + Some(_) => true, + None => false, + } + } + + pub fn has_message_handler_fields(&self) -> bool { + match self.message_handler_fields() { + Some(_) => true, + None => false, + } + } +} diff --git a/proc-macros/src/combined_message_attrs.rs b/proc-macros/src/combined_message_attrs.rs index aadca6384e..943457cd1c 100644 --- a/proc-macros/src/combined_message_attrs.rs +++ b/proc-macros/src/combined_message_attrs.rs @@ -61,7 +61,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) - <#parent as ToDiscriminant>::Discriminant }; - input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild)] }); + input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild, HierarchicalTree)] }); input.attrs.push(syn::parse_quote! { #[parent(#parent, #parent::#variant)] }); if parent_is_top { input.attrs.push(syn::parse_quote! { #[parent_is_top] }); @@ -97,7 +97,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) - fn top_level_impl(input_item: TokenStream) -> syn::Result { let mut input = syn::parse2::(input_item)?; - input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant)] }); + input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, HierarchicalTree)] }); input.attrs.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage))] }); for var in &mut input.variants { diff --git a/proc-macros/src/extract_fields.rs b/proc-macros/src/extract_fields.rs new file mode 100644 index 0000000000..606b3b8a37 --- /dev/null +++ b/proc-macros/src/extract_fields.rs @@ -0,0 +1,57 @@ +use crate::helpers::clean_rust_type_syntax; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, format_ident, quote}; +use syn::{Data, DeriveInput, Fields, Type, parse2}; + +pub fn derive_extract_field_impl(input: TokenStream) -> syn::Result { + let input = parse2::(input)?; + let struct_name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let fields = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => &fields.named, + _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs with named fields")), + }, + _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs")), + }; + + let mut field_line = Vec::new(); + // Extract field names and types as strings at compile time + let field_info = fields + .iter() + .map(|field| { + let ident = field.ident.as_ref().unwrap(); + let name = ident.to_string(); + let ty = clean_rust_type_syntax(field.ty.to_token_stream().to_string()); + let line = ident.span().start().line; + field_line.push(line); + (name, ty) + }) + .collect::>(); + + let field_str = field_info.into_iter().map(|(name, ty)| (format!("{}: {}", name, ty))); + + let res = quote! { + impl #impl_generics #struct_name #ty_generics #where_clause { + pub fn field_types() -> Vec<(String, usize)> { + vec![ + #((String::from(#field_str), #field_line)),* + ] + } + + pub fn print_field_types() { + for (field, line) in Self::field_types() { + println!("{} at line {}", field, line); + } + } + + pub fn path() -> &'static str { + file!() + } + } + }; + + Ok(res) +} diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index 11c18c8b41..230f792c0f 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -42,6 +42,58 @@ pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path { Path { leading_colon: None, segments } } +pub fn clean_rust_type_syntax(input: String) -> String { + let mut result = String::new(); + let mut chars = input.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '&' => { + result.push('&'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + '<' => { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + result.push('<'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + '>' => { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + result.push('>'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + ':' => { + if let Some(':') = chars.peek() { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + } + result.push(':'); + chars.next(); + result.push(':'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + _ => { + result.push(c); + } + } + } + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/proc-macros/src/hierarchical_tree.rs b/proc-macros/src/hierarchical_tree.rs new file mode 100644 index 0000000000..e0d6fb6a71 --- /dev/null +++ b/proc-macros/src/hierarchical_tree.rs @@ -0,0 +1,73 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, quote}; +use syn::{Data, DeriveInput, Fields, Type, parse2}; + +pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result { + let input = parse2::(input)?; + let input_type = &input.ident; + + let data = match &input.data { + Data::Enum(data) => data, + _ => return Err(syn::Error::new(Span::call_site(), "Tried to derive HierarchicalTree for non-enum")), + }; + + let build_message_tree = data.variants.iter().map(|variant| { + let variant_type = &variant.ident; + + let has_child = variant + .attrs + .iter() + .any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child")); + + if has_child { + if let Fields::Unnamed(fields) = &variant.fields { + let field_type = &fields.unnamed.first().unwrap().ty; + quote! { + { + let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type)); + let field_name = stringify!(#field_type); + const message_string: &str = "Message"; + if message_string == &field_name[field_name.len().saturating_sub(message_string.len())..] { + // The field is a Message type, recursively build its tree + let sub_tree = #field_type::build_message_tree(); + variant_tree.add_variant(sub_tree); + } + message_tree.add_variant(variant_tree); + } + } + } else { + quote! { + message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); + } + } + } else { + quote! { + message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); + } + } + }); + + let res = quote! { + impl HierarchicalTree for #input_type { + fn build_message_tree() -> DebugMessageTree { + let mut message_tree = DebugMessageTree::new(stringify!(#input_type)); + #(#build_message_tree)* + let message_handler_str = #input_type::message_handler_str(); + if message_handler_str.fields().len() > 0 { + message_tree.add_message_handler_field(message_handler_str); + } + + let message_handler_data_str = #input_type::message_handler_data_str(); + if message_handler_data_str.fields().len() > 0 { + message_tree.add_message_handler_data_field(message_handler_data_str); + } + + message_tree.set_path(file!()); + + message_tree + } + } + }; + + Ok(res) +} diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 02cd1caaf4..8d68df75c8 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -3,17 +3,23 @@ mod as_message; mod combined_message_attrs; mod discriminant; +mod extract_fields; mod helper_structs; mod helpers; +mod hierarchical_tree; mod hint; +mod message_handler_data_attr; mod transitive_child; mod widget_builder; use crate::as_message::derive_as_message_impl; use crate::combined_message_attrs::combined_message_attrs_impl; use crate::discriminant::derive_discriminant_impl; +use crate::extract_fields::derive_extract_field_impl; use crate::helper_structs::AttrInnerSingleString; +use crate::hierarchical_tree::generate_hierarchical_tree; use crate::hint::derive_hint_impl; +use crate::message_handler_data_attr::message_handler_data_attr_impl; use crate::transitive_child::derive_transitive_child_impl; use crate::widget_builder::derive_widget_builder_impl; use proc_macro::TokenStream; @@ -281,6 +287,21 @@ pub fn derive_widget_builder(input_item: TokenStream) -> TokenStream { TokenStream::from(derive_widget_builder_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) } +#[proc_macro_derive(HierarchicalTree)] +pub fn derive_hierarchical_tree(input_item: TokenStream) -> TokenStream { + TokenStream::from(generate_hierarchical_tree(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + +#[proc_macro_derive(ExtractField)] +pub fn derive_extract_field(input_item: TokenStream) -> TokenStream { + TokenStream::from(derive_extract_field_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + +#[proc_macro_attribute] +pub fn message_handler_data(attr: TokenStream, input_item: TokenStream) -> TokenStream { + TokenStream::from(message_handler_data_attr_impl(attr.into(), input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + #[cfg(test)] mod tests { use super::*; diff --git a/proc-macros/src/message_handler_data_attr.rs b/proc-macros/src/message_handler_data_attr.rs new file mode 100644 index 0000000000..b2170beac4 --- /dev/null +++ b/proc-macros/src/message_handler_data_attr.rs @@ -0,0 +1,118 @@ +use crate::helpers::{call_site_ident, clean_rust_type_syntax}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, quote}; +use syn::{ItemImpl, Type, parse2, spanned::Spanned}; + +pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream) -> syn::Result { + // Parse the input as an impl block + let impl_block = parse2::(input_item.clone())?; + + let self_ty = &impl_block.self_ty; + + let path = match &**self_ty { + Type::Path(path) => &path.path, + _ => return Err(syn::Error::new(Span::call_site(), "Expected impl implementation")), + }; + + let input_type = path.segments.last().map(|s| &s.ident).unwrap(); + + // Extract the message type from the trait path + let trait_path = match &impl_block.trait_ { + Some((_, path, _)) => path, + None => return Err(syn::Error::new(Span::call_site(), "Expected trait implementation")), + }; + + // Get the trait generics (should be MessageHandler) + if let Some(segment) = trait_path.segments.last() { + if segment.ident != "MessageHandler" { + return Err(syn::Error::new(segment.ident.span(), "Expected MessageHandler trait")); + } + if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + if args.args.len() >= 2 { + // Extract the message type (M) and data type (D) from the trait params + let message_type = &args.args[0]; + let data_type = &args.args[1]; + + // Check if the attribute is "CustomData" + let is_custom_data = attr.to_string().contains("CustomData"); + + let impl_item = match data_type { + syn::GenericArgument::Type(t) => { + match t { + syn::Type::Path(type_path) if !type_path.path.segments.is_empty() => { + // Get just the base identifier (ToolMessageData) without generics + let type_name = &type_path.path.segments.first().unwrap().ident; + + if is_custom_data { + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData { + custom_data() + } + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } else { + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData + { + MessageData::new(format!("{}",stringify!(#type_name)), #type_name::field_types(), #type_name::path()) + + } + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } + } + syn::Type::Tuple(_) => quote! { + #input_item + impl #message_type { + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + } + } + }, + syn::Type::Reference(type_reference) => { + let message_type = call_site_ident(format!("{input_type}Message")); + let type_ident = match &*type_reference.elem { + syn::Type::Path(type_path) => &type_path.path.segments.first().unwrap().ident, + _ => return Err(syn::Error::new(type_reference.elem.span(), "Expected type path")), + }; + let tr = clean_rust_type_syntax(type_reference.to_token_stream().to_string()); + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData { + MessageData::new(format!("{}", #tr),#type_ident::field_types(), #type_ident::path()) + } + + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } + _ => return Err(syn::Error::new(t.span(), "Unsupported type format")), + } + } + + _ => quote! { + #input_item + }, + }; + return Ok(impl_item); + } + } + } + Ok(input_item) +} From a40de58c7c3ef3cf568476dea5b9e4549a04506a Mon Sep 17 00:00:00 2001 From: mTvare Date: Wed, 9 Jul 2025 12:15:41 +0530 Subject: [PATCH 09/11] Fix brush bounding boxes by making BrushCacheImpl's hash not shared between different instances (#2845) * fix: add cache to each layer * fix: warning * fix: tests * Clean up code --------- Co-authored-by: Keavon Chambers --- .../node_graph/document_node_definitions.rs | 2 +- .../messages/tool/tool_messages/brush_tool.rs | 6 +- node-graph/gbrush/src/brush.rs | 2 +- node-graph/gbrush/src/brush_cache.rs | 84 +++++++++---------- 4 files changed, 45 insertions(+), 49 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 594f8125ee..3c85102098 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -946,7 +946,7 @@ fn static_nodes() -> Vec { inputs: vec![ NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), - NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), + NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false), ], ..Default::default() }, diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 9bba3711cf..a625bdb9e6 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -1,6 +1,6 @@ use super::tool_prelude::*; use crate::consts::DEFAULT_BRUSH_SIZE; -use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform}; +use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::FlowType; @@ -287,9 +287,7 @@ impl BrushToolData { } if *reference == Some("Transform".to_string()) { - let upstream = document.metadata().upstream_transform(node_id); - let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs))); - self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform; + self.transform = get_current_transform(&node.inputs) * self.transform; } } diff --git a/node-graph/gbrush/src/brush.rs b/node-graph/gbrush/src/brush.rs index b2782adc8a..3a085c0abe 100644 --- a/node-graph/gbrush/src/brush.rs +++ b/node-graph/gbrush/src/brush.rs @@ -403,7 +403,7 @@ mod test { blend_mode: BlendMode::Normal, }, }], - BrushCache::new_proto(), + BrushCache::default(), ) .await; assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20); diff --git a/node-graph/gbrush/src/brush_cache.rs b/node-graph/gbrush/src/brush_cache.rs index 853a13ca34..c5495534c1 100644 --- a/node-graph/gbrush/src/brush_cache.rs +++ b/node-graph/gbrush/src/brush_cache.rs @@ -6,11 +6,16 @@ use graphene_core::raster_types::CPU; use graphene_core::raster_types::Raster; use std::collections::HashMap; use std::hash::Hash; -use std::sync::Arc; -use std::sync::Mutex; +use std::hash::Hasher; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; -#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)] +// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten. +static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0); + +#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] struct BrushCacheImpl { + unique_id: u64, // The full previous input that was cached. prev_input: Vec, @@ -90,9 +95,29 @@ impl BrushCacheImpl { } } +impl Default for BrushCacheImpl { + fn default() -> Self { + Self { + unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst), + prev_input: Vec::new(), + background: Default::default(), + blended_image: Default::default(), + last_stroke_texture: Default::default(), + brush_texture_cache: HashMap::new(), + } + } +} + +impl PartialEq for BrushCacheImpl { + fn eq(&self, other: &Self) -> bool { + self.unique_id == other.unique_id + } +} + impl Hash for BrushCacheImpl { - // Zero hash. - fn hash(&self, _state: &mut H) {} + fn hash(&self, state: &mut H) { + self.unique_id.hash(state); + } } #[derive(Clone, Debug, Default)] @@ -103,46 +128,26 @@ pub struct BrushPlan { pub first_stroke_point_skip: usize, } -#[derive(Debug, DynAny, serde::Serialize, serde::Deserialize)] -pub struct BrushCache { - inner: Arc>, - proto: bool, -} - -impl Default for BrushCache { - fn default() -> Self { - Self::new_proto() - } -} +#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)] +pub struct BrushCache(Arc>); // A bit of a cursed implementation to work around the current node system. // The original object is a 'prototype' that when cloned gives you a independent // new object. Any further clones however are all the same underlying cache object. impl Clone for BrushCache { fn clone(&self) -> Self { - if self.proto { - let inner_val = self.inner.lock().unwrap(); - Self { - inner: Arc::new(Mutex::new(inner_val.clone())), - proto: false, - } - } else { - Self { - inner: Arc::clone(&self.inner), - proto: false, - } - } + Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone()))) } } impl PartialEq for BrushCache { fn eq(&self, other: &Self) -> bool { - if Arc::ptr_eq(&self.inner, &other.inner) { + if Arc::ptr_eq(&self.0, &other.0) { return true; } - let s = self.inner.lock().unwrap(); - let o = other.inner.lock().unwrap(); + let s = self.0.lock().unwrap(); + let o = other.0.lock().unwrap(); *s == *o } @@ -150,35 +155,28 @@ impl PartialEq for BrushCache { impl Hash for BrushCache { fn hash(&self, state: &mut H) { - self.inner.lock().unwrap().hash(state); + self.0.lock().unwrap().hash(state); } } impl BrushCache { - pub fn new_proto() -> Self { - Self { - inner: Default::default(), - proto: true, - } - } - pub fn compute_brush_plan(&self, background: Instance>, input: &[BrushStroke]) -> BrushPlan { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.compute_brush_plan(background, input) } pub fn cache_results(&self, input: Vec, blended_image: Instance>, last_stroke_texture: Instance>) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.cache_results(input, blended_image, last_stroke_texture) } pub fn get_cached_brush(&self, style: &BrushStyle) -> Option> { - let inner = self.inner.lock().unwrap(); + let inner = self.0.lock().unwrap(); inner.brush_texture_cache.get(style).cloned() } pub fn store_brush(&self, style: BrushStyle, brush: Raster) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.brush_texture_cache.insert(style, brush); } } From e02510303eb23fe9e17718979462734dabed5bb3 Mon Sep 17 00:00:00 2001 From: Kavin <40298945+Kavin006@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:19:39 +0530 Subject: [PATCH 10/11] Add X and Y offset distance overlay while dragging with the Select tool (#2814) * Add offset overlay to select tool * Add overlay to Dragging * Add axis align behavior * Style changes * Add trim to string * reduce code duplication * Code review --------- Co-authored-by: Keavon Chambers --- .../document/node_graph/node_properties.rs | 5 ++-- .../document/overlays/utility_types.rs | 30 +++++++++++++++++++ .../tool/tool_messages/select_tool.rs | 8 +++++ .../transform_layer_message_handler.rs | 28 ++--------------- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 18a7f82909..9551ed2094 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1274,6 +1274,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()]; corner_radius_row_2.push(TextLabel::new("").widget_holder()); + add_blank_assist(&mut corner_radius_row_2); let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, @@ -1381,8 +1382,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties // Size Y let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default()); - add_blank_assist(&mut corner_radius_row_2); - // Clamped let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default()); @@ -1436,7 +1435,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper if let Some(field) = graphene_std::registry::NODE_METADATA .lock() .unwrap() - .get(&proto_node_identifier) + .get(proto_node_identifier) .and_then(|metadata| metadata.fields.get(input_index)) { number_options = (field.number_min, field.number_max, field.number_mode_range); diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 6e25bb77b3..3d12ba4961 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -814,6 +814,36 @@ impl OverlayContext { self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position"); self.render_context.reset_transform().expect("Failed to reset the render context transform"); } + + pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option) { + if translation.x.abs() > 1e-3 { + self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5)); + + let width = match typed_string { + Some(ref typed_string) => typed_string, + None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(), + }; + let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.); + self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); + } + + if translation.y.abs() > 1e-3 { + self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5)); + + let height = match typed_string { + Some(ref typed_string) => typed_string, + None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(), + }; + let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.); + let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End }; + self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]); + } + + if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 { + self.line(quad.top_right(), quad.bottom_right(), None, None); + self.line(quad.bottom_left(), quad.bottom_right(), None, None); + } + } } pub enum Pivot { diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 6c8498317f..e6f4a5d7a3 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -974,6 +974,14 @@ impl Fsm for SelectToolFsmState { } } + if let Self::Dragging { .. } = self { + let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); + let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left()); + let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right()); + + overlay_context.translation_box(document_current - document_start, quad, None); + } + self } (_, SelectToolMessage::EditLayer) => { diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index a6221f729d..b9b9880e98 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -319,37 +319,15 @@ impl MessageHandler> for TransformLayer let translation = translation.to_dvec(self.initial_transform, self.increments); let viewport_translate = document_to_viewport.transform_vector2(translation); let pivot = document_to_viewport.transform_point2(self.grab_target); - let quad = Quad::from_box([pivot, pivot + viewport_translate]).0; - let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X); + let quad = Quad::from_box([pivot, pivot + viewport_translate]); responses.add(SelectToolMessage::PivotShift { offset: Some(viewport_translate), flush: false, }); - if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { - let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - - let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); - overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); - } - - if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. { - let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - let x_parameter = viewport_translate.x.clamp(-1., 1.); - let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.); - let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End }; - if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { - overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]); - } - } - - if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. { - overlay_context.line(quad[1], quad[2], None, None); - overlay_context.line(quad[3], quad[2], None, None); - } + let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); + overlay_context.translation_box(translation, quad, typed_string); } TransformOperation::Scaling(scale) => { let scale = scale.to_f64(self.increments); From 9c367aea6cef2e0bf7585327318d708d130737a2 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 9 Jul 2025 19:19:11 -0700 Subject: [PATCH 11/11] Update website deployment CI script to output auto-generated code docs artifacts --- .github/workflows/website.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 0451d4226e..93bda7e01d 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -9,6 +9,7 @@ on: pull_request: paths: - website/** + workflow_dispatch: {} env: CARGO_TERM_COLOR: always INDEX_HTML_HEAD_INCLUSION: @@ -80,8 +81,32 @@ jobs: - name: 🚚 Move `website/other/dist` contents to `website/public` run: | + mkdir -p website/public mv website/other/dist/* website/public + - name: 💿 Obtain cache of auto-generated code docs artifacts + id: cache-website-code-docs + uses: actions/cache/restore@v3 + with: + path: artifacts + key: website-code-docs + + - name: 📁 Fallback in case auto-generated code docs artifacts weren't cached + if: steps.cache-website-code-docs.outputs.cache-hit != 'true' + run: | + echo "🦀 Initial system version of Rust:" + rustc --version + rustup update stable + echo "🦀 Latest updated version of Rust:" + rustc --version + cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree + mkdir artifacts + mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt + + - name: 🚚 Move `artifacts` contents to `website/public` + run: | + mv artifacts/* website/public + - name: 📤 Publish to Cloudflare Pages id: cloudflare uses: cloudflare/pages-action@1