Skip to content

Improve Path tool behavior when double-clicking anchor points #2498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,6 @@ pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 15;

// INPUT
pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
64 changes: 34 additions & 30 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,7 @@ impl ShapeState {
}
}
}

/// Converts a nearby clicked anchor point's handles between sharp (zero-length handles) and smooth (pulled-apart handle(s)).
/// If both handles aren't zero-length, they are set that. If both are zero-length, they are stretched apart by a reasonable amount.
/// This can can be activated by double clicking on an anchor with the Path tool.
Expand Down Expand Up @@ -1568,44 +1569,47 @@ impl ShapeState {
.count();

// Check by comparing the handle positions to the anchor if this manipulator group is a point
if positions != 0 {
self.convert_manipulator_handles_to_colinear(&vector_data, id, responses, layer);
} else {
for handle in vector_data.all_connected(id) {
let Some(bezier) = vector_data.segment_from_id(handle.segment) else { continue };

match bezier.handles {
BezierHandles::Linear => {}
BezierHandles::Quadratic { .. } => {
let segment = handle.segment;
// Convert to linear
let modification_type = VectorModificationType::SetHandles { segment, handles: [None; 2] };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
for point in self.selected_points() {
let Some(point_id) = point.as_anchor() else { continue };
if positions != 0 {
self.convert_manipulator_handles_to_colinear(&vector_data, point_id, responses, layer);
} else {
for handle in vector_data.all_connected(point_id) {
let Some(bezier) = vector_data.segment_from_id(handle.segment) else { continue };

match bezier.handles {
BezierHandles::Linear => {}
BezierHandles::Quadratic { .. } => {
let segment = handle.segment;
// Convert to linear
let modification_type = VectorModificationType::SetHandles { segment, handles: [None; 2] };
responses.add(GraphOperationMessage::Vector { layer, modification_type });

// Set the manipulator to have non-colinear handles
for &handles in &vector_data.colinear_manipulators {
if handles.contains(&HandleId::primary(segment)) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Set the manipulator to have non-colinear handles
for &handles in &vector_data.colinear_manipulators {
if handles.contains(&HandleId::primary(segment)) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
}
}
BezierHandles::Cubic { .. } => {
// Set handle position to anchor position
let modification_type = handle.set_relative_position(DVec2::ZERO);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
BezierHandles::Cubic { .. } => {
// Set handle position to anchor position
let modification_type = handle.set_relative_position(DVec2::ZERO);
responses.add(GraphOperationMessage::Vector { layer, modification_type });

// Set the manipulator to have non-colinear handles
for &handles in &vector_data.colinear_manipulators {
if handles.contains(&handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Set the manipulator to have non-colinear handles
for &handles in &vector_data.colinear_manipulators {
if handles.contains(&handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
}
}
}
}
};
};
}

Some(true)
};
Expand Down
19 changes: 18 additions & 1 deletion editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ struct PathToolData {
select_anchor_toggled: bool,
saved_points_before_handle_drag: Vec<ManipulatorPointId>,
handle_drag_toggle: bool,
anchors_before_flip: HashSet<ManipulatorPointId>,
dragging_state: DraggingState,
angle: f64,
opposite_handle_position: Option<DVec2>,
Expand All @@ -374,6 +375,7 @@ struct PathToolData {
alt_dragging_from_anchor: bool,
angle_locked: bool,
temporary_colinear_handles: bool,
time: u64,
}

impl PathToolData {
Expand Down Expand Up @@ -447,6 +449,13 @@ impl PathToolData {
self.opposing_handle_lengths = None;

self.drag_start_pos = input.mouse.position;
let current_time = input.time;

if !self.anchors_before_flip.is_empty() && (current_time - self.time > 500) {
self.anchors_before_flip.clear();
}

self.time = current_time;

let old_selection = shape_editor.selected_points().cloned().collect::<Vec<_>>();

Expand Down Expand Up @@ -1296,6 +1305,10 @@ impl Fsm for PathToolFsmState {
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);

if !tool_data.anchors_before_flip.is_empty() {
tool_data.anchors_before_flip.clear();
}

// If there is a point nearby, then remove the overlay
if shape_editor
.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD)
Expand Down Expand Up @@ -1404,7 +1417,6 @@ impl Fsm for PathToolFsmState {
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;
}
Expand Down Expand Up @@ -1490,6 +1502,9 @@ impl Fsm for PathToolFsmState {
if !drag_occurred && !extend_selection {
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
if clicked_selected {
if tool_data.anchors_before_flip.is_empty() {
tool_data.anchors_before_flip = shape_editor.selected_points().copied().collect::<HashSet<_>>();
}
shape_editor.deselect_all_points();
shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);
responses.add(OverlaysMessage::Draw);
Expand Down Expand Up @@ -1537,7 +1552,9 @@ impl Fsm for PathToolFsmState {
// Flip the selected point between smooth and sharp
if !tool_data.double_click_handled && tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {
responses.add(DocumentMessage::StartTransaction);
shape_editor.select_points_by_manipulator_id(&tool_data.anchors_before_flip.iter().copied().collect::<Vec<_>>());
shape_editor.flip_smooth_sharp(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE, responses);
tool_data.anchors_before_flip.clear();
responses.add(DocumentMessage::EndTransaction);
responses.add(PathToolMessage::SelectedPointUpdated);
}
Expand Down
Loading