diff --git a/Cargo.lock b/Cargo.lock index e38f273a..7c18c5c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1777,6 +1777,7 @@ version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" dependencies = [ + "bytemuck", "libm", "serde", ] diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 211104bc..8284de4f 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -223,9 +223,9 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }), entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt, segment_editing_modifier: Control }), entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete), - entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors), - entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints), - entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllPoints), + entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAll), + entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllSelected), + entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllSelected), entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete), entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }), entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }), diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 6f652bf6..fa0f3a0e 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -1,12 +1,14 @@ use super::utility_types::{DrawHandles, OverlayContext}; use crate::consts::HIDE_HANDLE_DISTANCE; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState}; use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler}; use glam::{DAffine2, DVec2}; use graphene_std::subpath::{Bezier, BezierHandles}; use graphene_std::vector::misc::ManipulatorPointId; -use graphene_std::vector::{PointId, SegmentId}; +use graphene_std::vector::{PointId, SegmentId, Vector}; +use std::collections::HashMap; use wasm_bindgen::JsCast; pub fn overlay_canvas_element() -> Option { @@ -24,33 +26,40 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d { create_context().expect("Failed to get canvas context") } -pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor: &ShapeState) -> Vec { - let selected_points = shape_editor.selected_points(); - let selected_anchors = selected_points - .filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(*p) } else { None }) +pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor: &ShapeState) -> HashMap> { + let mut map = HashMap::new(); + + for (layer, state) in &shape_editor.selected_shape_state { + let Some(vector) = network_interface.compute_modified_vector(*layer) else { continue }; + let selected_segments = selected_segments_for_layer(&vector, state); + + map.insert(*layer, selected_segments); + } + + map +} + +pub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState) -> Vec { + let selected_anchors = state + .selected_points() + .filter_map(|point| if let ManipulatorPointId::Anchor(p) = point { Some(p) } else { None }) .collect::>(); // Collect the segments whose handles are selected - let mut selected_segments = shape_editor + let mut selected_segments = state .selected_points() .filter_map(|point_id| match point_id { - ManipulatorPointId::PrimaryHandle(segment_id) | ManipulatorPointId::EndHandle(segment_id) => Some(*segment_id), + ManipulatorPointId::PrimaryHandle(segment_id) | ManipulatorPointId::EndHandle(segment_id) => Some(segment_id), ManipulatorPointId::Anchor(_) => None, }) .collect::>(); - // TODO: Currently if there are two duplicate layers, both of their segments get overlays // Adding segments which are are connected to selected anchors - for layer in network_interface.selected_nodes().selected_layers(network_interface.document_metadata()) { - let Some(vector) = network_interface.compute_modified_vector(layer) else { continue }; - - for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() { - if selected_anchors.contains(&start) || selected_anchors.contains(&end) { - selected_segments.push(segment_id); - } + for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() { + if selected_anchors.contains(&start) || selected_anchors.contains(&end) { + selected_segments.push(segment_id); } } - selected_segments } @@ -124,22 +133,21 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle overlay_context.outline_vector(&vector, transform); } + let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else { + continue; + }; + // Get the selected segments and then add a bold line overlay on them for (segment_id, bezier, _, _) in vector.segment_iter() { - let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else { - continue; - }; - if selected_shape_state.is_segment_selected(segment_id) { overlay_context.outline_select_bezier(bezier, transform); } } - let selected = shape_editor.selected_shape_state.get(&layer); - let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point)); + let is_selected = |point: ManipulatorPointId| selected_shape_state.is_point_selected(point); if display_handles { - let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector.adjacent_segment(point_id)).collect(); + let opposite_handles_data = selected_shape_state.selected_points().filter_map(|point_id| vector.adjacent_segment(&point_id)).collect::>(); match draw_handles { DrawHandles::All => { @@ -148,9 +156,11 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle }); } DrawHandles::SelectedAnchors(ref selected_segments) => { + let Some(focused_segments) = selected_segments.get(&layer) else { continue }; + vector .segment_bezier_iter() - .filter(|(segment_id, ..)| selected_segments.contains(segment_id)) + .filter(|(segment_id, ..)| focused_segments.contains(segment_id)) .for_each(|(segment_id, bezier, _start, _end)| { overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context); }); @@ -161,7 +171,9 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle } } } - DrawHandles::FrontierHandles(ref segment_endpoints) => { + DrawHandles::FrontierHandles(ref segment_endpoints_by_layer) => { + let Some(segment_endpoints) = segment_endpoints_by_layer.get(&layer) else { continue }; + vector .segment_bezier_iter() .filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id)) diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 31e87ade..f02372ff 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -4,6 +4,7 @@ use crate::consts::{ 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, SEGMENT_SELECTED_THICKNESS, }; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::Message; use core::borrow::Borrow; use core::f64::consts::{FRAC_PI_2, PI, TAU}; @@ -1024,7 +1025,7 @@ pub enum Pivot { pub enum DrawHandles { All, - SelectedAnchors(Vec), - FrontierHandles(HashMap>), + SelectedAnchors(HashMap>), + FrontierHandles(HashMap>>), None, } diff --git a/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs b/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs index 1a752226..6cc9f37a 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types_vello.rs @@ -3,6 +3,7 @@ use crate::consts::{ 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::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::Message; use core::borrow::Borrow; use core::f64::consts::{FRAC_PI_2, PI, TAU}; @@ -400,8 +401,8 @@ pub enum Pivot { pub enum DrawHandles { All, - SelectedAnchors(Vec), - FrontierHandles(HashMap>), + SelectedAnchors(HashMap>), + FrontierHandles(HashMap>>), None, } diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index 99ca166c..1c937ae2 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -2,7 +2,7 @@ use super::graph_modification_utils::merge_layers; use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint}; use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position}; use crate::consts::HANDLE_LENGTH_FACTOR; -use crate::messages::portfolio::document::overlays::utility_functions::selected_segments; +use crate::messages::portfolio::document::overlays::utility_functions::selected_segments_for_layer; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::misc::{PathSnapSource, SnapSource}; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; @@ -421,7 +421,7 @@ impl ShapeState { (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors) } - pub fn close_selected_path(&self, document: &DocumentMessageHandler, responses: &mut VecDeque) { + pub fn close_selected_path(&self, document: &DocumentMessageHandler, responses: &mut VecDeque, vector_meshes: bool) { // First collect all selected anchor points across all layers let all_selected_points: Vec<(LayerNodeIdentifier, PointId)> = self .selected_shape_state @@ -449,7 +449,8 @@ impl ShapeState { let Some(vector1) = document.network_interface.compute_modified_vector(layer1) else { return }; let Some(vector2) = document.network_interface.compute_modified_vector(layer2) else { return }; - if vector1.all_connected(start_point).count() != 1 || vector2.all_connected(end_point).count() != 1 { + // If vector meshes is not selected then only for endpoints, otherwise normally applicable + if !vector_meshes && (vector1.all_connected(start_point).count() != 1 || vector2.all_connected(end_point).count() != 1) { return; } @@ -559,7 +560,7 @@ impl ShapeState { select_threshold: f64, extend_selection: bool, path_overlay_mode: PathOverlayMode, - frontier_handles_info: &Option>>, + frontier_handles_info: Option<&HashMap>>>, ) -> Option> { if self.selected_shape_state.is_empty() { return None; @@ -608,7 +609,7 @@ impl ShapeState { mouse_position: DVec2, select_threshold: f64, path_overlay_mode: PathOverlayMode, - frontier_handles_info: &Option>>, + frontier_handles_info: Option<&HashMap>>>, point_editing_mode: bool, ) -> Option<(bool, Option)> { if self.selected_shape_state.is_empty() { @@ -622,15 +623,22 @@ impl ShapeState { } let vector = network_interface.compute_modified_vector(layer)?; let point_position = manipulator_point_id.get_position(&vector)?; - + let selected_shape_state = self.selected_shape_state.get(&layer)?; // Check if point is visible under current overlay mode or not - let selected_segments = selected_segments(network_interface, self); + let selected_segments_for_layer = selected_segments_for_layer(&vector, selected_shape_state); let selected_points = self.selected_points().cloned().collect::>(); - if !is_visible_point(manipulator_point_id, &vector, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) { + let frontier_handles_for_layer = frontier_handles_info.and_then(|frontier_handles| frontier_handles.get(&layer)); + if !is_visible_point( + manipulator_point_id, + &vector, + path_overlay_mode, + frontier_handles_for_layer, + &selected_segments_for_layer, + &selected_points, + ) { return None; } - let selected_shape_state = self.selected_shape_state.get(&layer)?; let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); // Offset to snap the selected point to the cursor @@ -733,6 +741,13 @@ impl ShapeState { } } + /// Selects all segments for the selected layers. + pub fn select_all_segments_in_selected_layers(&mut self, document: &DocumentMessageHandler) { + for (&layer, state) in self.selected_shape_state.iter_mut() { + Self::select_all_segments_in_layer_with_state(document, layer, state); + } + } + /// Internal helper function that selects all anchors, and deselects all handles, for a layer given its [`LayerNodeIdentifier`] and [`SelectedLayerState`]. fn select_all_anchors_in_layer_with_state(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, state: &mut SelectedLayerState) { let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return }; @@ -744,6 +759,15 @@ impl ShapeState { } } + /// Internal helper function that selects all segments, for a layer given its [`LayerNodeIdentifier`] and [`SelectedLayerState`]. + fn select_all_segments_in_layer_with_state(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, state: &mut SelectedLayerState) { + let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return }; + + for &segment in vector.segment_domain.ids() { + state.select_segment(segment); + } + } + /// Deselects all points (anchors and handles) across every selected layer. pub fn deselect_all_points(&mut self) { for state in self.selected_shape_state.values_mut() { @@ -1606,7 +1630,7 @@ impl ShapeState { mouse_position: DVec2, select_threshold: f64, path_overlay_mode: PathOverlayMode, - frontier_handles_info: &Option>>, + frontier_handles_info: Option<&HashMap>>>, ) -> Option<(LayerNodeIdentifier, ManipulatorPointId)> { if self.selected_shape_state.is_empty() { return None; @@ -1621,10 +1645,11 @@ impl ShapeState { if distance_squared < select_threshold_squared { // Check if point is visible in current PathOverlayMode let vector = network_interface.compute_modified_vector(layer)?; - let selected_segments = selected_segments(network_interface, self); + let Some(state) = self.selected_shape_state.get(&layer) else { continue }; + let selected_segments = selected_segments_for_layer(&vector, state); let selected_points = self.selected_points().cloned().collect::>(); - - if !is_visible_point(manipulator_point_id, &vector, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) { + let frontier_handles_for_layer = frontier_handles_info.and_then(|handles_info| handles_info.get(&layer)); + if !is_visible_point(manipulator_point_id, &vector, path_overlay_mode, frontier_handles_for_layer, &selected_segments, &selected_points) { return None; } @@ -1868,53 +1893,64 @@ impl ShapeState { } } - pub fn select_points_by_manipulator_id(&mut self, points: &Vec) { - let layers_to_modify: Vec<_> = self.selected_shape_state.keys().cloned().collect(); + pub fn select_anchor_and_connected_handles(&mut self, network_interface: &NodeNetworkInterface) { + let mut non_empty_layers = self.selected_shape_state.iter_mut().filter(|(_, state)| !state.is_empty()); - for layer in layers_to_modify { - if let Some(state) = self.selected_shape_state.get_mut(&layer) { - for point in points { - state.select_point(*point); - } + let Some((layer, state)) = non_empty_layers.next() else { return }; + if non_empty_layers.next().is_some() { + return; + } + let Some(vector) = network_interface.compute_modified_vector(*layer) else { return }; + + // Get the current point and its connected handles + let selected_points = state.selected_points.clone(); + if let Some(point) = selected_points.iter().next() { + if let Some(anchor) = point.get_anchor(&vector) { + state.select_point(ManipulatorPointId::Anchor(anchor)); + } + if let Some(handles) = point.get_handle_pair(&vector) { + state.select_point(handles[0].to_manipulator_point()); + state.select_point(handles[1].to_manipulator_point()); } } } - /// Converts a nearby clicked anchor point's handles between sharp (zero-length handles) and smooth (pulled-apart handle(s)). - /// If both handles aren't zero-length, they are set that. If both are zero-length, they are stretched apart by a reasonable amount. - /// This can can be activated by double clicking on an anchor with the Path tool. - 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 = network_interface.compute_modified_vector(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; - - // Find the closest anchor point on the current layer - for (&id, &anchor) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) { - let screenspace = transform_to_screenspace.transform_point2(anchor); - let distance_squared = screenspace.distance_squared(target); - - if distance_squared < closest_distance_squared { - closest_distance_squared = distance_squared; - result = Some((id, anchor)); - } + pub fn select_points_by_layer_and_id(&mut self, points: &HashMap>) { + for (layer, points) in points { + if let Some(state) = self.selected_shape_state.get_mut(layer) { + points.iter().for_each(|point| state.select_point(*point)); } + } + } - let (id, anchor) = result?; - let handles = vector.all_connected(id); - let positions = handles - .filter_map(|handle| handle.to_manipulator_point().get_position(&vector)) - .filter(|&handle| anchor.abs_diff_eq(handle, 1e-5)) - .count(); + pub fn select_point_by_layer_and_id(&mut self, point: ManipulatorPointId, layer: LayerNodeIdentifier) { + if let Some(state) = self.selected_shape_state.get_mut(&layer) { + state.select_point(point); + } + } - // Check if the anchor is connected to linear segments. - let one_or_more_segment_linear = vector.connected_linear_segments(id) != 0; + /// Converts all selected anchor points' handles between sharp (zero-length handles) and smooth (pulled-apart colinear handle(s)). + /// If both handles aren't zero-length, they are set to that. If both are zero-length, they are stretched apart by a reasonable amount. + /// This can can be activated by double clicking on an anchor with the Path tool. + pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, responses: &mut VecDeque) { + let mut process_layer = |layer: LayerNodeIdentifier, selected_points: &HashSet| { + let vector = network_interface.compute_modified_vector(layer)?; // Check by comparing the handle positions to the anchor if this manipulator group is a point - for point in self.selected_points() { + for point in selected_points { let Some(point_id) = point.as_anchor() else { continue }; + let anchor = point.get_position(&vector)?; + let handles = vector.all_connected(point_id); + + // TODO: Check if this method of finding non-colinear is really required + let positions = handles + .filter_map(|handle| handle.to_manipulator_point().get_position(&vector)) + .filter(|&handle| anchor.abs_diff_eq(handle, 1e-5)) + .count(); + + // Check if the anchor is connected to linear segments. + let one_or_more_segment_linear = vector.connected_linear_segments(point_id) != 0; + if positions != 0 || one_or_more_segment_linear { self.convert_manipulator_handles_to_colinear(&vector, point_id, responses, layer); } else { @@ -1958,13 +1994,10 @@ impl ShapeState { Some(true) }; - for &layer in self.selected_shape_state.keys() { - if let Some(result) = process_layer(layer) { - return result; - } - } - - false + self.selected_shape_state.iter().for_each(|(layer, state)| { + let selected_points = &state.selected_points; + process_layer(*layer, selected_points); + }); } #[allow(clippy::too_many_arguments)] @@ -1974,7 +2007,7 @@ impl ShapeState { selection_shape: SelectionShape, selection_change: SelectionChange, path_overlay_mode: PathOverlayMode, - frontier_handles_info: &Option>>, + frontier_handles_info: Option<&HashMap>>>, select_segments: bool, select_points: bool, // Here, "selection mode" represents touched or enclosed, not to be confused with editing modes @@ -2046,14 +2079,13 @@ impl ShapeState { network_interface: &NodeNetworkInterface, selection_shape: SelectionShape, path_overlay_mode: PathOverlayMode, - frontier_handles_info: &Option>>, + frontier_handles_info: Option<&HashMap>>>, select_segments: bool, select_points: bool, // Represents if the box/lasso selection touches or encloses the targets (not to be confused with editing modes). selection_mode: SelectionMode, ) -> (HashMap>, HashMap>) { let selected_points = self.selected_points().cloned().collect::>(); - let selected_segments = selected_segments(network_interface, self); let mut points_inside: HashMap> = HashMap::new(); let mut segments_inside: HashMap> = HashMap::new(); @@ -2137,7 +2169,10 @@ impl ShapeState { }; if select && select_points { - let is_visible_handle = is_visible_point(id, &vector, path_overlay_mode, frontier_handles_info, selected_segments.clone(), &selected_points); + let frontier_handles_for_layer = frontier_handles_info.and_then(|frontier_handles| frontier_handles.get(&layer)); + let state = self.selected_shape_state.get(&layer).expect("Cannot find state for layer"); + let selected_segments_for_layer = selected_segments_for_layer(&vector, state); + let is_visible_handle = is_visible_point(id, &vector, path_overlay_mode, frontier_handles_for_layer, &selected_segments_for_layer, &selected_points); if is_visible_handle { points_inside.entry(layer).or_default().insert(id); diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index e0ad553c..28e7ab82 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -171,8 +171,8 @@ pub fn is_visible_point( manipulator_point_id: ManipulatorPointId, vector: &Vector, path_overlay_mode: PathOverlayMode, - frontier_handles_info: &Option>>, - selected_segments: Vec, + frontier_handles_for_layer: Option<&HashMap>>, + selected_segments: &[SegmentId], selected_points: &HashSet, ) -> bool { match manipulator_point_id { @@ -197,7 +197,7 @@ pub fn is_visible_point( warn!("No anchor for selected handle"); return false; }; - let Some(frontier_handles) = frontier_handles_info else { + let Some(frontier_handles) = frontier_handles_for_layer else { warn!("No frontier handles info provided"); return false; }; diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index bd3c4e6c..d9718da8 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -179,7 +179,7 @@ impl ArtboardToolData { let Some(movement) = &bounds.selected_edges else { return; }; - if self.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT { + if self.selected_artboard == Some(LayerNodeIdentifier::ROOT_PARENT) { log::error!("Selected artboard cannot be ROOT_PARENT"); return; } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 21c281a9..4d00263c 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -58,7 +58,7 @@ pub enum PathToolMessage { // Tool-specific messages BreakPath, - DeselectAllPoints, + DeselectAllSelected, Delete, DeleteAndBreakPath, DragStop { @@ -113,7 +113,7 @@ pub enum PathToolMessage { segment_editing_modifier: Key, }, RightClick, - SelectAllAnchors, + SelectAll, SelectedPointUpdated, SelectedPointXChanged { new_x: f64, @@ -401,12 +401,6 @@ impl<'a> MessageHandler> for Path self.send_layout(responses, LayoutTarget::ToolOptions); } }, - ToolMessage::Path(PathToolMessage::ClosePath) => { - responses.add(DocumentMessage::AddTransaction); - context.shape_editor.close_selected_path(context.document, responses); - responses.add(DocumentMessage::EndTransaction); - responses.add(OverlaysMessage::Draw); - } ToolMessage::Path(PathToolMessage::SwapSelectedHandles) => { if context.shape_editor.handle_with_pair_selected(&context.document.network_interface) { context.shape_editor.alternate_selected_handles(&context.document.network_interface); @@ -434,8 +428,8 @@ impl<'a> MessageHandler> for Path Delete, NudgeSelectedPoints, Enter, - SelectAllAnchors, - DeselectAllPoints, + SelectAll, + DeselectAllSelected, BreakPath, DeleteAndBreakPath, ClosePath, @@ -563,11 +557,11 @@ struct PathToolData { segment_editing_modifier: bool, multiple_toggle_pressed: bool, auto_panning: AutoPanning, - saved_points_before_anchor_select_toggle: Vec, + saved_points_before_anchor_select_toggle: HashMap>, select_anchor_toggled: bool, saved_selection_before_handle_drag: HashMap, HashSet)>, handle_drag_toggle: bool, - saved_points_before_anchor_convert_smooth_sharp: HashSet, + saved_points_before_anchor_convert_smooth_sharp: HashMap>, last_click_time: u64, dragging_state: DraggingState, angle: f64, @@ -584,7 +578,7 @@ struct PathToolData { molding_info: Option<(DVec2, DVec2)>, molding_segment: bool, temporary_adjacent_handles_while_molding: Option<[Option; 2]>, - frontier_handles_info: Option>>, + frontier_handles_info: Option>>>, adjacent_anchor_offset: Option, sliding_point_info: Option, started_drawing_from_inside: bool, @@ -599,7 +593,7 @@ struct PathToolData { } impl PathToolData { - fn save_points_before_anchor_toggle(&mut self, points: Vec) -> PathToolFsmState { + fn save_points_before_anchor_toggle(&mut self, points: HashMap>) -> PathToolFsmState { self.saved_points_before_anchor_select_toggle = points; PathToolFsmState::Dragging(self.dragging_state) } @@ -744,7 +738,7 @@ impl PathToolData { input.mouse.position, SELECTION_THRESHOLD, path_overlay_mode, - &self.frontier_handles_info, + self.frontier_handles_info.as_ref(), point_editing_mode, ) { responses.add(DocumentMessage::StartTransaction); @@ -762,7 +756,7 @@ impl PathToolData { SELECTION_THRESHOLD, extend_selection, path_overlay_mode, - &self.frontier_handles_info, + self.frontier_handles_info.as_ref(), ) { selection_info = updated_selection_info; } @@ -802,15 +796,15 @@ impl PathToolData { let manipulator_point_id = handles[0].to_manipulator_point(); shape_editor.deselect_all_points(); - shape_editor.select_points_by_manipulator_id(&vec![manipulator_point_id]); + shape_editor.select_point_by_layer_and_id(manipulator_point_id, layer); responses.add(PathToolMessage::SelectedPointUpdated); } } } - if let Some((Some(point), Some(vector))) = shape_editor + if let Some((Some(point), Some(vector), layer)) = shape_editor .find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) - .map(|(layer, point)| (point.as_anchor(), document.network_interface.compute_modified_vector(layer))) + .map(|(layer, point)| (point.as_anchor(), document.network_interface.compute_modified_vector(layer), layer)) { let handles = vector .all_connected(point) @@ -821,7 +815,7 @@ impl PathToolData { if drag_zero_handle && (handles.len() == 1 && !endpoint) { shape_editor.deselect_all_points(); - shape_editor.select_points_by_manipulator_id(&handles); + shape_editor.select_points_by_layer_and_id(&HashMap::from([(layer, handles)])); shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document); } } @@ -1201,7 +1195,7 @@ impl PathToolData { // Check if there is no point nearby // If the point mode is deactivated then don't override closest segment even if there is a closer point if shape_editor - .find_nearest_visible_point_indices(&document.network_interface, position, SELECTION_THRESHOLD, path_overlay_mode, &self.frontier_handles_info) + .find_nearest_visible_point_indices(&document.network_interface, position, SELECTION_THRESHOLD, path_overlay_mode, self.frontier_handles_info.as_ref()) .is_some() && point_editing_mode { @@ -1211,9 +1205,19 @@ impl PathToolData { else if let Some(closest_segment) = &mut self.segment { closest_segment.update_closest_point(document.metadata(), &document.network_interface, position); + let layer = closest_segment.layer(); + let segment_id = closest_segment.segment(); + if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) { self.segment = None; } + + // Check if that segment exists or it has been removed + if let Some(vector_data) = document.network_interface.compute_modified_vector(layer) + && !(vector_data.segment_domain.ids().iter().any(|segment| *segment == segment_id)) + { + self.segment = None; + } } // If not, check that if there is some closest segment or not else if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, position, SEGMENT_INSERTION_DISTANCE) { @@ -1468,7 +1472,8 @@ impl PathToolData { // Now change the selection to this handle shape_editor.deselect_all_points(); - shape_editor.select_points_by_manipulator_id(&vec![handle]); + shape_editor.select_point_by_layer_and_id(handle, layer); + responses.add(PathToolMessage::SelectionChanged); } } @@ -1691,25 +1696,31 @@ impl Fsm for PathToolFsmState { } PathOverlayMode::FrontierHandles => { let selected_segments = selected_segments(&document.network_interface, shape_editor); - let selected_points = shape_editor.selected_points(); - let selected_anchors = selected_points - .filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(*p) } else { None }) - .collect::>(); // Match the behavior of `PathOverlayMode::SelectedPointHandles` when only one point is selected if shape_editor.selected_points().count() == 1 { path_overlays(document, DrawHandles::SelectedAnchors(selected_segments), shape_editor, &mut overlay_context); } else { - let mut segment_endpoints: HashMap> = HashMap::new(); + let mut segment_endpoints_by_layer = HashMap::new(); for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { + let mut segment_endpoints: HashMap> = HashMap::new(); + let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue }; + let Some(state) = shape_editor.selected_shape_state.get_mut(&layer) else { continue }; + + let selected_points = state.selected_points(); + let selected_anchors = selected_points + .filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(p) } else { None }) + .collect::>(); + + let Some(focused_segments) = selected_segments.get(&layer) else { continue }; // The points which are part of only one segment will be rendered let mut selected_segments_by_point: HashMap> = HashMap::new(); for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() { - if selected_segments.contains(&segment_id) { + if focused_segments.contains(&segment_id) { selected_segments_by_point.entry(start).or_default().push(segment_id); selected_segments_by_point.entry(end).or_default().push(segment_id); } @@ -1725,13 +1736,15 @@ impl Fsm for PathToolFsmState { segment_endpoints.entry(attached_segments[1]).or_default().push(point); } } + + segment_endpoints_by_layer.insert(layer, segment_endpoints); } // Caching segment endpoints for use in point selection logic - tool_data.frontier_handles_info = Some(segment_endpoints.clone()); + tool_data.frontier_handles_info = Some(segment_endpoints_by_layer.clone()); // Now frontier anchors can be sent for rendering overlays - path_overlays(document, DrawHandles::FrontierHandles(segment_endpoints), shape_editor, &mut overlay_context); + path_overlays(document, DrawHandles::FrontierHandles(segment_endpoints_by_layer), shape_editor, &mut overlay_context); } } } @@ -1757,7 +1770,7 @@ impl Fsm for PathToolFsmState { input.mouse.position, SELECTION_THRESHOLD, tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), ); let Some((layer, manipulator_point_id)) = nearest_visible_point_indices else { return }; @@ -1869,7 +1882,7 @@ impl Fsm for PathToolFsmState { &document.network_interface, SelectionShape::Box(bbox), tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), select_segments, select_points, selection_mode, @@ -1879,7 +1892,7 @@ impl Fsm for PathToolFsmState { &document.network_interface, SelectionShape::Lasso(&tool_data.lasso_polygon), tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), select_segments, select_points, selection_mode, @@ -2096,13 +2109,19 @@ impl Fsm for PathToolFsmState { if initial_press { responses.add(PathToolMessage::SelectedPointUpdated); tool_data.select_anchor_toggled = true; - tool_data.save_points_before_anchor_toggle(shape_editor.selected_points().cloned().collect()); - shape_editor.select_handles_and_anchor_connected_to_current_handle(&document.network_interface); + + let mut points_to_save = HashMap::new(); + for (layer, state) in &shape_editor.selected_shape_state { + points_to_save.insert(*layer, state.selected_points().collect::>()); + } + tool_data.save_points_before_anchor_toggle(points_to_save); + + shape_editor.select_anchor_and_connected_handles(&document.network_interface); } else if released_from_toggle { responses.add(PathToolMessage::SelectedPointUpdated); tool_data.select_anchor_toggled = false; shape_editor.deselect_all_points(); - shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_anchor_select_toggle); + shape_editor.select_points_by_layer_and_id(&tool_data.saved_points_before_anchor_select_toggle); tool_data.remove_saved_points(); } @@ -2288,7 +2307,7 @@ impl Fsm for PathToolFsmState { SelectionShape::Box(bbox), selection_change, tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), tool_options.path_editing_mode.segment_editing_mode, tool_options.path_editing_mode.point_editing_mode, selection_mode, @@ -2299,7 +2318,7 @@ impl Fsm for PathToolFsmState { SelectionShape::Lasso(&tool_data.lasso_polygon), selection_change, tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), tool_options.path_editing_mode.segment_editing_mode, tool_options.path_editing_mode.point_editing_mode, selection_mode, @@ -2385,7 +2404,7 @@ impl Fsm for PathToolFsmState { SelectionShape::Box(bbox), select_kind, tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), tool_options.path_editing_mode.segment_editing_mode, tool_options.path_editing_mode.point_editing_mode, selection_mode, @@ -2396,7 +2415,7 @@ impl Fsm for PathToolFsmState { SelectionShape::Lasso(&tool_data.lasso_polygon), select_kind, tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), tool_options.path_editing_mode.segment_editing_mode, tool_options.path_editing_mode.point_editing_mode, selection_mode, @@ -2420,7 +2439,7 @@ impl Fsm for PathToolFsmState { input.mouse.position, SELECTION_THRESHOLD, tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), ); let nearest_segment = tool_data.segment.clone(); @@ -2473,7 +2492,11 @@ impl Fsm for PathToolFsmState { } if !drag_occurred && !extend_selection && clicked_selected { if tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() { - tool_data.saved_points_before_anchor_convert_smooth_sharp = shape_editor.selected_points().copied().collect::>(); + let mut saved_points = HashMap::new(); + for (layer, state) in &shape_editor.selected_shape_state { + saved_points.insert(*layer, state.selected_points().collect::>()); + } + tool_data.saved_points_before_anchor_convert_smooth_sharp = saved_points; } shape_editor.deselect_all_points(); @@ -2567,7 +2590,7 @@ impl Fsm for PathToolFsmState { if tool_data.select_anchor_toggled { shape_editor.deselect_all_points(); - shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_anchor_select_toggle); + shape_editor.select_points_by_layer_and_id(&tool_data.saved_points_before_anchor_select_toggle); tool_data.remove_saved_points(); tool_data.select_anchor_toggled = false; } @@ -2612,6 +2635,15 @@ impl Fsm for PathToolFsmState { shape_editor.delete_point_and_break_path(document, responses); PathToolFsmState::Ready } + (_, PathToolMessage::ClosePath) => { + responses.add(DocumentMessage::AddTransaction); + shape_editor.close_selected_path(document, responses, tool_action_data.preferences.vector_meshes); + responses.add(DocumentMessage::EndTransaction); + + responses.add(OverlaysMessage::Draw); + + self + } (_, PathToolMessage::StartSlidingPoint) => { responses.add(DocumentMessage::StartTransaction); if tool_data.start_sliding_point(shape_editor, document) { @@ -2928,8 +2960,8 @@ impl Fsm for PathToolFsmState { if !tool_data.double_click_handled && tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD { responses.add(DocumentMessage::StartTransaction); - shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_anchor_convert_smooth_sharp.iter().copied().collect::>()); - shape_editor.flip_smooth_sharp(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE, responses); + shape_editor.select_points_by_layer_and_id(&tool_data.saved_points_before_anchor_convert_smooth_sharp); + shape_editor.flip_smooth_sharp(&document.network_interface, responses); tool_data.saved_points_before_anchor_convert_smooth_sharp.clear(); responses.add(DocumentMessage::EndTransaction); @@ -3026,14 +3058,28 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } - (_, PathToolMessage::SelectAllAnchors) => { + (_, PathToolMessage::SelectAll) => { shape_editor.select_all_anchors_in_selected_layers(document); + + let point_editing_mode = tool_options.path_editing_mode.point_editing_mode; + let segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode; + + if point_editing_mode { + shape_editor.select_all_anchors_in_selected_layers(document); + } + if segment_editing_mode { + shape_editor.select_all_segments_in_selected_layers(document); + } + responses.add(OverlaysMessage::Draw); PathToolFsmState::Ready } - (_, PathToolMessage::DeselectAllPoints) => { + (_, PathToolMessage::DeselectAllSelected) => { shape_editor.deselect_all_points(); + shape_editor.deselect_all_segments(); + responses.add(OverlaysMessage::Draw); + PathToolFsmState::Ready } (_, PathToolMessage::SelectedPointXChanged { new_x }) => { @@ -3357,7 +3403,7 @@ fn update_dynamic_hints( position, SELECTION_THRESHOLD, tool_options.path_overlay_mode, - &tool_data.frontier_handles_info, + tool_data.frontier_handles_info.as_ref(), ) .is_some(); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 547f638f..06bac2ac 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -1667,17 +1667,21 @@ impl Fsm for PenToolFsmState { path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); } PenOverlayMode::FrontierHandles => { - if let Some(latest_segment) = tool_data.prior_segment { - path_overlays(document, DrawHandles::SelectedAnchors(vec![latest_segment]), shape_editor, &mut overlay_context); - } - // If a vector mesh then there can be more than one prior segments - else if let Some(segments) = tool_data.prior_segments.clone() { - if preferences.vector_meshes { - path_overlays(document, DrawHandles::SelectedAnchors(segments), shape_editor, &mut overlay_context); + if let Some(layer) = tool_data.current_layer { + if let Some(latest_segment) = tool_data.prior_segment { + let selected_anchors_data = HashMap::from([(layer, vec![latest_segment])]); + path_overlays(document, DrawHandles::SelectedAnchors(selected_anchors_data), shape_editor, &mut overlay_context); } - } else { - path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context); - }; + // If a vector mesh then there can be more than one prior segments + else if let Some(segments) = tool_data.prior_segments.clone() { + if preferences.vector_meshes { + let selected_anchors_data = HashMap::from([(layer, segments)]); + path_overlays(document, DrawHandles::SelectedAnchors(selected_anchors_data), shape_editor, &mut overlay_context); + } + } else { + path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context); + }; + } } }