Skip to content
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

Make Path tool handle dragging recover the opposing cubic handle when Alt is pressed if not cubic #2196

Open
wants to merge 6 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
7 changes: 5 additions & 2 deletions editor/src/messages/tool/tool_messages/line_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,18 +293,21 @@ fn generate_transform(tool_data: &mut LineToolData, snap_data: SnapData, lock_an

let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);
let mut line_length = (document_points[1] - document_points[0]).length();

if lock_angle {
angle = tool_data.angle;
}
if snap_angle {
} else if snap_angle {
let snap_resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
angle = (angle / snap_resolution).round() * snap_resolution;
}

tool_data.angle = angle;

if lock_angle {
let angle_vec = DVec2::new(angle.cos(), angle.sin());
line_length = (document_points[1] - document_points[0]).dot(angle_vec);
}

document_points[1] = document_points[0] + line_length * DVec2::new(angle.cos(), angle.sin());

let constrained = snap_angle || lock_angle;
Expand Down
75 changes: 59 additions & 16 deletions editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ struct PathToolData {
saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
select_anchor_toggled: bool,
dragging_state: DraggingState,
current_selected_handle_id: Option<ManipulatorPointId>,
angle: f64,
}

Expand Down Expand Up @@ -450,15 +451,20 @@ impl PathToolData {
}

fn update_colinear(&mut self, equidistant: bool, toggle_colinear: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {
// Check handle colinear state
let is_colinear = self
.selection_status
.angle()
.map(|angle| match angle {
ManipulatorAngle::Colinear => true,
ManipulatorAngle::Free | ManipulatorAngle::Mixed => false,
})
.unwrap_or(false);

// Check if the toggle_colinear key has just been pressed
if toggle_colinear && !self.toggle_colinear_debounce {
self.opposing_handle_lengths = None;
let colinear = self.selection_status.angle().is_some_and(|angle| match angle {
ManipulatorAngle::Colinear => true,
ManipulatorAngle::Free => false,
ManipulatorAngle::Mixed => false,
});
if colinear {
if is_colinear {
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
} else {
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
Expand All @@ -469,13 +475,47 @@ impl PathToolData {
self.toggle_colinear_debounce = toggle_colinear;

if equidistant && self.opposing_handle_lengths.is_none() {
if !is_colinear {
// Try to get selected handle info
let Some((_, _, selected_handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) else {
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
return false;
};

let Some((layer, _)) = shape_editor.selected_shape_state.iter().next() else {
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
return false;
};

let Some(vector_data) = document.network_interface.compute_modified_vector(*layer) else {
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
return false;
};

// Check if handle has a pair (to ignore handles of edges of open paths)
if let Some(handle_pair) = selected_handle_id.get_handle_pair(&vector_data) {
let opposite_handle_length = handle_pair.iter().filter(|&&h| h.to_manipulator_point() != selected_handle_id).find_map(|&h| {
let opp_handle_pos = h.to_manipulator_point().get_position(&vector_data)?;
let opp_anchor_id = h.to_manipulator_point().get_anchor(&vector_data)?;
let opp_anchor_pos = vector_data.point_domain.position_from_id(opp_anchor_id)?;
Some((opp_handle_pos - opp_anchor_pos).length())
});

// Make handles colinear if opposite handle is zero length
if opposite_handle_length.map_or(false, |l| l == 0.0) {
debug!("Zero length non-colinear handle detected: {:?}", selected_handle_id);
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
return true;
}
}
}
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
}
false
}

/// Attempts to get a single selected handle. Also retrieves the position of the anchor it is connected to. Used for the purpose of snapping the angle.
fn try_get_selected_handle_and_anchor(&self, shape_editor: &ShapeState, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2)> {
fn try_get_selected_handle_and_anchor(&self, shape_editor: &ShapeState, document: &DocumentMessageHandler) -> Option<(DVec2, DVec2, ManipulatorPointId)> {
// Only count selections of a single layer
let (layer, selection) = shape_editor.selected_shape_state.iter().next()?;

Expand All @@ -486,6 +526,7 @@ impl PathToolData {

// Only count selected handles
let selected_handle = selection.selected().next()?.as_handle()?;
let handle_id = selected_handle.to_manipulator_point();

let layer_to_document = document.metadata().transform_to_document(*layer);
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
Expand All @@ -497,24 +538,26 @@ impl PathToolData {
let handle_position_document = layer_to_document.transform_point2(handle_position_local);
let anchor_position_document = layer_to_document.transform_point2(anchor_position_local);

Some((handle_position_document, anchor_position_document))
Some((handle_position_document, anchor_position_document, handle_id))
}

fn calculate_handle_angle(&mut self, handle_vector: DVec2, lock_angle: bool, snap_angle: bool) -> f64 {
let mut handle_angle = -handle_vector.angle_to(DVec2::X);
fn calculate_handle_angle(&mut self, handle_vector: DVec2, handle_id: ManipulatorPointId, lock_angle: bool, snap_angle: bool) -> f64 {
let current_angle = -handle_vector.angle_to(DVec2::X);

// When the angle is locked we use the old angle
if lock_angle {
handle_angle = self.angle
if self.current_selected_handle_id == Some(handle_id) && lock_angle {
return self.angle;
}

// Round the angle to the closest increment
if snap_angle {
let mut handle_angle = current_angle;
if snap_angle && !lock_angle {
let snap_resolution = HANDLE_ROTATE_SNAP_ANGLE.to_radians();
handle_angle = (handle_angle / snap_resolution).round() * snap_resolution;
}

// Cache the old handle angle for the lock angle.
// Cache the angle and handle id for lock angle
self.current_selected_handle_id = Some(handle_id);
self.angle = handle_angle;

handle_angle
Expand Down Expand Up @@ -566,10 +609,10 @@ impl PathToolData {
let current_mouse = input.mouse.position;
let raw_delta = document_to_viewport.inverse().transform_vector2(current_mouse - previous_mouse);

let snapped_delta = if let Some((handle_pos, anchor_pos)) = self.try_get_selected_handle_and_anchor(shape_editor, document) {
let snapped_delta = if let Some((handle_pos, anchor_pos, handle_id)) = self.try_get_selected_handle_and_anchor(shape_editor, document) {
let cursor_pos = handle_pos + raw_delta;

let handle_angle = self.calculate_handle_angle(cursor_pos - anchor_pos, lock_angle, snap_angle);
let handle_angle = self.calculate_handle_angle(cursor_pos - anchor_pos, handle_id, lock_angle, snap_angle);

let constrained_direction = DVec2::new(handle_angle.cos(), handle_angle.sin());
let projected_length = (cursor_pos - anchor_pos).dot(constrained_direction);
Expand Down
Loading