Skip to content

Commit fd057a7

Browse files
committed
Lasso select for segment editing
1 parent b0d6513 commit fd057a7

5 files changed

Lines changed: 101 additions & 103 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ thiserror = { workspace = true }
4545
serde = { workspace = true }
4646
serde_json = { workspace = true }
4747
bezier-rs = { workspace = true }
48+
kurbo = { workspace = true }
4849
futures = { workspace = true }
4950
glam = { workspace = true, features = ["serde", "debug-glam-assert"] }
5051
derivative = { workspace = true }

editor/src/messages/tool/common_functionality/shape_editor.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::graph_modification_utils::merge_layers;
22
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
3-
use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
3+
use super::utility_functions::{adjust_handle_colinearity, calculate_bezier_bbox, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
44
use crate::consts::HANDLE_LENGTH_FACTOR;
55
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
66
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
@@ -124,6 +124,10 @@ impl SelectedLayerState {
124124
self.selected_points.clear();
125125
}
126126

127+
pub fn clear_segments(&mut self) {
128+
self.selected_segments.clear();
129+
}
130+
127131
pub fn selected_points_count(&self) -> usize {
128132
let count = self.selected_points.iter().fold(0, |acc, point| {
129133
let is_ignored = (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors);
@@ -164,6 +168,7 @@ pub struct ManipulatorPointInfo {
164168

165169
pub type OpposingHandleLengths = HashMap<LayerNodeIdentifier, HashMap<HandleId, f64>>;
166170

171+
#[derive(Clone)]
167172
pub struct ClosestSegment {
168173
layer: LayerNodeIdentifier,
169174
segment: SegmentId,
@@ -1822,7 +1827,8 @@ impl ShapeState {
18221827

18231828
for (&layer, state) in &mut self.selected_shape_state {
18241829
if selection_change == SelectionChange::Clear {
1825-
state.clear_points()
1830+
state.clear_points();
1831+
state.clear_segments();
18261832
}
18271833

18281834
let vector_data = network_interface.compute_modified_vector(layer);
@@ -1852,6 +1858,28 @@ impl ShapeState {
18521858
for (id, bezier, _, _) in vector_data.segment_bezier_iter() {
18531859
if select_segments {
18541860
// Checking for selection of segments if they lie inside the bounding box or lasso polygon
1861+
let segment_bbox = calculate_bezier_bbox(bezier);
1862+
let bottom_left = transform.transform_point2(segment_bbox[0]);
1863+
let top_right = transform.transform_point2(segment_bbox[1]);
1864+
1865+
let select = match selection_shape {
1866+
SelectionShape::Box(quad) => quad[0].min(quad[1]).cmple(bottom_left).all() && quad[0].max(quad[1]).cmpge(top_right).all(),
1867+
SelectionShape::Lasso(_) => {
1868+
//First check if the segement bbox intersects with lasso polygon (atleast one of the points of bbox should lie in lasso polygon)
1869+
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
1870+
1871+
// Sample 10 points on the bezier and check if all lie inside the polygon or not
1872+
let points = bezier.compute_lookup_table(Some(10), None);
1873+
points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p))
1874+
}
1875+
};
1876+
1877+
if select {
1878+
match selection_change {
1879+
SelectionChange::Shrink => state.deselect_segment(id),
1880+
_ => state.select_segment(id),
1881+
}
1882+
}
18551883
}
18561884

18571885
// Checking for selection of handles

editor/src/messages/tool/common_functionality/utility_functions.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
22
use crate::messages::prelude::*;
33
use crate::messages::tool::common_functionality::graph_modification_utils::get_text;
44
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
5-
use bezier_rs::Bezier;
5+
use bezier_rs::{Bezier, BezierHandles};
66
use glam::DVec2;
77
use graphene_core::renderer::Quad;
88
use graphene_core::text::{FontCache, load_face};
99
use graphene_std::vector::{HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType};
10+
use kurbo::{CubicBez, Line, ParamCurveExtrema, Point, QuadBez};
1011

1112
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
1213
pub fn should_extend(
@@ -198,6 +199,21 @@ pub fn is_visible_point(
198199
}
199200
}
200201

201-
pub fn calculate_bezier_bbox(_bezier: Bezier) {
202-
// Get the bbox of bezier
202+
/// Function to find the bounding box of bezier (uses method from kurbo)
203+
pub fn calculate_bezier_bbox(bezier: Bezier) -> [DVec2; 2] {
204+
let start = Point::new(bezier.start.x, bezier.start.y);
205+
let end = Point::new(bezier.end.x, bezier.end.y);
206+
let bbox = match bezier.handles {
207+
BezierHandles::Cubic { handle_start, handle_end } => {
208+
let p1 = Point::new(handle_start.x, handle_start.y);
209+
let p2 = Point::new(handle_end.x, handle_end.y);
210+
CubicBez::new(start, p1, p2, end).bounding_box()
211+
}
212+
BezierHandles::Quadratic { handle } => {
213+
let p1 = Point::new(handle.x, handle.y);
214+
QuadBez::new(start, p1, end).bounding_box()
215+
}
216+
BezierHandles::Linear => Line::new(start, end).bounding_box(),
217+
};
218+
[DVec2::new(bbox.x0, bbox.y0), DVec2::new(bbox.x1, bbox.y1)]
203219
}

editor/src/messages/tool/tool_messages/path_tool.rs

Lines changed: 50 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ struct PathToolData {
435435
angle: f64,
436436
opposite_handle_position: Option<DVec2>,
437437
last_clicked_point_was_selected: bool,
438+
last_clicked_segment_was_selected: bool,
438439
snapping_axis: Option<Axis>,
439440
alt_clicked_on_anchor: bool,
440441
alt_dragging_from_anchor: bool,
@@ -632,6 +633,7 @@ impl PathToolData {
632633
let layer = segment.layer();
633634
let segment_id = segment.segment();
634635
let already_selected = shape_editor.selected_shape_state.get(&layer).map_or(false, |state| state.is_selected_segment(segment_id));
636+
self.last_clicked_segment_was_selected = already_selected;
635637

636638
if !(already_selected && extend_selection) {
637639
// let vector_data = document.network_interface.compute_modified_vector(segment.layer());
@@ -676,6 +678,7 @@ impl PathToolData {
676678
// We didn't find a segment, so consider selecting the nearest shape instead
677679
else if let Some(layer) = document.click(input) {
678680
shape_editor.deselect_all_points();
681+
shape_editor.deselect_all_segments();
679682
if extend_selection {
680683
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
681684
} else {
@@ -698,77 +701,6 @@ impl PathToolData {
698701
}
699702
}
700703

701-
#[allow(clippy::too_many_arguments)]
702-
fn mouse_down_segment_mode(
703-
&mut self,
704-
shape_editor: &mut ShapeState,
705-
document: &DocumentMessageHandler,
706-
input: &InputPreprocessorMessageHandler,
707-
responses: &mut VecDeque<Message>,
708-
extend_selection: bool,
709-
_lasso_select: bool,
710-
_handle_drag_from_anchor: bool,
711-
_drag_zero_handle: bool,
712-
_path_overlay_mode: PathOverlayMode,
713-
) -> PathToolFsmState {
714-
self.drag_start_pos = input.mouse.position;
715-
716-
// Check if a segment is already selected; if not, select the first segment within the threshold
717-
if let Some(segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {
718-
//Got the segment add the segment in selected segments
719-
let layer = segment.layer();
720-
let segment_id = segment.segment();
721-
let already_selected = shape_editor.selected_shape_state.get(&layer).map_or(false, |state| state.is_selected_segment(segment_id));
722-
723-
if !(already_selected && extend_selection) {
724-
// let vector_data = document.network_interface.compute_modified_vector(segment.layer());
725-
726-
let new_selected = if already_selected { !extend_selection } else { true };
727-
if new_selected {
728-
let retain_existing_selection = extend_selection || already_selected;
729-
if !retain_existing_selection {
730-
shape_editor.deselect_all_segments();
731-
}
732-
733-
// Add to selected segments
734-
if let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) {
735-
selected_shape_state.select_segment(segment_id);
736-
}
737-
} else {
738-
if let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) {
739-
selected_shape_state.deselect_segment(segment_id);
740-
}
741-
}
742-
}
743-
responses.add(OverlaysMessage::Draw);
744-
PathToolFsmState::Dragging(self.dragging_state)
745-
}
746-
// We didn't find a segment, so consider selecting the nearest shape instead
747-
else if let Some(layer) = document.click(input) {
748-
shape_editor.deselect_all_points();
749-
if extend_selection {
750-
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
751-
} else {
752-
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
753-
}
754-
self.drag_start_pos = input.mouse.position;
755-
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
756-
757-
responses.add(DocumentMessage::StartTransaction);
758-
759-
PathToolFsmState::Dragging(self.dragging_state)
760-
}
761-
// Start drawing
762-
else {
763-
self.drag_start_pos = input.mouse.position;
764-
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
765-
766-
let selection_shape = if _lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
767-
responses.add(OverlaysMessage::Draw);
768-
PathToolFsmState::Drawing { selection_shape }
769-
}
770-
}
771-
772704
fn start_dragging_point(&mut self, selected_points: SelectedPointsInfo, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, shape_editor: &mut ShapeState) {
773705
let mut manipulators = HashMap::with_hasher(NoHashBuilder);
774706
let mut unselected = Vec::new();
@@ -1742,6 +1674,7 @@ impl Fsm for PathToolFsmState {
17421674
(PathToolFsmState::Dragging { .. }, PathToolMessage::Escape | PathToolMessage::RightClick) => {
17431675
if tool_data.handle_drag_toggle && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
17441676
shape_editor.deselect_all_points();
1677+
shape_editor.deselect_all_segments();
17451678
shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_handle_drag);
17461679

17471680
tool_data.saved_points_before_handle_drag.clear();
@@ -1815,7 +1748,7 @@ impl Fsm for PathToolFsmState {
18151748
(_, PathToolMessage::DragStop { extend_selection, .. }) => {
18161749
let extend_selection = input.keyboard.get(extend_selection as usize);
18171750
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
1818-
// TODO: Here we want only visible points to be considered
1751+
18191752
let nearest_point = shape_editor.find_nearest_visible_point_indices(
18201753
&document.network_interface,
18211754
input.mouse.position,
@@ -1824,40 +1757,78 @@ impl Fsm for PathToolFsmState {
18241757
tool_data.frontier_handles_info.clone(),
18251758
);
18261759

1760+
let nearest_segment = tool_data.segment.clone();
1761+
18271762
if let Some(segment) = &mut tool_data.segment {
18281763
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
18291764
if !drag_occurred && !tool_data.molding_segment && !segment_mode {
18301765
if tool_data.delete_segment_pressed {
18311766
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
18321767
shape_editor.dissolve_segment(responses, segment.layer(), &vector_data, segment.segment(), segment.points());
1833-
responses.add(DocumentMessage::EndTransaction);
18341768
}
18351769
} else {
18361770
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection);
1837-
responses.add(DocumentMessage::EndTransaction);
18381771
}
1839-
} else {
1840-
responses.add(DocumentMessage::EndTransaction);
18411772
}
18421773

18431774
tool_data.segment = None;
18441775
tool_data.molding_info = None;
18451776
tool_data.molding_segment = false;
18461777
tool_data.temporary_adjacent_handles_while_molding = None;
1847-
1848-
return PathToolFsmState::Ready;
18491778
}
18501779

1780+
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
1781+
18511782
if let Some((layer, nearest_point)) = nearest_point {
1783+
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
18521784
if !drag_occurred && extend_selection {
1853-
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
1785+
log::info!("yes here occured");
18541786
if clicked_selected && tool_data.last_clicked_point_was_selected {
18551787
shape_editor.selected_shape_state.entry(layer).or_default().deselect_point(nearest_point);
18561788
} else {
18571789
shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);
18581790
}
18591791
responses.add(OverlaysMessage::Draw);
18601792
}
1793+
if !drag_occurred && !extend_selection {
1794+
if clicked_selected {
1795+
if tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
1796+
tool_data.saved_points_before_anchor_convert_smooth_sharp = shape_editor.selected_points().copied().collect::<HashSet<_>>();
1797+
}
1798+
shape_editor.deselect_all_points();
1799+
shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);
1800+
responses.add(OverlaysMessage::Draw);
1801+
}
1802+
}
1803+
}
1804+
// Segment editing mode
1805+
else if let Some(nearest_segment) = nearest_segment {
1806+
//Condition this upon whether user has selected segment editing mode or not
1807+
if segment_mode {
1808+
let clicked_selected = shape_editor.selected_segments().any(|&segment| segment == nearest_segment.segment());
1809+
if !drag_occurred && extend_selection {
1810+
if clicked_selected && tool_data.last_clicked_segment_was_selected {
1811+
shape_editor
1812+
.selected_shape_state
1813+
.entry(nearest_segment.layer())
1814+
.or_default()
1815+
.deselect_segment(nearest_segment.segment());
1816+
} else {
1817+
shape_editor.selected_shape_state.entry(nearest_segment.layer()).or_default().select_segment(nearest_segment.segment());
1818+
}
1819+
responses.add(OverlaysMessage::Draw);
1820+
}
1821+
if !drag_occurred && !extend_selection && clicked_selected {
1822+
shape_editor.deselect_all_segments();
1823+
shape_editor.selected_shape_state.entry(nearest_segment.layer()).or_default().select_segment(nearest_segment.segment());
1824+
responses.add(OverlaysMessage::Draw);
1825+
}
1826+
}
1827+
}
1828+
// Deselect all points if the user clicks the filled region of the shape
1829+
else if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {
1830+
shape_editor.deselect_all_points();
1831+
shape_editor.deselect_all_segments();
18611832
}
18621833

18631834
if tool_data.temporary_colinear_handles {
@@ -1883,25 +1854,6 @@ impl Fsm for PathToolFsmState {
18831854
tool_data.select_anchor_toggled = false;
18841855
}
18851856

1886-
if let Some((layer, nearest_point)) = nearest_point {
1887-
if !drag_occurred && !extend_selection {
1888-
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
1889-
if clicked_selected {
1890-
if tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
1891-
tool_data.saved_points_before_anchor_convert_smooth_sharp = shape_editor.selected_points().copied().collect::<HashSet<_>>();
1892-
}
1893-
1894-
shape_editor.deselect_all_points();
1895-
shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);
1896-
responses.add(OverlaysMessage::Draw);
1897-
}
1898-
}
1899-
}
1900-
// Deselect all points if the user clicks the filled region of the shape
1901-
else if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {
1902-
shape_editor.deselect_all_points();
1903-
}
1904-
19051857
if tool_data.snapping_axis.is_some() {
19061858
tool_data.snapping_axis = None;
19071859
}

0 commit comments

Comments
 (0)