Skip to content

Commit 2e4fb95

Browse files
0SlowPoke0Keavon
andauthored
Add Path tool support for G/R/S rotation and scaling with a single selected handle (#2180)
* grab_scale_path and backspace for pen * minor improvements and fixes * code-review changes * Avoid more nesting, and other code cleanup --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent 9ad6c31 commit 2e4fb95

File tree

6 files changed

+119
-31
lines changed

6 files changed

+119
-31
lines changed

editor/src/messages/input_mapper/input_mappings.rs

+2
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ pub fn input_mappings() -> Mapping {
258258
entry!(KeyDown(MouseRight); action_dispatch=PenToolMessage::Confirm),
259259
entry!(KeyDown(Escape); action_dispatch=PenToolMessage::Confirm),
260260
entry!(KeyDown(Enter); action_dispatch=PenToolMessage::Confirm),
261+
entry!(KeyDown(Delete); action_dispatch=PenToolMessage::RemovePreviousHandle),
262+
entry!(KeyDown(Backspace); action_dispatch=PenToolMessage::RemovePreviousHandle),
261263
//
262264
// FreehandToolMessage
263265
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),

editor/src/messages/tool/tool_message_handler.rs

+9-4
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
7676
self.tool_is_active = true;
7777

7878
// Send the old and new tools a transition to their FSM Abort states
79-
let mut send_abort_to_tool = |tool_type, update_hints_and_cursor: bool| {
80-
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
79+
let mut send_abort_to_tool = |old_tool: ToolType, new_tool: ToolType, update_hints_and_cursor: bool| {
80+
if let Some(tool) = tool_data.tools.get_mut(&new_tool) {
8181
let mut data = ToolActionHandlerData {
8282
document,
8383
document_id,
@@ -101,9 +101,14 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
101101
tool.process_message(ToolMessage::UpdateCursor, responses, &mut data);
102102
}
103103
}
104+
105+
if matches!(old_tool, ToolType::Path | ToolType::Select) {
106+
responses.add(TransformLayerMessage::CancelTransformOperation);
107+
}
104108
};
105-
send_abort_to_tool(tool_type, true);
106-
send_abort_to_tool(old_tool, false);
109+
110+
send_abort_to_tool(old_tool, tool_type, true);
111+
send_abort_to_tool(old_tool, old_tool, false);
107112

108113
// Unsubscribe old tool from the broadcaster
109114
tool_data.tools.get(&tool_type).unwrap().deactivate(responses);

editor/src/messages/tool/tool_messages/path_tool.rs

+1-13
Original file line numberDiff line numberDiff line change
@@ -648,19 +648,7 @@ impl Fsm for PathToolFsmState {
648648
self
649649
}
650650
(Self::InsertPoint, PathToolMessage::Escape | PathToolMessage::Delete | PathToolMessage::RightClick) => tool_data.end_insertion(shape_editor, responses, InsertEndKind::Abort),
651-
(Self::InsertPoint, PathToolMessage::GRS { key: propagate }) => {
652-
// MAYBE: use `InputMapperMessage::KeyDown(..)` instead
653-
match propagate {
654-
// TODO: Don't use `Key::G` directly, instead take it as a variable from the input mappings list like in all other places
655-
Key::KeyG => responses.add(TransformLayerMessage::BeginGrab),
656-
// TODO: Don't use `Key::R` directly, instead take it as a variable from the input mappings list like in all other places
657-
Key::KeyR => responses.add(TransformLayerMessage::BeginRotate),
658-
// TODO: Don't use `Key::S` directly, instead take it as a variable from the input mappings list like in all other places
659-
Key::KeyS => responses.add(TransformLayerMessage::BeginScale),
660-
_ => warn!("Unexpected GRS key"),
661-
}
662-
tool_data.end_insertion(shape_editor, responses, InsertEndKind::Abort)
663-
}
651+
(Self::InsertPoint, PathToolMessage::GRS { key: _ }) => PathToolFsmState::InsertPoint,
664652
// Mouse down
665653
(
666654
_,

editor/src/messages/tool/tool_messages/pen_tool.rs

+11
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub enum PenToolMessage {
6262
Undo,
6363
UpdateOptions(PenOptionsUpdate),
6464
RecalculateLatestPointsPosition,
65+
RemovePreviousHandle,
6566
}
6667

6768
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@@ -175,6 +176,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
175176
PointerMove,
176177
Confirm,
177178
Abort,
179+
RemovePreviousHandle,
178180
),
179181
}
180182
}
@@ -685,6 +687,15 @@ impl Fsm for PenToolFsmState {
685687
PenToolFsmState::PlacingAnchor
686688
}
687689
}
690+
(PenToolFsmState::PlacingAnchor, PenToolMessage::RemovePreviousHandle) => {
691+
if let Some(last_point) = tool_data.latest_points.last_mut() {
692+
last_point.handle_start = last_point.pos;
693+
responses.add(OverlaysMessage::Draw);
694+
} else {
695+
log::warn!("No latest point available to modify handle_start.");
696+
}
697+
self
698+
}
688699
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => tool_data
689700
.finish_placing_handle(SnapData::new(document, input), transform, responses)
690701
.unwrap_or(PenToolFsmState::PlacingAnchor),

editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs

+82-14
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessa
4343
impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler {
4444
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
4545
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
46+
let using_select_tool = tool_data.active_tool_type == ToolType::Select;
4647

4748
// TODO: Add support for transforming layer not in the document network
4849
let selected_layers = document
@@ -75,10 +76,18 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
7576
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
7677

7778
let mut point_count: usize = 0;
78-
let get_location = |point: &ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
79+
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
7980
let points = shape_editor.selected_points();
80-
81-
*selected.pivot = points.filter_map(get_location).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
81+
let selected_points: Vec<&ManipulatorPointId> = points.collect();
82+
83+
if let [point] = selected_points.as_slice() {
84+
if let ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) = point {
85+
let anchor_position = point.get_anchor_position(&vector_data).unwrap();
86+
*selected.pivot = viewspace.transform_point2(anchor_position);
87+
} else {
88+
*selected.pivot = selected_points.iter().filter_map(get_location).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
89+
}
90+
}
8291
}
8392
} else {
8493
*selected.pivot = selected.mean_average_of_pivots();
@@ -104,12 +113,13 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
104113
responses.add(NodeGraphMessage::RunDocumentGraph);
105114
}
106115
TransformLayerMessage::BeginGrab => {
107-
if let TransformOperation::Grabbing(_) = self.transform_operation {
108-
return;
109-
}
116+
if (!using_path_tool && !using_select_tool)
117+
|| (using_path_tool && shape_editor.selected_points().next().is_none())
118+
|| selected_layers.is_empty()
119+
|| matches!(self.transform_operation, TransformOperation::Grabbing(_))
120+
{
121+
selected.original_transforms.clear();
110122

111-
// Don't allow grab with no selected layers
112-
if selected_layers.is_empty() {
113123
return;
114124
}
115125

@@ -120,13 +130,42 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
120130
selected.original_transforms.clear();
121131
}
122132
TransformLayerMessage::BeginRotate => {
123-
if let TransformOperation::Rotating(_) = self.transform_operation {
133+
let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect();
134+
135+
if (!using_path_tool && !using_select_tool)
136+
|| (using_path_tool && selected_points.is_empty())
137+
|| selected_layers.is_empty()
138+
|| matches!(self.transform_operation, TransformOperation::Rotating(_))
139+
{
140+
selected.original_transforms.clear();
124141
return;
125142
}
126143

127-
// Don't allow rotate with no selected layers
128-
if selected_layers.is_empty() {
144+
let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) else {
145+
selected.original_transforms.clear();
129146
return;
147+
};
148+
149+
if let [point] = selected_points.as_slice() {
150+
if matches!(point, ManipulatorPointId::Anchor(_)) {
151+
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
152+
let handle1_length = handle1.length(&vector_data);
153+
let handle2_length = handle2.length(&vector_data);
154+
155+
if (handle1_length == 0. && handle2_length == 0.) || (handle1_length == f64::MAX && handle2_length == f64::MAX) {
156+
return;
157+
}
158+
}
159+
} else {
160+
// TODO: Fix handle snap to anchor issue, see <https://discord.com/channels/731730685944922173/1217752903209713715>
161+
162+
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
163+
164+
if handle_length == Some(0.) {
165+
selected.original_transforms.clear();
166+
return;
167+
}
168+
}
130169
}
131170

132171
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
@@ -136,13 +175,41 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
136175
selected.original_transforms.clear();
137176
}
138177
TransformLayerMessage::BeginScale => {
139-
if let TransformOperation::Scaling(_) = self.transform_operation {
178+
let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect();
179+
180+
if (using_path_tool && selected_points.is_empty())
181+
|| (!using_path_tool && !using_select_tool)
182+
|| selected_layers.is_empty()
183+
|| matches!(self.transform_operation, TransformOperation::Scaling(_))
184+
{
185+
selected.original_transforms.clear();
140186
return;
141187
}
142188

143-
// Don't allow scale with no selected layers
144-
if selected_layers.is_empty() {
189+
let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) else {
190+
selected.original_transforms.clear();
145191
return;
192+
};
193+
194+
if let [point] = selected_points.as_slice() {
195+
if matches!(point, ManipulatorPointId::Anchor(_)) {
196+
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
197+
let handle1_length = handle1.length(&vector_data);
198+
let handle2_length = handle2.length(&vector_data);
199+
200+
if (handle1_length == 0. && handle2_length == 0.) || (handle1_length == f64::MAX && handle2_length == f64::MAX) {
201+
selected.original_transforms.clear();
202+
return;
203+
}
204+
}
205+
} else {
206+
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
207+
208+
if handle_length == Some(0.) {
209+
selected.original_transforms.clear();
210+
return;
211+
}
212+
}
146213
}
147214

148215
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
@@ -215,6 +282,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
215282
}
216283
};
217284
}
285+
218286
self.mouse_position = input.mouse.position;
219287
}
220288
TransformLayerMessage::SelectionChanged => {

node-graph/gcore/src/vector/vector_data.rs

+14
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,13 @@ impl ManipulatorPointId {
306306
}
307307
}
308308

309+
pub fn get_anchor_position(&self, vector_data: &VectorData) -> Option<DVec2> {
310+
match self {
311+
ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector_data).and_then(|id| vector_data.point_domain.position_from_id(id)),
312+
_ => self.get_position(vector_data),
313+
}
314+
}
315+
309316
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
310317
#[must_use]
311318
pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> {
@@ -396,6 +403,13 @@ impl HandleId {
396403
}
397404
}
398405

406+
/// Calculate the magnitude of the handle from the anchor.
407+
pub fn length(self, vector_data: &VectorData) -> f64 {
408+
let anchor_position = self.to_manipulator_point().get_anchor_position(vector_data).unwrap();
409+
let handle_position = self.to_manipulator_point().get_position(vector_data);
410+
handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)
411+
}
412+
399413
/// Set the handle's position relative to the anchor which is the start anchor for the primary handle and end anchor for the end handle.
400414
#[must_use]
401415
pub fn set_relative_position(self, relative_position: DVec2) -> VectorModificationType {

0 commit comments

Comments
 (0)