mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 22:58:11 +08:00
Rename handle mirroring to colinear
This commit is contained in:
@@ -205,7 +205,7 @@ impl Fsm for EllipseToolFsmState {
|
||||
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
|
||||
let manipulator_groups = subpath.manipulator_groups().to_vec();
|
||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses);
|
||||
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, layer, true, responses);
|
||||
graph_modification_utils::set_manipulator_colinear_handles_state(&manipulator_groups, layer, true, responses);
|
||||
shape_data.layer = Some(layer);
|
||||
|
||||
let fill_color = tool_options.fill.active_color();
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::overlays::utility_functions::path_over
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_mirror_handles, get_subpaths};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_colinear_manipulators, get_manipulator_from_id, get_subpaths};
|
||||
use crate::messages::tool::common_functionality::shape_editor::{ClosestSegment, ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
||||
|
||||
@@ -34,19 +34,19 @@ pub enum PathToolMessage {
|
||||
Delete,
|
||||
DeleteAndBreakPath,
|
||||
DragStop {
|
||||
shift_mirror_distance: Key,
|
||||
equidistant: Key,
|
||||
},
|
||||
Enter {
|
||||
add_to_selection: Key,
|
||||
},
|
||||
Escape,
|
||||
FlipSharp,
|
||||
FlipSmoothSharp,
|
||||
GRS {
|
||||
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
|
||||
key: Key,
|
||||
},
|
||||
ManipulatorAngleMakeSharp,
|
||||
ManipulatorAngleMakeSmooth,
|
||||
ManipulatorMakeHandlesFree,
|
||||
ManipulatorMakeHandlesColinear,
|
||||
MouseDown {
|
||||
ctrl: Key,
|
||||
shift: Key,
|
||||
@@ -126,23 +126,37 @@ impl LayoutHolder for PathTool {
|
||||
let related_seperator = Separator::new(SeparatorType::Related).widget_holder();
|
||||
let unrelated_seperator = Separator::new(SeparatorType::Unrelated).widget_holder();
|
||||
|
||||
let manipulator_angle_options = vec![
|
||||
RadioEntryData::new("smooth").label("Smooth").on_update(|_| PathToolMessage::ManipulatorAngleMakeSmooth.into()),
|
||||
RadioEntryData::new("sharp").label("Sharp").on_update(|_| PathToolMessage::ManipulatorAngleMakeSharp.into()),
|
||||
];
|
||||
let manipulator_angle_index = manipulator_angle.and_then(|angle| match angle {
|
||||
ManipulatorAngle::Smooth => Some(0),
|
||||
ManipulatorAngle::Sharp => Some(1),
|
||||
let colinear_handles_tooltip = "Ensures both handles remain 180° apart";
|
||||
let colinear_handles_state = manipulator_angle.and_then(|angle| match angle {
|
||||
ManipulatorAngle::Colinear => Some(true),
|
||||
ManipulatorAngle::Free => Some(false),
|
||||
ManipulatorAngle::Mixed => None,
|
||||
});
|
||||
|
||||
let manipulator_angle_radio = RadioInput::new(manipulator_angle_options)
|
||||
})
|
||||
// TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state
|
||||
.unwrap_or_default();
|
||||
let colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state)
|
||||
.disabled(self.tool_data.selection_status.is_none())
|
||||
.selected_index(manipulator_angle_index)
|
||||
.on_update(|&CheckboxInput { checked, .. }| {
|
||||
if checked {
|
||||
PathToolMessage::ManipulatorMakeHandlesColinear.into()
|
||||
} else {
|
||||
PathToolMessage::ManipulatorMakeHandlesFree.into()
|
||||
}
|
||||
})
|
||||
.tooltip(colinear_handles_tooltip)
|
||||
.widget_holder();
|
||||
let colinear_handles_label = TextLabel::new("Colinear Handles").tooltip(colinear_handles_tooltip).widget_holder();
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||
widgets: vec![x_location, related_seperator, y_location, unrelated_seperator, manipulator_angle_radio],
|
||||
widgets: vec![
|
||||
x_location,
|
||||
related_seperator.clone(),
|
||||
y_location,
|
||||
unrelated_seperator,
|
||||
colinear_handle_checkbox,
|
||||
related_seperator,
|
||||
colinear_handles_label,
|
||||
],
|
||||
}]))
|
||||
}
|
||||
}
|
||||
@@ -164,7 +178,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(PathToolMessageDiscriminant;
|
||||
FlipSharp,
|
||||
FlipSmoothSharp,
|
||||
MouseDown,
|
||||
Delete,
|
||||
NudgeSelectedPoints,
|
||||
@@ -177,7 +191,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
Dragging => actions!(PathToolMessageDiscriminant;
|
||||
Escape,
|
||||
RightClick,
|
||||
FlipSharp,
|
||||
FlipSmoothSharp,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Delete,
|
||||
@@ -185,7 +199,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
DeleteAndBreakPath,
|
||||
),
|
||||
DrawingBox => actions!(PathToolMessageDiscriminant;
|
||||
FlipSharp,
|
||||
FlipSmoothSharp,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Delete,
|
||||
@@ -378,7 +392,7 @@ impl PathToolData {
|
||||
// Check if the alt key has just been pressed
|
||||
if alt && !self.alt_debounce {
|
||||
self.opposing_handle_lengths = None;
|
||||
shape_editor.toggle_handle_mirroring_on_selected(responses);
|
||||
shape_editor.toggle_colinear_handles_state_on_selected(responses);
|
||||
}
|
||||
self.alt_debounce = alt;
|
||||
|
||||
@@ -545,21 +559,21 @@ impl Fsm for PathToolFsmState {
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
// Mouse up
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { equidistant }) => {
|
||||
let equidistant = input.keyboard.get(equidistant as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !equidistant);
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
(_, PathToolMessage::DragStop { equidistant }) => {
|
||||
let equidistant = input.keyboard.get(equidistant as usize);
|
||||
|
||||
let nearest_point = shape_editor
|
||||
.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD)
|
||||
@@ -567,7 +581,7 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.metadata, &tool_data.opposing_handle_lengths, responses);
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !equidistant {
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
|
||||
if clicked_selected {
|
||||
shape_editor.deselect_all_points();
|
||||
@@ -598,9 +612,9 @@ impl Fsm for PathToolFsmState {
|
||||
shape_editor.delete_point_and_break_path(&document.network, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::FlipSharp) => {
|
||||
(_, PathToolMessage::FlipSmoothSharp) => {
|
||||
if !tool_data.double_click_handled {
|
||||
shape_editor.flip_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
shape_editor.flip_smooth_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
}
|
||||
self
|
||||
@@ -641,16 +655,16 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.selection_status = get_selection_status(&document.network, &document.metadata, shape_editor);
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
|
||||
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.set_handle_mirroring_on_selected(true, responses);
|
||||
shape_editor.smooth_selected_groups(responses, &document.network);
|
||||
shape_editor.set_colinear_handles_state_on_selected(true, responses);
|
||||
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, &document.network);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorAngleMakeSharp) => {
|
||||
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
shape_editor.set_handle_mirroring_on_selected(false, responses);
|
||||
shape_editor.set_colinear_handles_state_on_selected(false, responses);
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
@@ -663,6 +677,8 @@ impl Fsm for PathToolFsmState {
|
||||
PathToolFsmState::Ready => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]),
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
|
||||
// TODO: Only show if at least one anchor is selected, and dynamically show either "Smooth" or "Sharp" based on the current state
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDouble, "Make Anchor Smooth/Sharp")]),
|
||||
// TODO: Only show the following hints if at least one point is selected
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
|
||||
HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected")]),
|
||||
@@ -677,15 +693,13 @@ impl Fsm for PathToolFsmState {
|
||||
PathToolFsmState::Dragging => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![
|
||||
// TODO: Make hint dynamically say "Make Handle Smooth" or "Make Handle Sharp" based on its current state
|
||||
// TODO: Switch this to the "S" key
|
||||
// TODO: Only show this if a handle (not an anchor) is being dragged, and disable that shortcut so it can't be pressed even with the hint not shown
|
||||
HintInfo::keys([Key::Alt], "Toggle Smooth/Sharp Handles"),
|
||||
// TODO: Switch this to the "Alt" key (since it's equivalent to the "From Center" modifier when drawing a line)
|
||||
// TODO: Show this only when a handle is being dragged
|
||||
HintInfo::keys([Key::Shift], "Equidistant Handles (Smooth Only)"),
|
||||
// TODO: Add "Snap 15°" modifier with the "Shift" key (only when a handle is being dragged)
|
||||
// TODO: Add "Lock Angle" modifier with the "Ctrl" key (only when a handle is being dragged)
|
||||
// TODO: Switch this to the "S" key. Also, make the hint dynamically say "Make Colinear" or "Make Not Colinear" based on its current state. And only
|
||||
// TODO: show this hint if a handle (not an anchor) is being dragged, and disable that shortcut so it can't be pressed even with the hint not shown.
|
||||
HintInfo::keys([Key::Alt], "Toggle Colinear Handles"),
|
||||
// TODO: Switch this to the "Alt" key (since it's equivalent to the "From Center" modifier when drawing a line). And show this only when a handle is being dragged.
|
||||
HintInfo::keys([Key::Shift], "Equidistant Handles"),
|
||||
// TODO: Add "Snap 15°" modifier with the "Shift" key (only when a handle is being dragged).
|
||||
// TODO: Add "Lock Angle" modifier with the "Ctrl" key (only when a handle is being dragged).
|
||||
]),
|
||||
]),
|
||||
PathToolFsmState::DrawingBox => HintData(vec![
|
||||
@@ -761,28 +775,31 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
|
||||
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
|
||||
let Some(subpaths) = get_subpaths(layer, document_network) else {
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
let Some(mirror) = get_mirror_handles(layer, document_network) else {
|
||||
let Some(colinear_manipulators) = get_colinear_manipulators(layer, document_network) else {
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
let Some(point) = shape_state.selected_points().next() else {
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
|
||||
let Some(group) = get_manipulator_from_id(subpaths, point.group) else {
|
||||
let Some(manipulator) = get_manipulator_from_id(subpaths, point.group) else {
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
let Some(local_position) = point.manipulator_type.get_position(group) else {
|
||||
let Some(local_position) = point.manipulator_type.get_position(manipulator) else {
|
||||
return SelectionStatus::None;
|
||||
};
|
||||
|
||||
let manipulator_angle = if mirror.contains(&point.group) { ManipulatorAngle::Smooth } else { ManipulatorAngle::Sharp };
|
||||
let coordinates = document_metadata.transform_to_document(layer).transform_point2(local_position);
|
||||
let manipulator_angle = if colinear_manipulators.contains(&point.group) {
|
||||
ManipulatorAngle::Colinear
|
||||
} else {
|
||||
ManipulatorAngle::Free
|
||||
};
|
||||
|
||||
return SelectionStatus::One(SingleSelectedPoint {
|
||||
coordinates: document_metadata.transform_to_document(layer).transform_point2(local_position),
|
||||
coordinates,
|
||||
layer,
|
||||
id: *point,
|
||||
manipulator_angle,
|
||||
|
||||
@@ -200,7 +200,7 @@ struct PenToolData {
|
||||
layer: Option<LayerNodeIdentifier>,
|
||||
subpath_index: usize,
|
||||
snap_manager: SnapManager,
|
||||
should_mirror: bool,
|
||||
colinear_handles: bool,
|
||||
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
|
||||
from_start: bool,
|
||||
angle: f64,
|
||||
@@ -212,21 +212,16 @@ impl PenToolData {
|
||||
self.from_start = from_start;
|
||||
self.subpath_index = subpath_index;
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let Some(subpaths) = get_subpaths(layer, &document.network) else {
|
||||
return;
|
||||
};
|
||||
let Some(subpaths) = get_subpaths(layer, &document.network) else { return };
|
||||
let manipulator_groups = subpaths[subpath_index].manipulator_groups();
|
||||
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else {
|
||||
return;
|
||||
};
|
||||
let first_or_last = if from_start { manipulator_groups.first() } else { manipulator_groups.last() };
|
||||
let Some(last_handle) = first_or_last else { return };
|
||||
let id = last_handle.id;
|
||||
|
||||
// Stop the handles on the first point from being colinear
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
||||
id: last_handle.id,
|
||||
mirror_angle: false,
|
||||
},
|
||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -270,53 +265,56 @@ impl PenToolData {
|
||||
self.subpath_index = 0;
|
||||
}
|
||||
|
||||
// TODO: tooltip / user documentation?
|
||||
/// If you place the anchor on top of the previous anchor then you break the mirror
|
||||
fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<()> {
|
||||
// Get subpath
|
||||
let layer = self.layer?;
|
||||
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
|
||||
/// 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, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
(|| -> Option<()> {
|
||||
// Get subpath
|
||||
let layer = self.layer?;
|
||||
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
// Get correct handle types
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
let previous_anchor = previous_manipulator_group.anchor;
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
let previous_anchor = previous_manipulator_group.anchor;
|
||||
|
||||
// Break the control
|
||||
let transform = document.metadata.document_to_viewport * transform;
|
||||
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if !on_top {
|
||||
return None;
|
||||
}
|
||||
// Remove the point that has just been placed
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
// Break the control
|
||||
let transform = document.metadata.document_to_viewport * transform;
|
||||
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if !on_top {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Move the in handle of the previous anchor to on top of the previous position
|
||||
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
|
||||
});
|
||||
// Remove the point that has just been placed
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
|
||||
// Stop the handles on the last point from mirroring
|
||||
let id = previous_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
||||
});
|
||||
// Move the in handle of the previous anchor to on top of the previous position
|
||||
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
|
||||
});
|
||||
|
||||
self.should_mirror = false;
|
||||
None
|
||||
// Stop the handles on the last point from being colinear
|
||||
let id = previous_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
||||
});
|
||||
|
||||
self.colinear_handles = false;
|
||||
|
||||
None
|
||||
})();
|
||||
}
|
||||
|
||||
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
@@ -357,11 +355,11 @@ impl PenToolData {
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
|
||||
});
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
// Stop the handles on the first point from being colinear
|
||||
let id = first_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
||||
});
|
||||
|
||||
// Remove the point that has just been placed
|
||||
@@ -409,11 +407,11 @@ impl PenToolData {
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
|
||||
let should_mirror = !modifiers.break_handle && self.should_mirror;
|
||||
let colinear = !modifiers.break_handle && self.colinear_handles;
|
||||
|
||||
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
|
||||
let pos = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, should_mirror, mouse, Some(last_anchor), false);
|
||||
if !pos.is_finite() {
|
||||
let position = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, colinear, mouse, Some(last_anchor), false);
|
||||
if !position.is_finite() {
|
||||
return Some(PenToolFsmState::DraggingHandle);
|
||||
}
|
||||
|
||||
@@ -421,25 +419,25 @@ impl PenToolData {
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: self.layer?,
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
|
||||
// Mirror handle of last segment
|
||||
if should_mirror {
|
||||
// Place the previous anchor's in handle at the opposing position
|
||||
if colinear {
|
||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||
let pos = last_anchor - (pos - last_anchor);
|
||||
let position = last_anchor - (position - last_anchor);
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: self.layer?,
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mirror status of the currently modifying point
|
||||
// Update the colinear handles status of the currently modifying point
|
||||
let id = last_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: self.layer?,
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: should_mirror },
|
||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear },
|
||||
});
|
||||
|
||||
Some(PenToolFsmState::DraggingHandle)
|
||||
@@ -487,7 +485,7 @@ impl PenToolData {
|
||||
}
|
||||
|
||||
/// Snap the angle of the line from relative to position if the key is pressed.
|
||||
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, mirror: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
|
||||
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, colinear: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
|
||||
let document = snap_data.document;
|
||||
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
|
||||
let snap = &mut self.snap_manager;
|
||||
@@ -509,7 +507,7 @@ impl PenToolData {
|
||||
};
|
||||
let near_point = SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone());
|
||||
let far_point = SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors);
|
||||
if mirror {
|
||||
if colinear {
|
||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
|
||||
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None);
|
||||
document_pos = if snapped_far.other_snap_better(&snapped) {
|
||||
@@ -523,7 +521,7 @@ impl PenToolData {
|
||||
document_pos = snapped.snapped_point_document;
|
||||
snap.update_indicator(snapped);
|
||||
}
|
||||
} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| mirror) {
|
||||
} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| colinear) {
|
||||
let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()), None, false);
|
||||
let snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors), None, false);
|
||||
document_pos = if snapped_far.other_snap_better(&snapped) {
|
||||
@@ -641,8 +639,8 @@ impl Fsm for PenToolFsmState {
|
||||
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Disable this tool's mirroring
|
||||
tool_data.should_mirror = false;
|
||||
// Prevent the initial point from having a colinear in handle while dragging the out handle
|
||||
tool_data.colinear_handles = false;
|
||||
|
||||
// Perform extension of an existing path
|
||||
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||
@@ -663,11 +661,11 @@ impl Fsm for PenToolFsmState {
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
tool_data.check_break(document, transform, responses);
|
||||
tool_data.bend_from_previous_point(document, transform, responses);
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||
tool_data.should_mirror = true;
|
||||
tool_data.colinear_handles = true;
|
||||
tool_data.finish_placing_handle(document, transform, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||
@@ -677,6 +675,7 @@ impl Fsm for PenToolFsmState {
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
let snap_data = SnapData::new(document, input);
|
||||
|
||||
let state = tool_data
|
||||
.drag_handle(snap_data, transform, input.mouse.position, modifiers, responses)
|
||||
.unwrap_or(PenToolFsmState::Ready);
|
||||
@@ -772,6 +771,10 @@ impl Fsm for PenToolFsmState {
|
||||
]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Sharp Point"), HintInfo::mouse(MouseMotion::LmbDrag, "Add Smooth Point")]),
|
||||
HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::Lmb, ""),
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Bend from Prev. Point").prepend_slash(),
|
||||
]),
|
||||
]),
|
||||
PenToolFsmState::DraggingHandle => HintData(vec![
|
||||
HintGroup(vec![
|
||||
@@ -780,6 +783,7 @@ impl Fsm for PenToolFsmState {
|
||||
HintInfo::keys([Key::Enter], "End Path").prepend_slash(),
|
||||
]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
|
||||
// TODO: Only show this if the handle being dragged is colinear, so don't show this when bending from the previous point (by clicking and dragging from the previously placed anchor)
|
||||
HintGroup(vec![HintInfo::keys([Key::Alt], "Bend Handle")]),
|
||||
]),
|
||||
};
|
||||
|
||||
@@ -343,7 +343,7 @@ fn update_spline(tool_data: &SplineToolData, show_preview: bool, responses: &mut
|
||||
return;
|
||||
};
|
||||
|
||||
graph_modification_utils::set_manipulator_mirror_angle(subpath.manipulator_groups(), layer, true, responses);
|
||||
graph_modification_utils::set_manipulator_colinear_handles_state(subpath.manipulator_groups(), layer, true, responses);
|
||||
let subpaths = vec![subpath];
|
||||
let modification = VectorDataModification::UpdateSubpaths { subpaths };
|
||||
responses.add_front(GraphOperationMessage::Vector { layer, modification });
|
||||
|
||||
Reference in New Issue
Block a user