Make the Pen tool use Ctrl to lock the angle of handles such that they maintain colinearity (#2284)
* pen tool feature and other minor fixes * ctrl done,need to improve handle_modifications * completes collinear and ctrl for all cases,need to refactor * more bug fix need to refactor * fixed minor issues and refactor done * Code review * minor bug fixes --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
687744d999
commit
e444785301
|
|
@ -591,11 +591,14 @@ impl<'a> Selected<'a> {
|
|||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
if let Some((id, initial)) = handle.mirror {
|
||||
let direction = viewspace.transform_vector2(new_pos_viewport - relative).try_normalize();
|
||||
let length = viewspace.transform_vector2(initial - relative).length();
|
||||
let new_relative = direction.map_or(initial - relative, |direction| viewspace.inverse().transform_vector2(-direction * length));
|
||||
let modification_type = id.set_relative_position(new_relative);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
// When the handle is scaled to zero, don't update the mirror handle
|
||||
if (new_pos_viewport - relative).length_squared() > f64::EPSILON {
|
||||
let direction = viewspace.transform_vector2(new_pos_viewport - relative).try_normalize();
|
||||
let length = viewspace.transform_vector2(initial - relative).length();
|
||||
let new_relative = direction.map_or(initial - relative, |direction| viewspace.inverse().transform_vector2(-direction * length));
|
||||
let modification_type = id.set_relative_position(new_relative);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -733,14 +733,16 @@ impl ShapeState {
|
|||
}
|
||||
|
||||
/// Move the selected points by dragging the mouse.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn move_selected_points(
|
||||
&self,
|
||||
handle_lengths: Option<OpposingHandleLengths>,
|
||||
document: &DocumentMessageHandler,
|
||||
delta: DVec2,
|
||||
equidistant: bool,
|
||||
responses: &mut VecDeque<Message>,
|
||||
in_viewport_space: bool,
|
||||
opposite_handle_position: Option<DVec2>,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
for (&layer, state) in &self.selected_shape_state {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
|
|
@ -791,6 +793,11 @@ impl ShapeState {
|
|||
|
||||
let new_relative = if equidistant {
|
||||
-(handle_position - anchor_position)
|
||||
}
|
||||
// If the handle is very close to the anchor, return the original position
|
||||
else if (handle_position - anchor_position).length_squared() < f64::EPSILON * 1e5 {
|
||||
let Some(opposite_handle_position) = opposite_handle_position else { continue };
|
||||
opposite_handle_position - anchor_position
|
||||
} else {
|
||||
// TODO: Is this equivalent to `transform_to_document_space`? If changed, the before and after should be tested.
|
||||
let transform = document.metadata().document_to_viewport.inverse() * transform_to_viewport_space;
|
||||
|
|
|
|||
|
|
@ -371,6 +371,7 @@ struct PathToolData {
|
|||
dragging_state: DraggingState,
|
||||
current_selected_handle_id: Option<ManipulatorPointId>,
|
||||
angle: f64,
|
||||
opposite_handle_position: Option<DVec2>,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
|
|
@ -551,6 +552,16 @@ impl PathToolData {
|
|||
for point in state.selected() {
|
||||
let Some(anchor) = point.get_anchor(&vector_data) else { continue };
|
||||
layer_manipulators.insert(anchor);
|
||||
let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) else { continue };
|
||||
let Some(handle) = point.as_handle() else { continue };
|
||||
// Check which handle is selected and which is opposite
|
||||
let opposite = if handle == handle1 { handle2 } else { handle1 };
|
||||
|
||||
self.opposite_handle_position = if self.opposite_handle_position.is_none() {
|
||||
opposite.to_manipulator_point().get_position(&vector_data)
|
||||
} else {
|
||||
self.opposite_handle_position
|
||||
};
|
||||
}
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
if layer_manipulators.contains(&id) {
|
||||
|
|
@ -742,7 +753,8 @@ impl PathToolData {
|
|||
};
|
||||
|
||||
let handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() };
|
||||
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, responses, true);
|
||||
let opposite = if lock_angle { None } else { self.opposite_handle_position };
|
||||
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, opposite, responses);
|
||||
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
|
||||
}
|
||||
}
|
||||
|
|
@ -1199,6 +1211,7 @@ impl Fsm for PathToolFsmState {
|
|||
responses.add(DocumentMessage::EndTransaction);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
tool_data.opposite_handle_position = None;
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
|
@ -1249,7 +1262,15 @@ impl Fsm for PathToolFsmState {
|
|||
}
|
||||
(_, PathToolMessage::PointerMove { .. }) => self,
|
||||
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
|
||||
shape_editor.move_selected_points(tool_data.opposing_handle_lengths.take(), document, (delta_x, delta_y).into(), true, responses, false);
|
||||
shape_editor.move_selected_points(
|
||||
tool_data.opposing_handle_lengths.take(),
|
||||
document,
|
||||
(delta_x, delta_y).into(),
|
||||
true,
|
||||
false,
|
||||
tool_data.opposite_handle_position,
|
||||
responses,
|
||||
);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
|||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, merge_layers};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use crate::messages::tool::common_functionality::utility_functions::should_extend;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend};
|
||||
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use graph_craft::document::NodeId;
|
||||
|
|
@ -243,6 +243,14 @@ struct LastPoint {
|
|||
in_segment: Option<SegmentId>,
|
||||
handle_start: DVec2,
|
||||
}
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
enum DrawMode {
|
||||
#[default]
|
||||
/// Modifies the clicked endpoint segment, once you go to the ready mode you need to modify the handles of the next clicked endpoint segment
|
||||
BreakPath,
|
||||
/// Modifies the handle_end
|
||||
ContinuePath,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
enum HandleMode {
|
||||
|
|
@ -267,8 +275,6 @@ struct PenToolData {
|
|||
g1_continuous: bool,
|
||||
toggle_colinear_debounce: bool,
|
||||
|
||||
segment_end_before_bent: Option<SegmentId>,
|
||||
|
||||
angle: f64,
|
||||
auto_panning: AutoPanning,
|
||||
modifiers: ModifierState,
|
||||
|
|
@ -280,6 +286,10 @@ struct PenToolData {
|
|||
alt_press: bool,
|
||||
|
||||
handle_mode: HandleMode,
|
||||
/// The point that is being dragged
|
||||
end_point: Option<PointId>,
|
||||
end_point_segment: Option<SegmentId>,
|
||||
draw_mode: DrawMode,
|
||||
}
|
||||
impl PenToolData {
|
||||
fn latest_point(&self) -> Option<&LastPoint> {
|
||||
|
|
@ -315,25 +325,35 @@ impl PenToolData {
|
|||
}
|
||||
|
||||
/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
|
||||
fn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2, layer: LayerNodeIdentifier) {
|
||||
fn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2, layer: LayerNodeIdentifier, preferences: &PreferencesMessageHandler) {
|
||||
self.g1_continuous = true;
|
||||
let document = snap_data.document;
|
||||
self.next_handle_start = self.next_point;
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
|
||||
// Break the control
|
||||
let Some(last_pos) = self.latest_point().map(|point| point.pos) else { return };
|
||||
let Some((last_pos, id)) = self.latest_point().map(|point| (point.pos, point.id)) else { return };
|
||||
|
||||
let transform = document.metadata().document_to_viewport * transform;
|
||||
let on_top = transform.transform_point2(self.next_point).distance_squared(transform.transform_point2(last_pos)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if on_top {
|
||||
self.handle_end = None;
|
||||
self.handle_mode = HandleMode::Free;
|
||||
|
||||
// Update `end_point_segment` that was clicked on
|
||||
self.store_clicked_endpoint(document, snap_data.input, preferences);
|
||||
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, id, self.end_point_segment);
|
||||
let last_segment = self.end_point_segment;
|
||||
let Some(point) = self.latest_point_mut() else { return };
|
||||
point.in_segment = last_segment;
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(point) = self.latest_point_mut() {
|
||||
point.in_segment = None;
|
||||
}
|
||||
|
||||
self.segment_end_before_bent = vector_data.segment_domain.ids().last().copied();
|
||||
self.handle_mode = HandleMode::Free;
|
||||
self.handle_end = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -379,9 +399,11 @@ impl PenToolData {
|
|||
end
|
||||
});
|
||||
|
||||
let points = [start, end];
|
||||
// Store the segment
|
||||
let id = SegmentId::generate();
|
||||
self.segment_end_before_bent = Some(id);
|
||||
self.end_point_segment = Some(id);
|
||||
|
||||
let points = [start, end];
|
||||
let modification_type = VectorModificationType::InsertSegment { id, points, handles };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
|
|
@ -413,12 +435,17 @@ impl PenToolData {
|
|||
self.next_handle_start = self.compute_snapped_angle(snap_data, transform, colinear, mouse, Some(self.next_point), false);
|
||||
let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) };
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer)?;
|
||||
// Check if the handle is the start of the segment
|
||||
let mut is_start = false;
|
||||
if let Some((anchor, segment)) = self.end_point.zip(self.end_point_segment) {
|
||||
is_start = vector_data.segment_start_from_id(segment) == Some(anchor);
|
||||
}
|
||||
|
||||
match self.handle_mode {
|
||||
HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => {
|
||||
self.g1_continuous = true;
|
||||
self.colinear(responses, layer, self.next_handle_start, self.next_point, &vector_data);
|
||||
self.adjust_handle_length(responses, layer, &vector_data);
|
||||
self.colinear(responses, layer, self.next_handle_start, self.next_point, &vector_data, is_start);
|
||||
self.adjust_handle_length(responses, layer, &vector_data, is_start);
|
||||
}
|
||||
HandleMode::Free => {
|
||||
self.g1_continuous = false;
|
||||
|
|
@ -431,83 +458,122 @@ impl PenToolData {
|
|||
}
|
||||
|
||||
/// Makes the opposite handle equidistant or locks its length.
|
||||
fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) {
|
||||
fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData, is_start: bool) {
|
||||
let Some(latest) = self.latest_point() else { return };
|
||||
let anchor_pos = latest.pos;
|
||||
|
||||
match self.handle_mode {
|
||||
HandleMode::ColinearEquidistant => self.adjust_equidistant_handle(anchor_pos, responses, layer, vector_data),
|
||||
HandleMode::ColinearLocked => self.adjust_locked_length_handle(anchor_pos, responses, layer),
|
||||
HandleMode::ColinearEquidistant => self.adjust_equidistant_handle(anchor_pos, responses, layer, vector_data, is_start),
|
||||
HandleMode::ColinearLocked => self.adjust_locked_length_handle(anchor_pos, responses, layer, is_start),
|
||||
HandleMode::Free => {} // No adjustments needed in free mode
|
||||
}
|
||||
}
|
||||
|
||||
fn colinear(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, handle_start: DVec2, anchor_point: DVec2, vector_data: &VectorData) {
|
||||
let Some(direction) = (anchor_point - handle_start).try_normalize() else {
|
||||
fn colinear(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, handle_start: DVec2, anchor_pos: DVec2, vector_data: &VectorData, is_start: bool) {
|
||||
let Some(direction) = (anchor_pos - handle_start).try_normalize() else {
|
||||
log::trace!("Skipping colinear adjustment: handle_start and anchor_point are too close!");
|
||||
return;
|
||||
};
|
||||
|
||||
let handle_offset = if let Some(handle_end) = self.handle_end {
|
||||
(handle_end - anchor_point).length()
|
||||
} else {
|
||||
let Some(segment) = self.segment_end_before_bent else { return };
|
||||
let end_handle = ManipulatorPointId::EndHandle(segment);
|
||||
let Some(end_handle) = end_handle.get_position(vector_data) else { return };
|
||||
(end_handle - anchor_point).length()
|
||||
};
|
||||
let new_handle_position = anchor_point + handle_offset * direction;
|
||||
self.update_handle_position(new_handle_position, anchor_point, responses, layer);
|
||||
let Some(handle_offset) = self.get_handle_offset(anchor_pos, vector_data, is_start) else { return };
|
||||
let new_handle_position = anchor_pos + handle_offset * direction;
|
||||
|
||||
self.update_handle_position(new_handle_position, anchor_pos, responses, layer, is_start);
|
||||
}
|
||||
|
||||
fn adjust_equidistant_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) {
|
||||
fn get_handle_offset(&self, anchor_pos: DVec2, vector_data: &VectorData, is_start: bool) -> Option<f64> {
|
||||
if is_start {
|
||||
let segment = self.end_point_segment?;
|
||||
let handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data)?;
|
||||
return Some((handle - anchor_pos).length());
|
||||
}
|
||||
|
||||
if self.draw_mode == DrawMode::ContinuePath {
|
||||
return self.handle_end.map(|handle| (handle - anchor_pos).length()).or_else(|| {
|
||||
self.end_point_segment
|
||||
.and_then(|segment| Some((ManipulatorPointId::EndHandle(segment).get_position(vector_data)? - anchor_pos).length()))
|
||||
});
|
||||
}
|
||||
|
||||
let handle = ManipulatorPointId::EndHandle(self.end_point_segment?).get_position(vector_data);
|
||||
if let Some(handle) = handle {
|
||||
return Some((handle - anchor_pos).length());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn adjust_equidistant_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData, is_start: bool) {
|
||||
if self.modifiers.break_handle {
|
||||
self.store_handle(vector_data);
|
||||
self.store_handle(vector_data, is_start);
|
||||
self.alt_press = true;
|
||||
let new_position = self.next_point * 2. - self.next_handle_start;
|
||||
self.update_handle_position(new_position, anchor_pos, responses, layer);
|
||||
self.update_handle_position(new_position, anchor_pos, responses, layer, is_start);
|
||||
} else {
|
||||
self.restore_previous_handle(anchor_pos, responses, layer);
|
||||
self.restore_previous_handle(anchor_pos, responses, layer, is_start);
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_locked_length_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
|
||||
fn adjust_locked_length_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, is_start: bool) {
|
||||
if !self.modifiers.break_handle {
|
||||
let new_position = self.next_point * 2. - self.next_handle_start;
|
||||
self.update_handle_position(new_position, anchor_pos, responses, layer);
|
||||
self.update_handle_position(new_position, anchor_pos, responses, layer, is_start);
|
||||
}
|
||||
}
|
||||
|
||||
/// Temporarily stores the opposite handle position to revert back when Alt is released in equidistant mode.
|
||||
fn store_handle(&mut self, vector_data: &VectorData) {
|
||||
fn store_handle(&mut self, vector_data: &VectorData, is_start: bool) {
|
||||
if !self.alt_press {
|
||||
self.previous_handle_end_pos = self.handle_end.or_else(|| {
|
||||
let segment = self.segment_end_before_bent?;
|
||||
ManipulatorPointId::EndHandle(segment).get_position(vector_data)
|
||||
});
|
||||
self.previous_handle_end_pos = if is_start {
|
||||
let Some(segment) = self.end_point_segment else { return };
|
||||
ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data)
|
||||
} else if self.draw_mode == DrawMode::ContinuePath {
|
||||
self.handle_end.or_else(|| {
|
||||
let segment = self.end_point_segment?;
|
||||
ManipulatorPointId::EndHandle(segment).get_position(vector_data)
|
||||
})
|
||||
} else {
|
||||
let Some(segment) = self.end_point_segment else { return };
|
||||
let end_handle = ManipulatorPointId::EndHandle(segment);
|
||||
end_handle.get_position(vector_data)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_previous_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
|
||||
fn restore_previous_handle(&mut self, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, is_start: bool) {
|
||||
if self.alt_press {
|
||||
self.alt_press = false;
|
||||
if let Some(previous_handle) = self.previous_handle_end_pos {
|
||||
self.update_handle_position(previous_handle, anchor_pos, responses, layer);
|
||||
self.update_handle_position(previous_handle, anchor_pos, responses, layer, is_start);
|
||||
}
|
||||
self.previous_handle_end_pos = None; // Reset storage
|
||||
}
|
||||
}
|
||||
|
||||
fn update_handle_position(&mut self, new_position: DVec2, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
|
||||
if let Some(handle) = self.handle_end.as_mut() {
|
||||
*handle = new_position;
|
||||
} else {
|
||||
let Some(segment) = self.segment_end_before_bent else { return };
|
||||
let relative_position = new_position - anchor_pos;
|
||||
let modification_type = VectorModificationType::SetEndHandle { segment, relative_position };
|
||||
fn update_handle_position(&mut self, new_position: DVec2, anchor_pos: DVec2, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, is_start: bool) {
|
||||
let relative_position = new_position - anchor_pos;
|
||||
|
||||
if self.draw_mode == DrawMode::ContinuePath {
|
||||
if let Some(handle) = self.handle_end.as_mut() {
|
||||
*handle = new_position;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(segment) = self.end_point_segment else { return };
|
||||
let modification_type = VectorModificationType::SetEndHandle { segment, relative_position };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(segment) = self.end_point_segment else { return };
|
||||
|
||||
if is_start {
|
||||
let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
return;
|
||||
}
|
||||
|
||||
let modification_type = VectorModificationType::SetEndHandle { segment, relative_position };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
|
|
@ -626,20 +692,36 @@ impl PenToolData {
|
|||
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
|
||||
if let Some((layer, point, position)) = should_extend(document, viewport, tolerance, selected_nodes.selected_layers(document.metadata()), preferences) {
|
||||
// Perform extension of an existing path
|
||||
let in_segment = if self.modifiers.lock_angle { self.end_point_segment } else { None };
|
||||
self.add_point(LastPoint {
|
||||
id: point,
|
||||
pos: position,
|
||||
in_segment: None,
|
||||
in_segment,
|
||||
handle_start: position,
|
||||
});
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
self.next_point = position;
|
||||
self.next_handle_start = position;
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, point, segment);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if append {
|
||||
if let Some((layer, point, _)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, point, segment);
|
||||
}
|
||||
}
|
||||
self.end_point_segment = None;
|
||||
let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface);
|
||||
let existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none());
|
||||
if let Some(layer) = existing_layer {
|
||||
|
|
@ -649,6 +731,16 @@ impl PenToolData {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
|
||||
if self.modifiers.lock_angle {
|
||||
self.set_lock_angle(&vector_data, point, segment);
|
||||
self.handle_mode = HandleMode::Free;
|
||||
}
|
||||
}
|
||||
|
||||
// New path layer
|
||||
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
|
||||
let nodes = vec![(NodeId(0), node_type.default_node_template())];
|
||||
|
|
@ -657,6 +749,8 @@ impl PenToolData {
|
|||
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
|
||||
tool_options.fill.apply_fill(layer, responses);
|
||||
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||
self.end_point_segment = None;
|
||||
self.draw_mode = DrawMode::ContinuePath;
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
|
||||
// This causes the following message to be run only after the next graph evaluation runs and the transforms are updated
|
||||
|
|
@ -665,6 +759,61 @@ impl PenToolData {
|
|||
responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport });
|
||||
}
|
||||
|
||||
// Stores the segment and point ID of the clicked endpoint
|
||||
fn store_clicked_endpoint(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, preferences: &PreferencesMessageHandler) {
|
||||
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||
|
||||
let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
|
||||
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
|
||||
|
||||
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
|
||||
|
||||
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
|
||||
self.end_point = Some(point);
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
|
||||
self.end_point_segment = segment;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_lock_angle(&mut self, vector_data: &VectorData, anchor: PointId, segment: Option<SegmentId>) {
|
||||
let anchor_position = vector_data.point_domain.position_from_id(anchor);
|
||||
|
||||
let Some((anchor_position, segment)) = anchor_position.zip(segment) else {
|
||||
self.handle_mode = HandleMode::Free;
|
||||
return;
|
||||
};
|
||||
|
||||
// Closure to check if a point is the start or end of a segment
|
||||
let is_start = |point: PointId, segment: SegmentId| vector_data.segment_start_from_id(segment) == Some(point);
|
||||
|
||||
let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector_data);
|
||||
let start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data);
|
||||
|
||||
let start_point = if is_start(anchor, segment) {
|
||||
vector_data.segment_end_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id))
|
||||
} else {
|
||||
vector_data.segment_start_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id))
|
||||
};
|
||||
|
||||
let required_handle = if is_start(anchor, segment) {
|
||||
start_handle
|
||||
.filter(|&handle| handle != anchor_position)
|
||||
.or(end_handle.filter(|&handle| Some(handle) != start_point))
|
||||
.or(start_point)
|
||||
} else {
|
||||
end_handle
|
||||
.filter(|&handle| handle != anchor_position)
|
||||
.or(start_handle.filter(|&handle| Some(handle) != start_point))
|
||||
.or(start_point)
|
||||
};
|
||||
|
||||
if let Some(required_handle) = required_handle {
|
||||
self.angle = -(required_handle - anchor_position).angle_to(DVec2::X);
|
||||
self.handle_mode = HandleMode::ColinearEquidistant;
|
||||
}
|
||||
}
|
||||
|
||||
fn add_point_layer_position(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, viewport: DVec2) {
|
||||
// Add the first point
|
||||
let id = PointId::generate();
|
||||
|
|
@ -737,7 +886,7 @@ impl Fsm for PenToolFsmState {
|
|||
tool_data.previous_handle_start_pos = latest.handle_start;
|
||||
|
||||
// Store the handle_end position
|
||||
let segment = tool_data.segment_end_before_bent;
|
||||
let segment = tool_data.end_point_segment;
|
||||
if let Some(segment) = segment {
|
||||
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
tool_data.previous_handle_end_pos = ManipulatorPointId::EndHandle(segment).get_position(&vector_data);
|
||||
|
|
@ -759,7 +908,7 @@ impl Fsm for PenToolFsmState {
|
|||
match tool_data.handle_mode {
|
||||
HandleMode::Free => {}
|
||||
HandleMode::ColinearEquidistant | HandleMode::ColinearLocked => {
|
||||
if let Some((latest, segment)) = tool_data.latest_point().zip(tool_data.segment_end_before_bent) {
|
||||
if let Some((latest, segment)) = tool_data.latest_point().zip(tool_data.end_point_segment) {
|
||||
let handle = ManipulatorPointId::EndHandle(segment).get_position(&vector_data);
|
||||
let Some(handle) = handle else { return PenToolFsmState::GRSHandle };
|
||||
|
||||
|
|
@ -814,7 +963,7 @@ impl Fsm for PenToolFsmState {
|
|||
});
|
||||
|
||||
// Set the handle-end back to original position
|
||||
if let Some(((latest, segment), handle_end)) = tool_data.latest_point().zip(tool_data.segment_end_before_bent).zip(tool_data.previous_handle_end_pos) {
|
||||
if let Some(((latest, segment), handle_end)) = tool_data.latest_point().zip(tool_data.end_point_segment).zip(tool_data.previous_handle_end_pos) {
|
||||
let relative = handle_end - latest.pos;
|
||||
let modification_type = VectorModificationType::SetEndHandle { segment, relative_position: relative };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
|
@ -866,6 +1015,19 @@ impl Fsm for PenToolFsmState {
|
|||
// Draw the line between the currently-being-placed anchor and its currently-being-dragged-out outgoing handle (opposite the one currently being dragged out)
|
||||
overlay_context.line(next_anchor, next_handle_start, None);
|
||||
|
||||
match tool_options.pen_overlay_mode {
|
||||
PenOverlayMode::AllHandles => {
|
||||
path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
|
||||
}
|
||||
PenOverlayMode::FrontierHandles => {
|
||||
if let Some(latest_segment) = tool_data.end_point_segment {
|
||||
path_overlays(document, DrawHandles::SelectedAnchors(vec![latest_segment]), shape_editor, &mut overlay_context);
|
||||
} else {
|
||||
path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if let (Some(anchor_start), Some(handle_start), Some(handle_end)) = (anchor_start, handle_start, handle_end) {
|
||||
// Draw the line between the most recently placed anchor and its outgoing handle (which is currently influencing the currently-being-placed segment)
|
||||
overlay_context.line(anchor_start, handle_start, None);
|
||||
|
|
@ -878,18 +1040,9 @@ impl Fsm for PenToolFsmState {
|
|||
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, DrawHandles::All, shape_editor, &mut overlay_context);
|
||||
}
|
||||
PenOverlayMode::FrontierHandles => {
|
||||
// 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, DrawHandles::None, shape_editor, &mut overlay_context);
|
||||
};
|
||||
}
|
||||
// Draw the line between the currently-being-placed anchor and last-placed point (Lock angle bent overlays)
|
||||
if self == PenToolFsmState::PlacingAnchor && anchor_start != handle_start && tool_data.modifiers.snap_angle {
|
||||
overlay_context.dashed_line(anchor_start, next_anchor, None, Some(4.), Some(4.), Some(0.5));
|
||||
}
|
||||
|
||||
if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) {
|
||||
|
|
@ -938,6 +1091,9 @@ impl Fsm for PenToolFsmState {
|
|||
(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
tool_data.handle_mode = HandleMode::Free;
|
||||
|
||||
// Get the closest point and the segment it is on
|
||||
tool_data.store_clicked_endpoint(document, input, preferences);
|
||||
tool_data.create_initial_point(document, input, responses, tool_options, input.keyboard.key(append_to_selected), preferences);
|
||||
|
||||
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
|
||||
|
|
@ -959,11 +1115,13 @@ impl Fsm for PenToolFsmState {
|
|||
|
||||
// Early return if the buffer was started and this message is being run again after the buffer (so that place_anchor updates the state with the newly merged vector)
|
||||
if tool_data.buffering_merged_vector {
|
||||
tool_data.buffering_merged_vector = false;
|
||||
tool_data.handle_mode = HandleMode::ColinearLocked;
|
||||
tool_data.bend_from_previous_point(SnapData::new(document, input), transform, layer.unwrap());
|
||||
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses);
|
||||
tool_data.buffering_merged_vector = false;
|
||||
if let Some(layer) = layer {
|
||||
tool_data.buffering_merged_vector = false;
|
||||
tool_data.handle_mode = HandleMode::ColinearLocked;
|
||||
tool_data.bend_from_previous_point(SnapData::new(document, input), transform, layer, preferences);
|
||||
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses);
|
||||
tool_data.buffering_merged_vector = false;
|
||||
}
|
||||
PenToolFsmState::DraggingHandle(tool_data.handle_mode)
|
||||
} else {
|
||||
if tool_data.handle_end.is_some() {
|
||||
|
|
@ -982,6 +1140,7 @@ impl Fsm for PenToolFsmState {
|
|||
merge_layers(document, current_layer, other_layer, responses);
|
||||
}
|
||||
}
|
||||
|
||||
// Even if no buffer was started, the message still has to be run again in order to call bend_from_previous_point
|
||||
tool_data.buffering_merged_vector = true;
|
||||
responses.add(PenToolMessage::DragStart { append_to_selected });
|
||||
|
|
@ -997,9 +1156,13 @@ impl Fsm for PenToolFsmState {
|
|||
}
|
||||
self
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => tool_data
|
||||
.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses)
|
||||
.unwrap_or(PenToolFsmState::PlacingAnchor),
|
||||
(PenToolFsmState::DraggingHandle(_), PenToolMessage::DragStop) => {
|
||||
tool_data.end_point = None;
|
||||
tool_data.draw_mode = DrawMode::ContinuePath;
|
||||
tool_data
|
||||
.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses)
|
||||
.unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
(
|
||||
PenToolFsmState::DraggingHandle(_),
|
||||
PenToolMessage::PointerMove {
|
||||
|
|
@ -1093,7 +1256,21 @@ impl Fsm for PenToolFsmState {
|
|||
|
||||
state
|
||||
}
|
||||
(PenToolFsmState::Ready, PenToolMessage::PointerMove { .. }) => {
|
||||
(
|
||||
PenToolFsmState::Ready,
|
||||
PenToolMessage::PointerMove {
|
||||
snap_angle,
|
||||
break_handle,
|
||||
lock_angle,
|
||||
colinear,
|
||||
},
|
||||
) => {
|
||||
tool_data.modifiers = ModifierState {
|
||||
snap_angle: input.keyboard.key(snap_angle),
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
colinear: input.keyboard.key(colinear),
|
||||
};
|
||||
tool_data.snap_manager.preview_draw(&SnapData::new(document, input), input.mouse.position);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
|
|
@ -1143,6 +1320,7 @@ impl Fsm for PenToolFsmState {
|
|||
(PenToolFsmState::DraggingHandle(..) | PenToolFsmState::PlacingAnchor, PenToolMessage::Confirm) => {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
tool_data.handle_end = None;
|
||||
tool_data.draw_mode = DrawMode::BreakPath;
|
||||
tool_data.latest_points.clear();
|
||||
tool_data.point_index = 0;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
|
@ -1154,6 +1332,7 @@ impl Fsm for PenToolFsmState {
|
|||
tool_data.handle_end = None;
|
||||
tool_data.latest_points.clear();
|
||||
tool_data.point_index = 0;
|
||||
tool_data.draw_mode = DrawMode::BreakPath;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
|
|
|||
Loading…
Reference in New Issue