From 93880abc4cff49bb31ae17783360e09ba2a41aa7 Mon Sep 17 00:00:00 2001 From: James Lindsay <78500760+0HyperCube@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:13:35 +0000 Subject: [PATCH] Experimental vector meshes (#2223) * Experimental vector meshes * Clarify limitations in label and tooltip * Restore old traversal direction * Fix Bezier-rs crashes --------- Co-authored-by: Keavon Chambers --- Cargo.lock | 1 + editor/src/dispatcher.rs | 1 + .../preferences_dialog_message_handler.rs | 15 ++++++- .../document/overlays/utility_functions.rs | 6 +-- .../preferences/preferences_message.rs | 1 + .../preferences_message_handler.rs | 5 +++ .../common_functionality/utility_functions.rs | 13 ++++-- .../src/messages/tool/tool_message_handler.rs | 5 +++ .../tool/tool_messages/freehand_tool.rs | 6 ++- .../messages/tool/tool_messages/pen_tool.rs | 40 ++++++++++++------- .../tool/tool_messages/spline_tool.rs | 22 ++++++---- editor/src/messages/tool/utility_types.rs | 22 +--------- libraries/bezier-rs/src/subpath/solvers.rs | 8 ++-- node-graph/gcore/Cargo.toml | 1 + node-graph/gcore/src/vector/vector_data.rs | 14 +++---- .../src/vector/vector_data/attributes.rs | 20 ++++------ 16 files changed, 102 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a829eac..412bedd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2397,6 +2397,7 @@ dependencies = [ "serde_json", "specta", "spirv-std", + "tinyvec", "tokio", "usvg", "vello", diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index f6a922a6..22d331e7 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -228,6 +228,7 @@ impl Dispatcher { input: &self.message_handlers.input_preprocessor_message_handler, persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data, node_graph: &self.message_handlers.portfolio_message_handler.executor, + preferences: &self.message_handlers.preferences_message_handler, }; self.message_handlers.tool_message_handler.process_message(message, &mut queue, data); 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 b084df83..b4375a7f 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 @@ -47,7 +47,7 @@ impl PreferencesDialogMessageHandler { TextLabel::new("Zoom with Scroll").table_align(true).tooltip(zoom_with_scroll_tooltip).widget_holder(), ]; let vello_tooltip = "Use the experimental Vello renderer (your browser must support WebGPU)"; - let renderer_section = vec![TextLabel::new("Renderer").italic(true).widget_holder()]; + let renderer_section = vec![TextLabel::new("Experimental").italic(true).widget_holder()]; let use_vello = vec![ CheckboxInput::new(preferences.use_vello && preferences.supports_wgpu()) .tooltip(vello_tooltip) @@ -55,7 +55,7 @@ impl PreferencesDialogMessageHandler { .on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::UseVello { use_vello: checkbox_input.checked }.into()) .widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder(), - TextLabel::new("Vello (Experimental)") + TextLabel::new("Vello Renderer") .table_align(true) .tooltip(vello_tooltip) .disabled(!preferences.supports_wgpu()) @@ -85,11 +85,22 @@ impl PreferencesDialogMessageHandler { // .widget_holder(), // ]; + let vector_mesh_tooltip = "Allow tools to produce vector meshes, where more than two segments can connect to an anchor point.\n\nCurrently this does not properly handle line joins and fills."; + let vector_meshes = vec![ + CheckboxInput::new(preferences.vector_meshes) + .tooltip(vector_mesh_tooltip) + .on_update(|checkbox_input: &CheckboxInput| PreferencesMessage::VectorMeshes { enabled: checkbox_input.checked }.into()) + .widget_holder(), + Separator::new(SeparatorType::Unrelated).widget_holder(), + TextLabel::new("Vector Meshes").table_align(true).tooltip(vector_mesh_tooltip).widget_holder(), + ]; + Layout::WidgetLayout(WidgetLayout::new(vec![ LayoutGroup::Row { widgets: input_section }, LayoutGroup::Row { widgets: zoom_with_scroll }, LayoutGroup::Row { widgets: renderer_section }, LayoutGroup::Row { widgets: use_vello }, + LayoutGroup::Row { widgets: vector_meshes }, // LayoutGroup::Row { widgets: imaginate_server_hostname }, // LayoutGroup::Row { widgets: imaginate_refresh_frequency }, ])) diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index b71d3e38..1556699e 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -1,7 +1,7 @@ use super::utility_types::OverlayContext; use crate::consts::HIDE_HANDLE_DISTANCE; use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState}; -use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler; +use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler}; use graphene_core::vector::ManipulatorPointId; @@ -62,7 +62,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape } } -pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) { +pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, preferences: &PreferencesMessageHandler) { for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue; @@ -72,7 +72,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: & let selected = shape_editor.selected_shape_state.get(&layer); let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); - for point in vector_data.single_connected_points() { + for point in vector_data.extendable_points(preferences.vector_meshes) { let Some(position) = vector_data.point_domain.position_from_id(point) else { continue }; let position = transform.transform_point2(position); overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None); diff --git a/editor/src/messages/preferences/preferences_message.rs b/editor/src/messages/preferences/preferences_message.rs index 44f1c686..692b21e2 100644 --- a/editor/src/messages/preferences/preferences_message.rs +++ b/editor/src/messages/preferences/preferences_message.rs @@ -9,5 +9,6 @@ pub enum PreferencesMessage { ImaginateRefreshFrequency { seconds: f64 }, UseVello { use_vello: bool }, ImaginateServerHostname { hostname: String }, + VectorMeshes { enabled: bool }, ModifyLayout { zoom_with_scroll: bool }, } diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index f83234f5..55b43236 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -8,6 +8,7 @@ pub struct PreferencesMessageHandler { pub imaginate_refresh_frequency: f64, pub zoom_with_scroll: bool, pub use_vello: bool, + pub vector_meshes: bool, } impl PreferencesMessageHandler { @@ -34,6 +35,7 @@ impl Default for PreferencesMessageHandler { imaginate_refresh_frequency: 1., zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll), use_vello, + vector_meshes: false, } } } @@ -88,6 +90,9 @@ impl MessageHandler for PreferencesMessageHandler { responses.add(PortfolioMessage::ImaginateCheckServerStatus); responses.add(PortfolioMessage::EditorPreferences); } + PreferencesMessage::VectorMeshes { enabled } => { + self.vector_meshes = enabled; + } PreferencesMessage::ModifyLayout { zoom_with_scroll } => { self.zoom_with_scroll = zoom_with_scroll; diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 74ae9eda..e845bdc9 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -6,8 +6,14 @@ use graphene_std::vector::PointId; use glam::DVec2; /// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable. -pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator) -> Option<(LayerNodeIdentifier, PointId, DVec2)> { - closest_point(document, goal, tolerance, layers, |_| false) +pub fn should_extend( + document: &DocumentMessageHandler, + goal: DVec2, + tolerance: f64, + layers: impl Iterator, + preferences: &PreferencesMessageHandler, +) -> Option<(LayerNodeIdentifier, PointId, DVec2)> { + closest_point(document, goal, tolerance, layers, |_| false, preferences) } /// Determine the closest point to the goal point under max_distance. @@ -18,6 +24,7 @@ pub fn closest_point( max_distance: f64, layers: impl Iterator, exclude: T, + preferences: &PreferencesMessageHandler, ) -> Option<(LayerNodeIdentifier, PointId, DVec2)> where T: Fn(PointId) -> bool, @@ -29,7 +36,7 @@ where let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue; }; - for id in vector_data.single_connected_points() { + for id in vector_data.extendable_points(preferences.vector_meshes) { if exclude(id) { continue; } diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 239f0acc..4d4d9d37 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -18,6 +18,7 @@ pub struct ToolMessageData<'a> { pub input: &'a InputPreprocessorMessageHandler, pub persistent_data: &'a PersistentData, pub node_graph: &'a NodeGraphExecutor, + pub preferences: &'a PreferencesMessageHandler, } #[derive(Debug, Default)] @@ -36,6 +37,7 @@ impl MessageHandler> for ToolMessageHandler { input, persistent_data, node_graph, + preferences, } = data; let font_cache = &persistent_data.font_cache; @@ -86,6 +88,7 @@ impl MessageHandler> for ToolMessageHandler { font_cache, shape_editor: &mut self.shape_editor, node_graph, + preferences, }; if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort { @@ -180,6 +183,7 @@ impl MessageHandler> for ToolMessageHandler { font_cache, shape_editor: &mut self.shape_editor, node_graph, + preferences, }; // Set initial hints and cursor @@ -269,6 +273,7 @@ impl MessageHandler> for ToolMessageHandler { font_cache, shape_editor: &mut self.shape_editor, node_graph, + preferences, }; if matches!(tool_message, ToolMessage::UpdateHints) { if self.transform_layer_handler.is_transforming() { diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 9fd3ca09..ce7f4dfb 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -191,13 +191,14 @@ impl Fsm for FreehandToolFsmState { global_tool_data, input, shape_editor, + preferences, .. } = tool_action_data; let ToolMessage::Freehand(event) = event else { return self }; match (self, event) { (_, FreehandToolMessage::Overlays(mut overlay_context)) => { - path_endpoint_overlays(document, shape_editor, &mut overlay_context); + path_endpoint_overlays(document, shape_editor, &mut overlay_context, tool_action_data.preferences); self } @@ -210,7 +211,8 @@ impl Fsm for FreehandToolFsmState { // Extend an endpoint of the selected path let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); - if let Some((layer, point, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) { + let tolerance = crate::consts::SNAP_POINT_TOLERANCE; + if let Some((layer, point, position)) = should_extend(document, input.mouse.position, tolerance, selected_nodes.selected_layers(document.metadata()), preferences) { tool_data.layer = Some(layer); tool_data.end_point = Some((position, point)); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 66fcbf1f..df917b62 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -275,19 +275,19 @@ impl PenToolData { } } - fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, responses: &mut VecDeque) -> Option { + fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque) -> Option { let document = snap_data.document; let next_handle_start = self.next_handle_start; let handle_start = self.latest_point()?.handle_start; let mouse = snap_data.input.mouse.position; let Some(handle_end) = self.handle_end else { self.handle_end = Some(next_handle_start); - self.place_anchor(snap_data, transform, mouse, responses); + self.place_anchor(snap_data, transform, mouse, preferences, responses); self.latest_point_mut()?.handle_start = next_handle_start; return None; }; let next_point = self.next_point; - self.place_anchor(snap_data, transform, mouse, responses); + self.place_anchor(snap_data, transform, mouse, preferences, responses); let handles = [handle_start - self.latest_point()?.pos, handle_end - next_point].map(Some); // Get close path @@ -298,7 +298,7 @@ impl PenToolData { let vector_data = document.network_interface.compute_modified_vector(layer)?; let start = self.latest_point()?.id; let transform = document.metadata().document_to_viewport * transform; - for id in vector_data.single_connected_points().filter(|&point| point != start) { + for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != start) { let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue }; let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point)); let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); @@ -359,7 +359,7 @@ impl PenToolData { Some(PenToolFsmState::DraggingHandle) } - fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque) -> Option { + fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque) -> Option { let document = snap_data.document; let relative = self.latest_point().map(|point| point.pos); @@ -370,7 +370,7 @@ impl PenToolData { let layer = selected_layers.next().filter(|_| selected_layers.next().is_none())?; let vector_data = document.network_interface.compute_modified_vector(layer)?; let transform = document.metadata().document_to_viewport * transform; - for point in vector_data.single_connected_points() { + for point in vector_data.extendable_points(preferences.vector_meshes) { let Some(pos) = vector_data.point_domain.position_from_id(point) else { continue }; let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point)); let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2); @@ -456,7 +456,15 @@ impl PenToolData { transform.inverse().transform_point2(document_pos) } - fn create_initial_point(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, tool_options: &PenOptions, append: bool) { + fn create_initial_point( + &mut self, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + responses: &mut VecDeque, + tool_options: &PenOptions, + append: bool, + preferences: &PreferencesMessageHandler, + ) { let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position)); let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default()); let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document); @@ -464,7 +472,8 @@ impl PenToolData { let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); self.handle_end = None; - if let Some((layer, point, position)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) { + let tolerance = crate::consts::SNAP_POINT_TOLERANCE; + if let Some((layer, point, position)) = should_extend(document, viewport, tolerance, selected_nodes.selected_layers(document.metadata()), preferences) { // Perform extension of an existing path self.add_point(LastPoint { id: point, @@ -533,6 +542,7 @@ impl Fsm for PenToolFsmState { global_tool_data, input, shape_editor, + preferences, .. } = tool_action_data; @@ -638,7 +648,7 @@ impl Fsm for PenToolFsmState { (PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => { responses.add(DocumentMessage::StartTransaction); - tool_data.create_initial_point(document, input, responses, tool_options, input.keyboard.key(append_to_selected)); + tool_data.create_initial_point(document, input, responses, tool_options, input.keyboard.key(append_to_selected), preferences); // Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle PenToolFsmState::DraggingHandle @@ -660,7 +670,7 @@ impl Fsm for PenToolFsmState { if tool_data.buffering_merged_vector { tool_data.buffering_merged_vector = false; tool_data.bend_from_previous_point(SnapData::new(document, input), transform); - tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses); + tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses); tool_data.buffering_merged_vector = false; PenToolFsmState::DraggingHandle } else { @@ -673,7 +683,7 @@ impl Fsm for PenToolFsmState { let layers = LayerNodeIdentifier::ROOT_PARENT .descendants(document.metadata()) .filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])); - if let Some((other_layer, _, _)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, layers) { + if let Some((other_layer, _, _)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE, layers, preferences) { let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); let mut selected_layers = selected_nodes.selected_layers(document.metadata()); if let Some(current_layer) = selected_layers.next().filter(|current_layer| selected_layers.next().is_none() && *current_layer != other_layer) { @@ -696,7 +706,7 @@ impl Fsm for PenToolFsmState { self } (PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => tool_data - .finish_placing_handle(SnapData::new(document, input), transform, responses) + .finish_placing_handle(SnapData::new(document, input), transform, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor), (PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => { tool_data.modifiers = ModifierState { @@ -724,7 +734,7 @@ impl Fsm for PenToolFsmState { break_handle: input.keyboard.key(break_handle), }; let state = tool_data - .place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses) + .place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses) .unwrap_or(PenToolFsmState::Ready); // Auto-panning @@ -783,7 +793,7 @@ impl Fsm for PenToolFsmState { if tool_data.point_index > 0 { tool_data.point_index -= 1; tool_data - .place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses) + .place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses) .unwrap_or(PenToolFsmState::PlacingAnchor) } else { responses.add(PenToolMessage::Abort); @@ -792,7 +802,7 @@ impl Fsm for PenToolFsmState { } (_, PenToolMessage::Redo) => { tool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1)); - tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses); + tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses); match tool_data.point_index { 0 => PenToolFsmState::Ready, _ => PenToolFsmState::PlacingAnchor, diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 0962825d..7a9708b8 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -227,6 +227,7 @@ impl Fsm for SplineToolFsmState { global_tool_data, input, shape_editor, + preferences, .. } = tool_action_data; @@ -234,7 +235,7 @@ impl Fsm for SplineToolFsmState { match (self, event) { (_, SplineToolMessage::CanvasTransformed) => self, (_, SplineToolMessage::Overlays(mut overlay_context)) => { - path_endpoint_overlays(document, shape_editor, &mut overlay_context); + path_endpoint_overlays(document, shape_editor, &mut overlay_context, preferences); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); self } @@ -250,7 +251,7 @@ impl Fsm for SplineToolFsmState { // Extend an endpoint of the selected path let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap(); - if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata())) { + if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata()), preferences) { tool_data.layer = Some(layer); tool_data.points.push((point, position)); tool_data.next_point = position; @@ -304,7 +305,7 @@ impl Fsm for SplineToolFsmState { if tool_data.layer.is_none() { return SplineToolFsmState::Ready; }; - if join_path(document, input.mouse.position, tool_data, responses) { + if join_path(document, input.mouse.position, tool_data, preferences, responses) { responses.add(DocumentMessage::EndTransaction); return SplineToolFsmState::Ready; } @@ -318,7 +319,7 @@ impl Fsm for SplineToolFsmState { (SplineToolFsmState::Drawing, SplineToolMessage::PointerMove) => { let Some(layer) = tool_data.layer else { return SplineToolFsmState::Ready }; let ignore = |cp: PointId| tool_data.preview_point.is_some_and(|pp| pp == cp) || tool_data.points.last().is_some_and(|(ep, _)| *ep == cp); - let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore); + let join_point = closest_point(document, input.mouse.position, PATH_JOIN_THRESHOLD, vec![layer].into_iter(), ignore, preferences); // Endpoints snapping if let Some((_, _, point)) = join_point { @@ -402,7 +403,7 @@ impl Fsm for SplineToolFsmState { } /// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`. -fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, responses: &mut VecDeque) -> bool { +fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler, responses: &mut VecDeque) -> bool { let Some(&(endpoint, _)) = tool_data.points.last() else { return false }; let preview_point = tool_data.preview_point; @@ -410,9 +411,14 @@ fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mu let selected_layers = selected_nodes.selected_layers(document.metadata()); // Get the closest point to mouse position which is not preview_point or end_point. - let closest_point = closest_point(document, mouse_pos, PATH_JOIN_THRESHOLD, selected_layers, |cp| { - preview_point.is_some_and(|pp| pp == cp) || cp == endpoint - }); + let closest_point = closest_point( + document, + mouse_pos, + PATH_JOIN_THRESHOLD, + selected_layers, + |cp| preview_point.is_some_and(|pp| pp == cp) || cp == endpoint, + preferences, + ); let Some((layer, join_point, _)) = closest_point else { return false }; // Last end point inserted was the preview point and segment therefore we delete it before joining the end_point & join_point. diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index a547a3af..c5f685d4 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -26,27 +26,7 @@ pub struct ToolActionHandlerData<'a> { pub font_cache: &'a FontCache, pub shape_editor: &'a mut ShapeState, pub node_graph: &'a NodeGraphExecutor, -} -impl<'a> ToolActionHandlerData<'a> { - pub fn new( - document: &'a mut DocumentMessageHandler, - document_id: DocumentId, - global_tool_data: &'a DocumentToolData, - input: &'a InputPreprocessorMessageHandler, - font_cache: &'a FontCache, - shape_editor: &'a mut ShapeState, - node_graph: &'a NodeGraphExecutor, - ) -> Self { - Self { - document, - document_id, - global_tool_data, - input, - font_cache, - shape_editor, - node_graph, - } - } + pub preferences: &'a PreferencesMessageHandler, } pub trait ToolCommon: for<'a, 'b> MessageHandler> + LayoutHolder + ToolTransition + ToolMetadata {} diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index 72a66bc0..2393aa55 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -428,7 +428,7 @@ impl Subpath { _ => 4., }; // TODO: Besides returning None using the `?` operator, is there a more appropriate way to handle a `None` result from `get_segment`? - let in_segment = self.get_segment(self.len_segments() - 1)?; + let in_segment = self.get_segment(self.len_segments().checked_sub(1)?)?; let out_segment = other.get_segment(0)?; let in_tangent = in_segment.tangent(TValue::Parametric(1.)); @@ -483,13 +483,13 @@ impl Subpath { let center_to_right = right - center; let center_to_left = left - center; - let in_segment = self.get_segment(self.len_segments() - 1).unwrap(); - let in_tangent = in_segment.tangent(TValue::Parametric(1.)); + let in_segment = self.len_segments().checked_sub(1).and_then(|segment| self.get_segment(segment)); + let in_tangent = in_segment.map(|in_segment| in_segment.tangent(TValue::Parametric(1.))); let mut angle = center_to_right.angle_to(center_to_left) / 2.; let mut arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right); - if (arc_point - left).angle_to(in_tangent).abs() > PI / 2. { + if in_tangent.map(|in_tangent| (arc_point - left).angle_to(in_tangent).abs()).unwrap_or_default() > PI / 2. { angle = angle - PI * (if angle < 0. { -1. } else { 1. }); arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right); } diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index ac4465bd..80fc2462 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -55,6 +55,7 @@ glam = { workspace = true, default-features = false, features = [ # Required dependencies half = { version = "2.4.1", default-features = false, features = ["bytemuck"] } +tinyvec = { version = "1" } # Optional workspace dependencies dyn-any = { workspace = true, optional = true } diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 93cadbbb..ef19ac38 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -243,14 +243,12 @@ impl VectorData { self.point_domain.resolve_id(point).map_or(0, |point| self.segment_domain.connected_count(point)) } - /// Points connected to a single segment - pub fn single_connected_points(&self) -> impl Iterator + '_ { - self.point_domain - .ids() - .iter() - .enumerate() - .filter(|(index, _)| self.segment_domain.connected_count(*index) == 1) - .map(|(_, &id)| id) + /// Points that can be extended from. + /// + /// This is usually only points with exactly one connection unless vector meshes are enabled. + pub fn extendable_points(&self, vector_meshes: bool) -> impl Iterator + '_ { + let point_ids = self.point_domain.ids().iter().enumerate(); + point_ids.filter(move |(index, _)| vector_meshes || self.segment_domain.connected_count(*index) == 1).map(|(_, &id)| id) } /// Computes if all the connected handles are colinear for an anchor, or if that handle is colinear for a handle. diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index e41c9988..4b0ebc74 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -654,7 +654,7 @@ impl super::VectorData { } } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] struct StrokePathIterPointSegmentMetadata { segment_index: usize, start_from_end: bool, @@ -675,28 +675,24 @@ impl StrokePathIterPointSegmentMetadata { } #[derive(Clone, Default)] -struct StrokePathIterPointMetadata([Option; 2]); +struct StrokePathIterPointMetadata(tinyvec::TinyVec<[StrokePathIterPointSegmentMetadata; 2]>); impl StrokePathIterPointMetadata { fn set(&mut self, value: StrokePathIterPointSegmentMetadata) { - if self.0[0].is_none() { - self.0[0] = Some(value) - } else if self.0[1].is_none() { - self.0[1] = Some(value); - } else { - panic!("Mesh networks are not supported"); - } + self.0.insert(0, value); } #[must_use] fn connected(&self) -> usize { - self.0.iter().filter(|val| val.is_some()).count() + self.0.len() } #[must_use] fn take_first(&mut self) -> Option { - self.0[0].take().or_else(|| self.0[1].take()) + self.0.pop() } fn take_eq(&mut self, target: StrokePathIterPointSegmentMetadata) -> bool { - self.0[0].take_if(|&mut value| value == target).or_else(|| self.0[1].take_if(|&mut value| value == target)).is_some() + let has_taken = self.0.contains(&target); + self.0.retain(|value| *value != target); + has_taken } }