Skip to content

Commit b564579

Browse files
4adexKeavon
andauthored
Make the Path tool only allow selecting points that are visible (#2668)
* Fix only visible points selection in point selection * Fix comments * Remove bug from box selection and lasso * Code review * Fix comment --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent f6e592d commit b564579

File tree

4 files changed

+216
-28
lines changed

4 files changed

+216
-28
lines changed

editor/src/messages/portfolio/document/overlays/utility_functions.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::utility_types::{DrawHandles, OverlayContext};
22
use crate::consts::HIDE_HANDLE_DISTANCE;
3+
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
34
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
45
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
56
use bezier_rs::{Bezier, BezierHandles};
@@ -23,7 +24,7 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
2324
create_context().expect("Failed to get canvas context")
2425
}
2526

26-
pub fn selected_segments(document: &DocumentMessageHandler, shape_editor: &mut ShapeState) -> Vec<SegmentId> {
27+
pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor: &ShapeState) -> Vec<SegmentId> {
2728
let selected_points = shape_editor.selected_points();
2829
let selected_anchors = selected_points
2930
.filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(*p) } else { None })
@@ -40,8 +41,8 @@ pub fn selected_segments(document: &DocumentMessageHandler, shape_editor: &mut S
4041

4142
// TODO: Currently if there are two duplicate layers, both of their segments get overlays
4243
// Adding segments which are are connected to selected anchors
43-
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
44-
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
44+
for layer in network_interface.selected_nodes().selected_layers(network_interface.document_metadata()) {
45+
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
4546

4647
for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() {
4748
if selected_anchors.contains(&start) || selected_anchors.contains(&end) {

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

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ use super::graph_modification_utils::{self, merge_layers};
22
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
33
use super::utility_functions::calculate_segment_angle;
44
use crate::consts::HANDLE_LENGTH_FACTOR;
5+
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
56
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
67
use crate::messages::portfolio::document::utility_types::misc::{PathSnapSource, SnapSource};
78
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
89
use crate::messages::prelude::*;
910
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
10-
use crate::messages::tool::tool_messages::path_tool::PointSelectState;
11+
use crate::messages::tool::common_functionality::utility_functions::is_visible_point;
12+
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
1113
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
1214
use glam::{DAffine2, DVec2};
1315
use graphene_core::transform::Transform;
@@ -421,12 +423,20 @@ impl ShapeState {
421423

422424
/// Select/deselect the first point within the selection threshold.
423425
/// Returns a tuple of the points if found and the offset, or `None` otherwise.
424-
pub fn change_point_selection(&mut self, network_interface: &NodeNetworkInterface, mouse_position: DVec2, select_threshold: f64, extend_selection: bool) -> Option<Option<SelectedPointsInfo>> {
426+
pub fn change_point_selection(
427+
&mut self,
428+
network_interface: &NodeNetworkInterface,
429+
mouse_position: DVec2,
430+
select_threshold: f64,
431+
extend_selection: bool,
432+
path_overlay_mode: PathOverlayMode,
433+
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
434+
) -> Option<Option<SelectedPointsInfo>> {
425435
if self.selected_shape_state.is_empty() {
426436
return None;
427437
}
428438

429-
if let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(network_interface, mouse_position, select_threshold) {
439+
if let Some((layer, manipulator_point_id)) = self.find_nearest_visible_point_indices(network_interface, mouse_position, select_threshold, path_overlay_mode, frontier_handles_info) {
430440
let vector_data = network_interface.compute_modified_vector(layer)?;
431441
let point_position = manipulator_point_id.get_position(&vector_data)?;
432442

@@ -467,7 +477,14 @@ impl ShapeState {
467477
None
468478
}
469479

470-
pub fn get_point_selection_state(&mut self, network_interface: &NodeNetworkInterface, mouse_position: DVec2, select_threshold: f64) -> Option<(bool, Option<SelectedPointsInfo>)> {
480+
pub fn get_point_selection_state(
481+
&mut self,
482+
network_interface: &NodeNetworkInterface,
483+
mouse_position: DVec2,
484+
select_threshold: f64,
485+
path_overlay_mode: PathOverlayMode,
486+
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
487+
) -> Option<(bool, Option<SelectedPointsInfo>)> {
471488
if self.selected_shape_state.is_empty() {
472489
return None;
473490
}
@@ -476,6 +493,13 @@ impl ShapeState {
476493
let vector_data = network_interface.compute_modified_vector(layer)?;
477494
let point_position = manipulator_point_id.get_position(&vector_data)?;
478495

496+
// Check if point is visible under current overlay mode or not
497+
let selected_segments = selected_segments(network_interface, self);
498+
let selected_points = self.selected_points().cloned().collect::<HashSet<_>>();
499+
if !is_visible_point(manipulator_point_id, &vector_data, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) {
500+
return None;
501+
}
502+
479503
let selected_shape_state = self.selected_shape_state.get(&layer)?;
480504
let already_selected = selected_shape_state.is_selected(manipulator_point_id);
481505

@@ -1320,6 +1344,42 @@ impl ShapeState {
13201344
None
13211345
}
13221346

1347+
pub fn find_nearest_visible_point_indices(
1348+
&mut self,
1349+
network_interface: &NodeNetworkInterface,
1350+
mouse_position: DVec2,
1351+
select_threshold: f64,
1352+
path_overlay_mode: PathOverlayMode,
1353+
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
1354+
) -> Option<(LayerNodeIdentifier, ManipulatorPointId)> {
1355+
if self.selected_shape_state.is_empty() {
1356+
return None;
1357+
}
1358+
1359+
let select_threshold_squared = select_threshold.powi(2);
1360+
1361+
// Find the closest control point among all elements of shapes_to_modify
1362+
for &layer in self.selected_shape_state.keys() {
1363+
if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(network_interface, layer, mouse_position) {
1364+
// Choose the first point under the threshold
1365+
if distance_squared < select_threshold_squared {
1366+
// Check if point is visible in current PathOverlayMode
1367+
let vector_data = network_interface.compute_modified_vector(layer)?;
1368+
let selected_segments = selected_segments(network_interface, self);
1369+
let selected_points = self.selected_points().cloned().collect::<HashSet<_>>();
1370+
1371+
if !is_visible_point(manipulator_point_id, &vector_data, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) {
1372+
return None;
1373+
}
1374+
1375+
return Some((layer, manipulator_point_id));
1376+
}
1377+
}
1378+
}
1379+
1380+
None
1381+
}
1382+
13231383
// TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n))
13241384
/// Find the closest manipulator, manipulator point, and distance so we can select path elements.
13251385
/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.
@@ -1623,7 +1683,17 @@ impl ShapeState {
16231683
false
16241684
}
16251685

1626-
pub fn select_all_in_shape(&mut self, network_interface: &NodeNetworkInterface, selection_shape: SelectionShape, selection_change: SelectionChange) {
1686+
pub fn select_all_in_shape(
1687+
&mut self,
1688+
network_interface: &NodeNetworkInterface,
1689+
selection_shape: SelectionShape,
1690+
selection_change: SelectionChange,
1691+
path_overlay_mode: PathOverlayMode,
1692+
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
1693+
) {
1694+
let selected_points = self.selected_points().cloned().collect::<HashSet<_>>();
1695+
let selected_segments = selected_segments(network_interface, self);
1696+
16271697
for (&layer, state) in &mut self.selected_shape_state {
16281698
if selection_change == SelectionChange::Clear {
16291699
state.clear_points()
@@ -1666,13 +1736,17 @@ impl ShapeState {
16661736
};
16671737

16681738
if select {
1669-
match selection_change {
1670-
SelectionChange::Shrink => state.deselect_point(id),
1671-
_ => {
1672-
// Select only the handles which are of nonzero length
1673-
if let Some(handle) = id.as_handle() {
1674-
if handle.length(&vector_data) > 0. {
1675-
state.select_point(id)
1739+
let is_visible_handle = is_visible_point(id, &vector_data, path_overlay_mode, frontier_handles_info.clone(), selected_segments.clone(), &selected_points);
1740+
1741+
if is_visible_handle {
1742+
match selection_change {
1743+
SelectionChange::Shrink => state.deselect_point(id),
1744+
_ => {
1745+
// Select only the handles which are of nonzero length
1746+
if let Some(handle) = id.as_handle() {
1747+
if handle.length(&vector_data) > 0. {
1748+
state.select_point(id)
1749+
}
16761750
}
16771751
}
16781752
}

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
22
use crate::messages::prelude::*;
33
use crate::messages::tool::common_functionality::graph_modification_utils::get_text;
4+
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
45
use glam::DVec2;
56
use graphene_core::renderer::Quad;
67
use graphene_core::text::{FontCache, load_face};
@@ -93,3 +94,46 @@ pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector_data:
9394

9495
required_handle.map(|handle| -(handle - anchor_position).angle_to(DVec2::X))
9596
}
97+
98+
/// Check whether a point is visible in the current overlay mode.
99+
pub fn is_visible_point(
100+
manipulator_point_id: ManipulatorPointId,
101+
vector_data: &VectorData,
102+
path_overlay_mode: PathOverlayMode,
103+
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
104+
selected_segments: Vec<SegmentId>,
105+
selected_points: &HashSet<ManipulatorPointId>,
106+
) -> bool {
107+
match manipulator_point_id {
108+
ManipulatorPointId::Anchor(_) => true,
109+
ManipulatorPointId::EndHandle(segment_id) | ManipulatorPointId::PrimaryHandle(segment_id) => {
110+
match (path_overlay_mode, selected_points.len() == 1) {
111+
(PathOverlayMode::AllHandles, _) => true,
112+
(PathOverlayMode::SelectedPointHandles, _) | (PathOverlayMode::FrontierHandles, true) => {
113+
if selected_segments.contains(&segment_id) {
114+
return true;
115+
}
116+
117+
// Either the segment is a part of selected segments or the opposite handle is a part of existing selection
118+
let Some(handle_pair) = manipulator_point_id.get_handle_pair(vector_data) else { return false };
119+
let other_handle = handle_pair[1].to_manipulator_point();
120+
121+
// Return whether the list of selected points contain the other handle
122+
selected_points.contains(&other_handle)
123+
}
124+
(PathOverlayMode::FrontierHandles, false) => {
125+
let Some(anchor) = manipulator_point_id.get_anchor(vector_data) else {
126+
warn!("No anchor for selected handle");
127+
return false;
128+
};
129+
let Some(frontier_handles) = &frontier_handles_info else {
130+
warn!("No frontier handles info provided");
131+
return false;
132+
};
133+
134+
frontier_handles.get(&segment_id).map(|anchors| anchors.contains(&anchor)).unwrap_or_default()
135+
}
136+
}
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)