From 97567bd31f7684b4688b472a31f2ed7618bab6ce Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Tue, 4 Feb 2025 22:43:45 +0530 Subject: [PATCH 1/7] Path tool only show frontier overlays --- .../document/overlays/utility_functions.rs | 39 ++++++++++--------- .../messages/tool/tool_messages/path_tool.rs | 2 +- .../messages/tool/tool_messages/pen_tool.rs | 6 +-- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 1556699ed9..ab92ba9552 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -23,7 +23,7 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d { create_context().expect("Failed to get canvas context") } -pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) { +pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, draw_handles: bool) { 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; @@ -34,26 +34,29 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); overlay_context.outline_vector(&vector_data, transform); - for (segment_id, bezier, _start, _end) in vector_data.segment_bezier_iter() { - let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); - let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; - match bezier.handles { - bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { - overlay_context.line(handle, bezier.start, None); - overlay_context.line(handle, bezier.end, None); - overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); - } - bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { - if not_under_anchor(handle_start, bezier.start) { - overlay_context.line(handle_start, bezier.start, None); - overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + //TODO: Here define which handles to show and which handles to not, for path tool selection + if draw_handles { + for (segment_id, bezier, _start, _end) in vector_data.segment_bezier_iter() { + let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); + let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; + match bezier.handles { + bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { + overlay_context.line(handle, bezier.start, None); + overlay_context.line(handle, bezier.end, None); + overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); } - if not_under_anchor(handle_end, bezier.end) { - overlay_context.line(handle_end, bezier.end, None); - overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + if not_under_anchor(handle_start, bezier.start) { + overlay_context.line(handle_start, bezier.start, None); + overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + } + if not_under_anchor(handle_end, bezier.end) { + overlay_context.line(handle_end, bezier.end, None); + overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + } } + _ => {} } - _ => {} } } for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index e91ade16d9..fb8d963d17 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -703,7 +703,7 @@ impl Fsm for PathToolFsmState { self } (_, PathToolMessage::Overlays(mut overlay_context)) => { - path_overlays(document, shape_editor, &mut overlay_context); + path_overlays(document, shape_editor, &mut overlay_context, true); match self { Self::Drawing { selection_shape } => { diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 689eac307a..4d1a9b923d 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -641,7 +641,7 @@ impl Fsm for PenToolFsmState { self } (PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => { - path_overlays(document, shape_editor, &mut overlay_context); + path_overlays(document, shape_editor, &mut overlay_context, true); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); self } @@ -680,7 +680,7 @@ impl Fsm for PenToolFsmState { // Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out) overlay_context.line(next_anchor, handle_end, None); - path_overlays(document, shape_editor, &mut overlay_context); + path_overlays(document, shape_editor, &mut overlay_context, false); if self == PenToolFsmState::DraggingHandle && valid(next_anchor, handle_end) { // Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out) @@ -693,7 +693,7 @@ impl Fsm for PenToolFsmState { } } else { // Draw the whole path and its manipulators when the user is clicking-and-dragging out from the most recently placed anchor to set its outgoing handle, during which it would otherwise not have its overlays drawn - path_overlays(document, shape_editor, &mut overlay_context); + path_overlays(document, shape_editor, &mut overlay_context, false); } if self == PenToolFsmState::DraggingHandle && valid(next_anchor, next_handle_start) { From 3d3cea2e3512c4475c661411f30d63b1088cfba8 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Fri, 7 Feb 2025 13:23:31 +0530 Subject: [PATCH 2/7] Implemented all modes for pen and path tool --- .../document/overlays/utility_functions.rs | 142 +++++++++++++++--- .../document/overlays/utility_types.rs | 11 +- .../messages/tool/tool_messages/path_tool.rs | 105 +++++++++++-- .../messages/tool/tool_messages/pen_tool.rs | 47 +++++- 4 files changed, 267 insertions(+), 38 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index ab92ba9552..071bb2d6e7 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -1,11 +1,13 @@ -use super::utility_types::OverlayContext; +use super::utility_types::{DrawHandles, 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, PreferencesMessageHandler}; use graphene_core::vector::ManipulatorPointId; -use glam::DVec2; +use bezier_rs::Bezier; +use glam::{DAffine2, DVec2}; +use graphene_std::vector::{PointId, SegmentId}; use wasm_bindgen::JsCast; pub fn overlay_canvas_element() -> Option { @@ -23,7 +25,70 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d { create_context().expect("Failed to get canvas context") } -pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, draw_handles: bool) { +pub fn get_selected_segments(document: &DocumentMessageHandler, shape_editor: &mut ShapeState) -> Vec { + let selected_points = shape_editor.selected_points(); + let selected_anchors: Vec = selected_points + .filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(*p) } else { None }) + .collect(); + //Collect the segments whose handles are selected + let mut selected_segments: Vec = shape_editor + .selected_points() + .filter_map(|point_id| { + if let ManipulatorPointId::EndHandle(segment_id) = point_id { + Some(*segment_id) + } else if let ManipulatorPointId::PrimaryHandle(segment_id) = point_id { + Some(*segment_id) + } else { + None + } + }) + .collect(); + //TODO: Currently if there are two duplicate layers, both of their segments get overlays + // Segments of which the selected anchors are a part of + 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; + }; + for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() { + if selected_anchors.contains(&start) || selected_anchors.contains(&end) { + selected_segments.push(segment_id); + } + } + } + selected_segments +} + +fn overlay_bezier_handles( + segment_id: SegmentId, + bezier: Bezier, + transform: DAffine2, + overlay_context: &mut OverlayContext, + selected: Option<&SelectedLayerState>, + is_selected: impl Fn(Option<&SelectedLayerState>, ManipulatorPointId) -> bool, +) { + let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); + let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; + match bezier.handles { + bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { + overlay_context.line(handle, bezier.start, None); + overlay_context.line(handle, bezier.end, None); + overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + } + bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + if not_under_anchor(handle_start, bezier.start) { + overlay_context.line(handle_start, bezier.start, None); + overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + } + if not_under_anchor(handle_end, bezier.end) { + overlay_context.line(handle_end, bezier.end, None); + overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + } + } + _ => {} + } +} + +pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, draw_handles: DrawHandles) { 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; @@ -35,30 +100,59 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape overlay_context.outline_vector(&vector_data, transform); //TODO: Here define which handles to show and which handles to not, for path tool selection - if draw_handles { - for (segment_id, bezier, _start, _end) in vector_data.segment_bezier_iter() { - let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); - let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; - match bezier.handles { - bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { - overlay_context.line(handle, bezier.start, None); - overlay_context.line(handle, bezier.end, None); - overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); - } - bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { - if not_under_anchor(handle_start, bezier.start) { - overlay_context.line(handle_start, bezier.start, None); - overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); - } - if not_under_anchor(handle_end, bezier.end) { - overlay_context.line(handle_end, bezier.end, None); - overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + match draw_handles { + DrawHandles::All => { + vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| { + overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); + }); + } + DrawHandles::SelectedAnchors(ref selected_segments) => { + vector_data + .segment_bezier_iter() + .filter(|(segment_id, ..)| selected_segments.contains(segment_id)) + .for_each(|(segment_id, bezier, _start, _end)| { + overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); + }); + } + + DrawHandles::FrontierHandles(ref segment_endpoints) => { + vector_data + .segment_bezier_iter() + .filter(|(segment_id, ..)| segment_endpoints.contains_key(&segment_id)) + .for_each(|(segment_id, bezier, start, end)| { + if segment_endpoints.get(&segment_id).unwrap().len() == 1 { + let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0]; + let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); + let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; + match bezier.handles { + bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { + if start == point_to_render { + overlay_context.line(handle, bezier.start, None); + } else { + overlay_context.line(handle, bezier.end, None); + } + overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + } + bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) { + overlay_context.line(handle_start, bezier.start, None); + overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + } + if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) { + overlay_context.line(handle_end, bezier.end, None); + overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + } + } + _ => {} + } + } else { + overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); } - } - _ => {} - } + }); } + DrawHandles::None => {} } + for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(selected, ManipulatorPointId::Anchor(id)), None); } diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 7c1f7bf12d..58e9b87d8f 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use super::utility_functions::overlay_canvas_context; use crate::consts::{ COLOR_OVERLAY_BLUE, COLOR_OVERLAY_TRANSPARENT, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, @@ -6,7 +8,7 @@ use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; use graphene_core::renderer::Quad; -use graphene_std::vector::{PointId, VectorData}; +use graphene_std::vector::{PointId, SegmentId, VectorData}; use core::borrow::Borrow; use core::f64::consts::TAU; @@ -482,3 +484,10 @@ pub enum Pivot { Middle, End, } + +pub enum DrawHandles { + All, + SelectedAnchors(Vec), + FrontierHandles(HashMap>), + None, +} diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index fb8d963d17..d99f566d9b 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -3,8 +3,8 @@ use super::tool_prelude::*; use crate::consts::{ COLOR_OVERLAY_BLUE, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, }; -use crate::messages::portfolio::document::overlays::utility_functions::path_overlays; -use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::overlays::utility_functions::{get_selected_segments, path_overlays}; +use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::preferences::SelectionMode; @@ -15,8 +15,8 @@ use crate::messages::tool::common_functionality::shape_editor::{ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; use graphene_core::renderer::Quad; -use graphene_core::vector::ManipulatorPointId; -use graphene_std::vector::NoHashBuilder; +use graphene_core::vector::{ManipulatorPointId, PointId}; +use graphene_std::vector::{NoHashBuilder, SegmentId}; use std::vec; @@ -24,6 +24,19 @@ use std::vec; pub struct PathTool { fsm_state: PathToolFsmState, tool_data: PathToolData, + options: PathToolOptions, +} + +pub struct PathToolOptions { + path_overlay_mode: PathOverlayMode, +} + +impl Default for PathToolOptions { + fn default() -> Self { + Self { + path_overlay_mode: PathOverlayMode::SelectedPointHandles, + } + } } #[impl_message(Message, ToolMessage, Path)] @@ -89,6 +102,19 @@ pub enum PathToolMessage { new_y: f64, }, SwapSelectedHandles, + UpdateOptions(PathOptionsUpdate), +} + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum PathOverlayMode { + AllHandles = 0, + SelectedPointHandles = 1, + FrontierHandles = 2, +} + +#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum PathOptionsUpdate { + OverlayModeType(PathOverlayMode), } impl ToolMetadata for PathTool { @@ -103,6 +129,21 @@ impl ToolMetadata for PathTool { } } +fn create_path_overlay_mode_widget(path_overlay_mode: PathOverlayMode) -> WidgetHolder { + let entries = vec![ + RadioEntryData::new("1") + .label("1") + .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles)).into()), + RadioEntryData::new("2") + .label("2") + .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles)).into()), + RadioEntryData::new("3") + .label("3") + .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles)).into()), + ]; + RadioInput::new(entries).selected_index(Some(path_overlay_mode as u32)).widget_holder() +} + impl LayoutHolder for PathTool { fn layout(&self) -> Layout { let coordinates = self.tool_data.selection_status.as_one().as_ref().map(|point| point.coordinates); @@ -175,10 +216,12 @@ impl LayoutHolder for PathTool { x_location, related_seperator.clone(), y_location, - unrelated_seperator, + unrelated_seperator.clone(), colinear_handle_checkbox, related_seperator, colinear_handles_label, + unrelated_seperator.clone(), + create_path_overlay_mode_widget(self.options.path_overlay_mode), ], }])) } @@ -189,6 +232,12 @@ impl<'a> MessageHandler> for PathToo let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated); match message { + ToolMessage::Path(PathToolMessage::UpdateOptions(action)) => match action { + PathOptionsUpdate::OverlayModeType(overlay_mode_type) => { + self.options.path_overlay_mode = overlay_mode_type; + responses.add(OverlaysMessage::Draw); + } + }, ToolMessage::Path(PathToolMessage::ClosePath) => { responses.add(DocumentMessage::AddTransaction); tool_data.shape_editor.close_selected_path(tool_data.document, responses); @@ -204,7 +253,7 @@ impl<'a> MessageHandler> for PathToo } } _ => { - self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); + self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true); } } @@ -686,9 +735,9 @@ impl PathToolData { impl Fsm for PathToolFsmState { type ToolData = PathToolData; - type ToolOptions = (); + type ToolOptions = PathToolOptions; - fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque) -> Self { + fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque) -> Self { let ToolActionHandlerData { document, input, shape_editor, .. } = tool_action_data; let ToolMessage::Path(event) = event else { return self }; match (self, event) { @@ -703,7 +752,45 @@ impl Fsm for PathToolFsmState { self } (_, PathToolMessage::Overlays(mut overlay_context)) => { - path_overlays(document, shape_editor, &mut overlay_context, true); + //TODO: find the segment ids of which the selected points are a part of + + match tool_options.path_overlay_mode { + PathOverlayMode::AllHandles => { + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); + } + + PathOverlayMode::SelectedPointHandles => { + let selected_segments = get_selected_segments(document, shape_editor); + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::SelectedAnchors(selected_segments)); + } + + PathOverlayMode::FrontierHandles => { + let selected_segments = get_selected_segments(document, shape_editor); + let mut segment_endpoints: HashMap> = HashMap::new(); + + 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; + }; + + //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_data.segment_bezier_iter() { + if selected_segments.contains(&segment_id) { + selected_segments_by_point.entry(start).or_insert_with(Vec::new).push(segment_id); + selected_segments_by_point.entry(end).or_insert_with(Vec::new).push(segment_id); + } + } + for (point, attached_segments) in selected_segments_by_point { + if attached_segments.len() == 1 { + segment_endpoints.entry(attached_segments[0]).or_insert_with(Vec::new).push(point); + } + } + } + //Now frontier anchors can be sent for rendering overlays + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::FrontierHandles(segment_endpoints)); + } + } match self { Self::Drawing { selection_shape } => { diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 4d1a9b923d..c3432ee3c9 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -2,7 +2,7 @@ use super::tool_prelude::*; use crate::consts::{DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE}; use crate::messages::portfolio::document::node_graph::document_node_definitions::{self, resolve_document_node_type}; use crate::messages::portfolio::document::overlays::utility_functions::path_overlays; -use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; +use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; @@ -28,6 +28,7 @@ pub struct PenOptions { line_weight: f64, fill: ToolColorOptions, stroke: ToolColorOptions, + pen_overlay_mode: PenOverlayMode, } impl Default for PenOptions { @@ -36,6 +37,7 @@ impl Default for PenOptions { line_weight: DEFAULT_STROKE_WIDTH, fill: ToolColorOptions::new_secondary(), stroke: ToolColorOptions::new_primary(), + pen_overlay_mode: PenOverlayMode::FrontierHandles, } } } @@ -76,6 +78,12 @@ enum PenToolFsmState { GRSHandle, } +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum PenOverlayMode { + AllHandles = 0, + FrontierHandles = 1, +} + #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] pub enum PenOptionsUpdate { FillColor(Option), @@ -84,6 +92,7 @@ pub enum PenOptionsUpdate { StrokeColor(Option), StrokeColorType(ToolColorType), WorkingColors(Option, Option), + OverlayModeType(PenOverlayMode), } impl ToolMetadata for PenTool { @@ -108,6 +117,18 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .widget_holder() } +fn create_path_overlay_mode_widget(path_overlay_mode: PenOverlayMode) -> WidgetHolder { + let entries = vec![ + RadioEntryData::new("1") + .label("1") + .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles)).into()), + RadioEntryData::new("2") + .label("2") + .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles)).into()), + ]; + RadioInput::new(entries).selected_index(Some(path_overlay_mode as u32)).widget_holder() +} + impl LayoutHolder for PenTool { fn layout(&self) -> Layout { let mut widgets = self.options.fill.create_widgets( @@ -129,6 +150,8 @@ impl LayoutHolder for PenTool { )); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(create_weight_widget(self.options.line_weight)); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + widgets.push(create_path_overlay_mode_widget(self.options.pen_overlay_mode)); Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) } @@ -141,6 +164,10 @@ impl<'a> MessageHandler> for PenTool return; }; match action { + PenOptionsUpdate::OverlayModeType(overlay_mode_type) => { + self.options.pen_overlay_mode = overlay_mode_type; + responses.add(OverlaysMessage::Draw); + } PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight, PenOptionsUpdate::FillColor(color) => { self.options.fill.custom_color = color; @@ -641,7 +668,7 @@ impl Fsm for PenToolFsmState { self } (PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => { - path_overlays(document, shape_editor, &mut overlay_context, true); + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); self } @@ -680,7 +707,19 @@ impl Fsm for PenToolFsmState { // Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out) overlay_context.line(next_anchor, handle_end, None); - path_overlays(document, shape_editor, &mut overlay_context, false); + match tool_options.pen_overlay_mode { + PenOverlayMode::AllHandles => { + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); + } + PenOverlayMode::FrontierHandles => { + //Find the last segment id to be drawn handles of + if let Some(latest_segment) = tool_data.latest_point().unwrap().in_segment { + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::SelectedAnchors(vec![latest_segment])); + } else { + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::None); + }; + } + } if self == PenToolFsmState::DraggingHandle && valid(next_anchor, handle_end) { // Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out) @@ -693,7 +732,7 @@ impl Fsm for PenToolFsmState { } } else { // Draw the whole path and its manipulators when the user is clicking-and-dragging out from the most recently placed anchor to set its outgoing handle, during which it would otherwise not have its overlays drawn - path_overlays(document, shape_editor, &mut overlay_context, false); + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); } if self == PenToolFsmState::DraggingHandle && valid(next_anchor, next_handle_start) { From 5fae5208af81de3b6c7434ab36444233fa7bec47 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Fri, 7 Feb 2025 13:46:35 +0530 Subject: [PATCH 3/7] Fixed formatting issue --- editor/src/messages/tool/tool_messages/pen_tool.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 85c28fa062..75b4809f3f 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -860,12 +860,12 @@ impl Fsm for PenToolFsmState { // Draw the line between the currently-being-placed anchor and its incoming handle (opposite the one currently being dragged out) overlay_context.line(next_anchor, handle_end, None); - - if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle { + + if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.lock_angle { // Draw the line between the currently-being-placed anchor and last-placed point (Lock angle bent overlays) overlay_context.dashed_line(anchor_start, next_anchor, None, Some(4.), Some(4.), Some(0.5)); } - + match tool_options.pen_overlay_mode { PenOverlayMode::AllHandles => { path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); From 818b0afef190dbafff1cc4f9d7f15ab5f139ba55 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Sun, 9 Feb 2025 14:34:50 +0530 Subject: [PATCH 4/7] Changes in selection behaviour of handles --- .../document/overlays/utility_functions.rs | 73 +++++++++++------- .../messages/tool/tool_messages/path_tool.rs | 74 ++++++++++++++----- node-graph/gcore/src/vector/vector_data.rs | 20 +++++ 3 files changed, 122 insertions(+), 45 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 071bb2d6e7..6003ba30ee 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -43,8 +43,9 @@ pub fn get_selected_segments(document: &DocumentMessageHandler, shape_editor: &m } }) .collect(); + //TODO: Currently if there are two duplicate layers, both of their segments get overlays - // Segments of which the selected anchors are a part of + // Adding segments which are are connected to selected anchors 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; @@ -88,6 +89,43 @@ fn overlay_bezier_handles( } } +pub fn overlay_bezier_handle_specific_point( + segment_id: SegmentId, + bezier: Bezier, + start: PointId, + end: PointId, + transform: DAffine2, + overlay_context: &mut OverlayContext, + selected: Option<&SelectedLayerState>, + is_selected: impl Fn(Option<&SelectedLayerState>, ManipulatorPointId) -> bool, + point_to_render: PointId, +) { + let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); + let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; + match bezier.handles { + bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { + if start == point_to_render { + //what is point of doing this, what have to be done in this? + overlay_context.line(handle, bezier.start, None); + } else { + overlay_context.line(handle, bezier.end, None); + } + overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + } + bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) { + overlay_context.line(handle_start, bezier.start, None); + overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + } + if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) { + overlay_context.line(handle_end, bezier.end, None); + overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + } + } + _ => {} + } +} + pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, draw_handles: DrawHandles) { 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 { @@ -99,7 +137,8 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); overlay_context.outline_vector(&vector_data, transform); - //TODO: Here define which handles to show and which handles to not, for path tool selection + let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.get_adjacent_segment(point_id)).collect(); + match draw_handles { DrawHandles::All => { vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| { @@ -113,6 +152,12 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape .for_each(|(segment_id, bezier, _start, _end)| { overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); }); + + for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() { + if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) { + overlay_bezier_handle_specific_point(segment_id, bezier, start, end, transform, overlay_context, selected, is_selected, *corresponding_anchor); + } + } } DrawHandles::FrontierHandles(ref segment_endpoints) => { @@ -122,29 +167,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape .for_each(|(segment_id, bezier, start, end)| { if segment_endpoints.get(&segment_id).unwrap().len() == 1 { let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0]; - let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); - let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; - match bezier.handles { - bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { - if start == point_to_render { - overlay_context.line(handle, bezier.start, None); - } else { - overlay_context.line(handle, bezier.end, None); - } - overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); - } - bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { - if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) { - overlay_context.line(handle_start, bezier.start, None); - overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); - } - if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) { - overlay_context.line(handle_end, bezier.end, None); - overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); - } - } - _ => {} - } + overlay_bezier_handle_specific_point(segment_id, bezier, start, end, transform, overlay_context, selected, is_selected, point_to_render); } else { overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 206192be7f..3f2f0f7539 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -370,6 +370,8 @@ struct PathToolData { auto_panning: AutoPanning, saved_points_before_anchor_select_toggle: Vec, select_anchor_toggled: bool, + saved_points_before_handle_drag: Vec, + handle_drag_toggle: bool, dragging_state: DraggingState, current_selected_handle_id: Option, angle: f64, @@ -473,18 +475,38 @@ impl PathToolData { extend_selection: bool, direct_insert_without_sliding: bool, lasso_select: bool, + tool_options: &PathToolOptions, ) -> PathToolFsmState { self.double_click_handled = false; self.opposing_handle_lengths = None; self.drag_start_pos = input.mouse.position; + let old_selection: Vec = shape_editor.selected_points().cloned().collect(); + // Select the first point within the threshold (in pixels) if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, extend_selection) { responses.add(DocumentMessage::StartTransaction); if let Some(selected_points) = selected_points { self.drag_start_pos = input.mouse.position; + + //While in mode 2 or 3, if selected points contain only handles and there was some selection before then it stores it and restore on release + if !matches!(tool_options.path_overlay_mode, PathOverlayMode::AllHandles) { + let mut dragging_only_handles = true; + for point in &selected_points.points { + if matches!(point.point_id, ManipulatorPointId::Anchor(_)) { + dragging_only_handles = false; + break; + } + } + + if dragging_only_handles && !self.handle_drag_toggle && old_selection.len() > 0 { + self.saved_points_before_handle_drag = old_selection; + self.handle_drag_toggle = true; + } + } + self.start_dragging_point(selected_points, input, document, shape_editor); responses.add(OverlaysMessage::Draw); } @@ -767,29 +789,34 @@ impl Fsm for PathToolFsmState { PathOverlayMode::FrontierHandles => { let selected_segments = get_selected_segments(document, shape_editor); - let mut segment_endpoints: HashMap> = HashMap::new(); - - 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; - }; - - //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_data.segment_bezier_iter() { - if selected_segments.contains(&segment_id) { - selected_segments_by_point.entry(start).or_insert_with(Vec::new).push(segment_id); - selected_segments_by_point.entry(end).or_insert_with(Vec::new).push(segment_id); + //Behaviour like Selectedpointhandles when only one point is selected + if shape_editor.selected_points().count() == 1 { + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::SelectedAnchors(selected_segments)); + } else { + let mut segment_endpoints: HashMap> = HashMap::new(); + + 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; + }; + + //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_data.segment_bezier_iter() { + if selected_segments.contains(&segment_id) { + selected_segments_by_point.entry(start).or_insert_with(Vec::new).push(segment_id); + selected_segments_by_point.entry(end).or_insert_with(Vec::new).push(segment_id); + } } - } - for (point, attached_segments) in selected_segments_by_point { - if attached_segments.len() == 1 { - segment_endpoints.entry(attached_segments[0]).or_insert_with(Vec::new).push(point); + for (point, attached_segments) in selected_segments_by_point { + if attached_segments.len() == 1 { + segment_endpoints.entry(attached_segments[0]).or_insert_with(Vec::new).push(point); + } } } + //Now frontier anchors can be sent for rendering overlays + path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::FrontierHandles(segment_endpoints)); } - //Now frontier anchors can be sent for rendering overlays - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::FrontierHandles(segment_endpoints)); } } @@ -873,7 +900,7 @@ impl Fsm for PathToolFsmState { tool_data.selection_mode = None; tool_data.lasso_polygon.clear(); - tool_data.mouse_down(shape_editor, document, input, responses, extend_selection, direct_insert_without_sliding, lasso_select) + tool_data.mouse_down(shape_editor, document, input, responses, extend_selection, direct_insert_without_sliding, lasso_select, tool_options) } ( PathToolFsmState::Drawing { selection_shape }, @@ -1114,6 +1141,13 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } (_, PathToolMessage::DragStop { extend_selection, .. }) => { + if tool_data.handle_drag_toggle { + shape_editor.deselect_all_points(); + shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_handle_drag); + tool_data.saved_points_before_handle_drag.clear(); + tool_data.handle_drag_toggle = false; + } + 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); diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index bd5cf324eb..6543e1d581 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -293,6 +293,26 @@ impl VectorData { None } } + + pub fn get_adjacent_segment(&self, manipulator_id: &ManipulatorPointId) -> Option<(PointId, SegmentId)> { + match manipulator_id { + ManipulatorPointId::PrimaryHandle(segment_id) => { + // For start handle, find segments ending at our start point + let (start_point_id, _, _) = self.segment_points_from_id(*segment_id)?; + let start_index = self.point_domain.resolve_id(start_point_id)?; + + self.segment_domain.end_connected(start_index).find(|&id| id != *segment_id).map(|id| (start_point_id, id)) + } + ManipulatorPointId::EndHandle(segment_id) => { + // For end handle, find segments starting at our end point + let (_, end_point_id, _) = self.segment_points_from_id(*segment_id)?; + let end_index = self.point_domain.resolve_id(end_point_id)?; + + self.segment_domain.start_connected(end_index).find(|&id| id != *segment_id).map(|id| (end_point_id, id)) + } + ManipulatorPointId::Anchor(_) => None, + } + } } impl Default for VectorData { From 51bf873b589e62f9a7500e486f8aef46cc7ee173 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Sun, 9 Feb 2025 17:18:36 +0530 Subject: [PATCH 5/7] Selection toggle only on drag not click --- .../messages/tool/tool_messages/path_tool.rs | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 3f2f0f7539..a1cc2d8a3c 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -475,7 +475,6 @@ impl PathToolData { extend_selection: bool, direct_insert_without_sliding: bool, lasso_select: bool, - tool_options: &PathToolOptions, ) -> PathToolFsmState { self.double_click_handled = false; self.opposing_handle_lengths = None; @@ -491,21 +490,17 @@ impl PathToolData { if let Some(selected_points) = selected_points { self.drag_start_pos = input.mouse.position; - //While in mode 2 or 3, if selected points contain only handles and there was some selection before then it stores it and restore on release - if !matches!(tool_options.path_overlay_mode, PathOverlayMode::AllHandles) { - let mut dragging_only_handles = true; - for point in &selected_points.points { - if matches!(point.point_id, ManipulatorPointId::Anchor(_)) { - dragging_only_handles = false; - break; - } - } - - if dragging_only_handles && !self.handle_drag_toggle && old_selection.len() > 0 { - self.saved_points_before_handle_drag = old_selection; - self.handle_drag_toggle = true; + //If selected points contain only handles and there was some selection before then it stores it and restore on release + let mut dragging_only_handles = true; + for point in &selected_points.points { + if matches!(point.point_id, ManipulatorPointId::Anchor(_)) { + dragging_only_handles = false; + break; } } + if dragging_only_handles && !self.handle_drag_toggle && (old_selection.len() > 0) { + self.saved_points_before_handle_drag = old_selection; + } self.start_dragging_point(selected_points, input, document, shape_editor); responses.add(OverlaysMessage::Draw); @@ -900,7 +895,7 @@ impl Fsm for PathToolFsmState { tool_data.selection_mode = None; tool_data.lasso_polygon.clear(); - tool_data.mouse_down(shape_editor, document, input, responses, extend_selection, direct_insert_without_sliding, lasso_select, tool_options) + tool_data.mouse_down(shape_editor, document, input, responses, extend_selection, direct_insert_without_sliding, lasso_select) } ( PathToolFsmState::Drawing { selection_shape }, @@ -953,6 +948,21 @@ impl Fsm for PathToolFsmState { lock_angle, }, ) => { + let mut selected_only_handles = true; + + let selected_points = shape_editor.selected_points(); + + for point in selected_points { + if matches!(point, ManipulatorPointId::Anchor(_)) { + selected_only_handles = false; + break; + } + } + + if (tool_data.saved_points_before_handle_drag.len() > 0) && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) { + tool_data.handle_drag_toggle = true; + } + if tool_data.selection_status.is_none() { if let Some(layer) = document.click(input) { shape_editor.select_all_anchors_in_layer(document, layer); @@ -1141,7 +1151,7 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } (_, PathToolMessage::DragStop { extend_selection, .. }) => { - if tool_data.handle_drag_toggle { + if tool_data.handle_drag_toggle && !(tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD) { shape_editor.deselect_all_points(); shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_handle_drag); tool_data.saved_points_before_handle_drag.clear(); From 413e1706e7184d190ad7844ff672d330431a9365 Mon Sep 17 00:00:00 2001 From: Adesh Gupta Date: Sun, 9 Feb 2025 17:21:03 +0530 Subject: [PATCH 6/7] Changed comment --- .../messages/portfolio/document/overlays/utility_functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 6003ba30ee..7ce0171610 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -105,7 +105,7 @@ pub fn overlay_bezier_handle_specific_point( match bezier.handles { bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { if start == point_to_render { - //what is point of doing this, what have to be done in this? + //review this overlay_context.line(handle, bezier.start, None); } else { overlay_context.line(handle, bezier.end, None); From 2c6c13e239a40e238e9365e3f35d880a06328b3c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 11 Feb 2025 21:40:48 -0800 Subject: [PATCH 7/7] Code review --- .../document/overlays/utility_functions.rs | 114 ++++++++---------- .../document/overlays/utility_types.rs | 3 +- .../messages/tool/tool_messages/path_tool.rs | 95 +++++++-------- .../messages/tool/tool_messages/pen_tool.rs | 43 ++++--- node-graph/gcore/src/vector/vector_data.rs | 2 +- 5 files changed, 119 insertions(+), 138 deletions(-) diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 7ce0171610..d9af75f7a4 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -4,10 +4,10 @@ use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerSta use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler}; use graphene_core::vector::ManipulatorPointId; +use graphene_std::vector::{PointId, SegmentId}; -use bezier_rs::Bezier; +use bezier_rs::{Bezier, BezierHandles}; use glam::{DAffine2, DVec2}; -use graphene_std::vector::{PointId, SegmentId}; use wasm_bindgen::JsCast; pub fn overlay_canvas_element() -> Option { @@ -25,64 +25,54 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d { create_context().expect("Failed to get canvas context") } -pub fn get_selected_segments(document: &DocumentMessageHandler, shape_editor: &mut ShapeState) -> Vec { +pub fn selected_segments(document: &DocumentMessageHandler, shape_editor: &mut ShapeState) -> Vec { let selected_points = shape_editor.selected_points(); - let selected_anchors: Vec = selected_points + let selected_anchors = selected_points .filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(*p) } else { None }) - .collect(); - //Collect the segments whose handles are selected - let mut selected_segments: Vec = shape_editor + .collect::>(); + + // Collect the segments whose handles are selected + let mut selected_segments = shape_editor .selected_points() - .filter_map(|point_id| { - if let ManipulatorPointId::EndHandle(segment_id) = point_id { - Some(*segment_id) - } else if let ManipulatorPointId::PrimaryHandle(segment_id) = point_id { - Some(*segment_id) - } else { - None - } + .filter_map(|point_id| match point_id { + ManipulatorPointId::PrimaryHandle(segment_id) | ManipulatorPointId::EndHandle(segment_id) => Some(*segment_id), + ManipulatorPointId::Anchor(_) => None, }) - .collect(); + .collect::>(); - //TODO: Currently if there are two duplicate layers, both of their segments get overlays + // 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 document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) { - let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { - continue; - }; + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; + for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() { if selected_anchors.contains(&start) || selected_anchors.contains(&end) { selected_segments.push(segment_id); } } } + selected_segments } -fn overlay_bezier_handles( - segment_id: SegmentId, - bezier: Bezier, - transform: DAffine2, - overlay_context: &mut OverlayContext, - selected: Option<&SelectedLayerState>, - is_selected: impl Fn(Option<&SelectedLayerState>, ManipulatorPointId) -> bool, -) { +fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAffine2, is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext) { let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; + match bezier.handles { - bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { + BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { overlay_context.line(handle, bezier.start, None); overlay_context.line(handle, bezier.end, None); - overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } - bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + BezierHandles::Cubic { handle_start, handle_end } => { if not_under_anchor(handle_start, bezier.start) { overlay_context.line(handle_start, bezier.start, None); - overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } if not_under_anchor(handle_end, bezier.end) { overlay_context.line(handle_end, bezier.end, None); - overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None); } } _ => {} @@ -90,59 +80,54 @@ fn overlay_bezier_handles( } pub fn overlay_bezier_handle_specific_point( - segment_id: SegmentId, bezier: Bezier, - start: PointId, - end: PointId, + segment_id: SegmentId, + (start, end): (PointId, PointId), + point_to_render: PointId, transform: DAffine2, + is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext, - selected: Option<&SelectedLayerState>, - is_selected: impl Fn(Option<&SelectedLayerState>, ManipulatorPointId) -> bool, - point_to_render: PointId, ) { let bezier = bezier.apply_transformation(|point| transform.transform_point2(point)); let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; + match bezier.handles { - bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => { - if start == point_to_render { - //review this - overlay_context.line(handle, bezier.start, None); - } else { - overlay_context.line(handle, bezier.end, None); + BezierHandles::Quadratic { handle } => { + if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) { + let end = if start == point_to_render { bezier.start } else { bezier.end }; + overlay_context.line(handle, end, None); + overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } - overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); } - bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { + BezierHandles::Cubic { handle_start, handle_end } => { if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) { overlay_context.line(handle_start, bezier.start, None); - overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None); + overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None); } if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) { overlay_context.line(handle_end, bezier.end, None); - overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None); + overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None); } } _ => {} } } -pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext, draw_handles: DrawHandles) { +pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandles, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) { 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; - }; - //let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; let transform = document.metadata().transform_to_viewport(layer); - 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)); overlay_context.outline_vector(&vector_data, transform); - let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.get_adjacent_segment(point_id)).collect(); + let selected = shape_editor.selected_shape_state.get(&layer); + let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point)); + + let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect(); match draw_handles { DrawHandles::All => { vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| { - overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); + overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context); }); } DrawHandles::SelectedAnchors(ref selected_segments) => { @@ -150,26 +135,25 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape .segment_bezier_iter() .filter(|(segment_id, ..)| selected_segments.contains(segment_id)) .for_each(|(segment_id, bezier, _start, _end)| { - overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); + overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context); }); for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() { if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) { - overlay_bezier_handle_specific_point(segment_id, bezier, start, end, transform, overlay_context, selected, is_selected, *corresponding_anchor); + overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context); } } } - DrawHandles::FrontierHandles(ref segment_endpoints) => { vector_data .segment_bezier_iter() - .filter(|(segment_id, ..)| segment_endpoints.contains_key(&segment_id)) + .filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id)) .for_each(|(segment_id, bezier, start, end)| { if segment_endpoints.get(&segment_id).unwrap().len() == 1 { let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0]; - overlay_bezier_handle_specific_point(segment_id, bezier, start, end, transform, overlay_context, selected, is_selected, point_to_render); + overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context); } else { - overlay_bezier_handles(segment_id, bezier, transform, overlay_context, selected, is_selected); + overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context); } }); } @@ -177,7 +161,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape } for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) { - overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(selected, ManipulatorPointId::Anchor(id)), None); + overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None); } } } diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 10e7b3287e..a16eda48c4 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use super::utility_functions::overlay_canvas_context; use crate::consts::{ COLOR_OVERLAY_BLUE, COLOR_OVERLAY_TRANSPARENT, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, @@ -13,6 +11,7 @@ use graphene_std::vector::{PointId, SegmentId, VectorData}; use core::borrow::Borrow; use core::f64::consts::TAU; use glam::{DAffine2, DVec2}; +use std::collections::HashMap; use wasm_bindgen::JsValue; pub type OverlayProvider = fn(OverlayContext) -> Message; diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index a1cc2d8a3c..a87fccbfcb 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -3,7 +3,7 @@ use super::tool_prelude::*; use crate::consts::{ COLOR_OVERLAY_BLUE, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, }; -use crate::messages::portfolio::document::overlays::utility_functions::{get_selected_segments, path_overlays}; +use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments}; use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; @@ -27,18 +27,11 @@ pub struct PathTool { options: PathToolOptions, } +#[derive(Default)] pub struct PathToolOptions { path_overlay_mode: PathOverlayMode, } -impl Default for PathToolOptions { - fn default() -> Self { - Self { - path_overlay_mode: PathOverlayMode::SelectedPointHandles, - } - } -} - #[impl_message(Message, ToolMessage, Path)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] pub enum PathToolMessage { @@ -105,9 +98,10 @@ pub enum PathToolMessage { UpdateOptions(PathOptionsUpdate), } -#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize, specta::Type)] pub enum PathOverlayMode { AllHandles = 0, + #[default] SelectedPointHandles = 1, FrontierHandles = 2, } @@ -129,21 +123,6 @@ impl ToolMetadata for PathTool { } } -fn create_path_overlay_mode_widget(path_overlay_mode: PathOverlayMode) -> WidgetHolder { - let entries = vec![ - RadioEntryData::new("1") - .label("1") - .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles)).into()), - RadioEntryData::new("2") - .label("2") - .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles)).into()), - RadioEntryData::new("3") - .label("3") - .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles)).into()), - ]; - RadioInput::new(entries).selected_index(Some(path_overlay_mode as u32)).widget_holder() -} - impl LayoutHolder for PathTool { fn layout(&self) -> Layout { let coordinates = self.tool_data.selection_status.as_one().as_ref().map(|point| point.coordinates); @@ -211,6 +190,20 @@ impl LayoutHolder for PathTool { .tooltip(colinear_handles_tooltip) .widget_holder(); + let path_overlay_mode_widget = RadioInput::new(vec![ + RadioEntryData::new("1") + .label("1") + .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles)).into()), + RadioEntryData::new("2") + .label("2") + .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles)).into()), + RadioEntryData::new("3") + .label("3") + .on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles)).into()), + ]) + .selected_index(Some(self.options.path_overlay_mode as u32)) + .widget_holder(); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ x_location, @@ -220,8 +213,8 @@ impl LayoutHolder for PathTool { colinear_handle_checkbox, related_seperator, colinear_handles_label, - unrelated_seperator.clone(), - create_path_overlay_mode_widget(self.options.path_overlay_mode), + unrelated_seperator, + path_overlay_mode_widget, ], }])) } @@ -481,7 +474,7 @@ impl PathToolData { self.drag_start_pos = input.mouse.position; - let old_selection: Vec = shape_editor.selected_points().cloned().collect(); + let old_selection = shape_editor.selected_points().cloned().collect::>(); // Select the first point within the threshold (in pixels) if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, extend_selection) { @@ -490,7 +483,7 @@ impl PathToolData { if let Some(selected_points) = selected_points { self.drag_start_pos = input.mouse.position; - //If selected points contain only handles and there was some selection before then it stores it and restore on release + // If selected points contain only handles and there was some selection before, then it is stored and becomes restored upon release let mut dragging_only_handles = true; for point in &selected_points.points { if matches!(point.point_id, ManipulatorPointId::Anchor(_)) { @@ -498,7 +491,7 @@ impl PathToolData { break; } } - if dragging_only_handles && !self.handle_drag_toggle && (old_selection.len() > 0) { + if dragging_only_handles && !self.handle_drag_toggle && !old_selection.is_empty() { self.saved_points_before_handle_drag = old_selection; } @@ -770,47 +763,48 @@ impl Fsm for PathToolFsmState { self } (_, PathToolMessage::Overlays(mut overlay_context)) => { - //TODO: find the segment ids of which the selected points are a part of + // TODO: find the segment ids of which the selected points are a part of match tool_options.path_overlay_mode { PathOverlayMode::AllHandles => { - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); + path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); } - PathOverlayMode::SelectedPointHandles => { - let selected_segments = get_selected_segments(document, shape_editor); - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::SelectedAnchors(selected_segments)); - } + let selected_segments = selected_segments(document, shape_editor); + path_overlays(document, DrawHandles::SelectedAnchors(selected_segments), shape_editor, &mut overlay_context); + } PathOverlayMode::FrontierHandles => { - let selected_segments = get_selected_segments(document, shape_editor); - //Behaviour like Selectedpointhandles when only one point is selected + let selected_segments = selected_segments(document, shape_editor); + + // Match the behavior of `PathOverlayMode::SelectedPointHandles` when only one point is selected if shape_editor.selected_points().count() == 1 { - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::SelectedAnchors(selected_segments)); + path_overlays(document, DrawHandles::SelectedAnchors(selected_segments), shape_editor, &mut overlay_context); } else { let mut segment_endpoints: HashMap> = HashMap::new(); 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; - }; + let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; - //The points which are part of only one segment will be rendered + // 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_data.segment_bezier_iter() { if selected_segments.contains(&segment_id) { - selected_segments_by_point.entry(start).or_insert_with(Vec::new).push(segment_id); - selected_segments_by_point.entry(end).or_insert_with(Vec::new).push(segment_id); + selected_segments_by_point.entry(start).or_default().push(segment_id); + selected_segments_by_point.entry(end).or_default().push(segment_id); } } + for (point, attached_segments) in selected_segments_by_point { if attached_segments.len() == 1 { - segment_endpoints.entry(attached_segments[0]).or_insert_with(Vec::new).push(point); + segment_endpoints.entry(attached_segments[0]).or_default().push(point); } } } - //Now frontier anchors can be sent for rendering overlays - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::FrontierHandles(segment_endpoints)); + + // Now frontier anchors can be sent for rendering overlays + path_overlays(document, DrawHandles::FrontierHandles(segment_endpoints), shape_editor, &mut overlay_context); } } } @@ -959,7 +953,7 @@ impl Fsm for PathToolFsmState { } } - if (tool_data.saved_points_before_handle_drag.len() > 0) && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) { + if !tool_data.saved_points_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) { tool_data.handle_drag_toggle = true; } @@ -1151,9 +1145,10 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } (_, PathToolMessage::DragStop { extend_selection, .. }) => { - if tool_data.handle_drag_toggle && !(tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD) { + if tool_data.handle_drag_toggle && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { shape_editor.deselect_all_points(); shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_handle_drag); + tool_data.saved_points_before_handle_drag.clear(); tool_data.handle_drag_toggle = false; } diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index fa1f56c7ae..d59af4ff25 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -116,18 +116,6 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .widget_holder() } -fn create_path_overlay_mode_widget(path_overlay_mode: PenOverlayMode) -> WidgetHolder { - let entries = vec![ - RadioEntryData::new("1") - .label("1") - .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles)).into()), - RadioEntryData::new("2") - .label("2") - .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles)).into()), - ]; - RadioInput::new(entries).selected_index(Some(path_overlay_mode as u32)).widget_holder() -} - impl LayoutHolder for PenTool { fn layout(&self) -> Layout { let mut widgets = self.options.fill.create_widgets( @@ -147,10 +135,25 @@ impl LayoutHolder for PenTool { |color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()), |color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid())).into(), )); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + widgets.push(create_weight_widget(self.options.line_weight)); + widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(create_path_overlay_mode_widget(self.options.pen_overlay_mode)); + + widgets.push( + RadioInput::new(vec![ + RadioEntryData::new("1") + .label("1") + .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles)).into()), + RadioEntryData::new("2") + .label("2") + .on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles)).into()), + ]) + .selected_index(Some(self.options.pen_overlay_mode as u32)) + .widget_holder(), + ); Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) } @@ -822,7 +825,7 @@ impl Fsm for PenToolFsmState { self } (PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => { - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); + path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context); self } @@ -868,14 +871,14 @@ impl Fsm for PenToolFsmState { match tool_options.pen_overlay_mode { PenOverlayMode::AllHandles => { - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); + path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); } PenOverlayMode::FrontierHandles => { - //Find the last segment id to be drawn handles of - if let Some(latest_segment) = tool_data.latest_point().unwrap().in_segment { - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::SelectedAnchors(vec![latest_segment])); + // Find the last segment ID to have its handles drawn + if let Some(latest_segment) = tool_data.latest_point().and_then(|point| point.in_segment) { + path_overlays(document, DrawHandles::SelectedAnchors(vec![latest_segment]), shape_editor, &mut overlay_context); } else { - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::None); + path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context); }; } } @@ -891,7 +894,7 @@ impl Fsm for PenToolFsmState { } } else { // Draw the whole path and its manipulators when the user is clicking-and-dragging out from the most recently placed anchor to set its outgoing handle, during which it would otherwise not have its overlays drawn - path_overlays(document, shape_editor, &mut overlay_context, DrawHandles::All); + path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context); } if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) { diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs index 6543e1d581..8502b7178b 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gcore/src/vector/vector_data.rs @@ -294,7 +294,7 @@ impl VectorData { } } - pub fn get_adjacent_segment(&self, manipulator_id: &ManipulatorPointId) -> Option<(PointId, SegmentId)> { + pub fn adjacent_segment(&self, manipulator_id: &ManipulatorPointId) -> Option<(PointId, SegmentId)> { match manipulator_id { ManipulatorPointId::PrimaryHandle(segment_id) => { // For start handle, find segments ending at our start point