Skip to content

Commit d084775

Browse files
otdaviesKeavon
authored andcommitted
Add Pen Tool (#536)
* First steps toward pen tool, similar to spline tool currently. * Broken WIP * Progress, but still glitchy * Improvements, a little farther * Pen tool functioning as expected * Merged master * Fixed commit bug and overlay flashing * Reordered import statements TODO: Resolve issue with last segment losing its handle position on confirm
1 parent f8c2be8 commit d084775

File tree

9 files changed

+277
-98
lines changed

9 files changed

+277
-98
lines changed

editor/src/viewport_tools/tool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ pub fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageTy
200200
ToolType::BlurSharpen => None, // Some(BlurSharpenMessage::DocumentIsDirty.into()),
201201
ToolType::Relight => None, // Some(RelightMessage::DocumentIsDirty.into()),
202202
ToolType::Path => Some(PathMessage::DocumentIsDirty.into()),
203-
ToolType::Pen => None, // Some(PenMessage::DocumentIsDirty.into()),
203+
ToolType::Pen => Some(PenMessage::DocumentIsDirty.into()),
204204
ToolType::Freehand => None, // Some(FreehandMessage::DocumentIsDirty.into()),
205205
ToolType::Spline => None, // Some(SplineMessage::DocumentIsDirty.into()),
206206
ToolType::Line => None, // Some(LineMessage::DocumentIsDirty.into()),

editor/src/viewport_tools/tools/path.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ impl Default for PathToolFsmState {
9494
struct PathToolData {
9595
shape_editor: ShapeEditor,
9696
snap_handler: SnapHandler,
97+
98+
drag_start_pos: DVec2,
9799
alt_debounce: bool,
98100
shift_debounce: bool,
99101
}
@@ -151,6 +153,7 @@ impl Fsm for PathToolFsmState {
151153
.map(|point| point.position)
152154
.collect();
153155
data.snap_handler.add_snap_points(document, snap_points);
156+
data.drag_start_pos = input.mouse.position;
154157
Dragging
155158
}
156159
// We didn't find a point nearby, so consider selecting the nearest shape instead
@@ -207,7 +210,7 @@ impl Fsm for PathToolFsmState {
207210

208211
// Move the selected points by the mouse position
209212
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
210-
data.shape_editor.move_selected_points(snapped_position, responses);
213+
data.shape_editor.move_selected_points(snapped_position - data.drag_start_pos, true, responses);
211214
Dragging
212215
}
213216
// Mouse up

editor/src/viewport_tools/tools/pen.rs

Lines changed: 121 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::consts::DRAG_THRESHOLD;
21
use crate::document::DocumentMessageHandler;
32
use crate::frontend::utility_types::MouseCursorIcon;
43
use crate::input::keyboard::{Key, MouseMotion};
@@ -8,11 +7,14 @@ use crate::message_prelude::*;
87
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
98
use crate::viewport_tools::snapping::SnapHandler;
109
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
10+
use crate::viewport_tools::vector_editor::shape_editor::ShapeEditor;
11+
use crate::viewport_tools::vector_editor::vector_shape::VectorShape;
1112

1213
use graphene::layers::style;
1314
use graphene::Operation;
1415

1516
use glam::{DAffine2, DVec2};
17+
use kurbo::{PathEl, Point};
1618
use serde::{Deserialize, Serialize};
1719

1820
#[derive(Default)]
@@ -38,6 +40,8 @@ impl Default for PenOptions {
3840
pub enum PenMessage {
3941
// Standard messages
4042
#[remain::unsorted]
43+
DocumentIsDirty,
44+
#[remain::unsorted]
4145
Abort,
4246

4347
// Tool-specific messages
@@ -111,7 +115,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Pen {
111115

112116
match self.fsm_state {
113117
Ready => actions!(PenMessageDiscriminant; Undo, DragStart, DragStop, Confirm, Abort),
114-
Drawing => actions!(PenMessageDiscriminant; DragStop, PointerMove, Confirm, Abort),
118+
Drawing => actions!(PenMessageDiscriminant; DragStart, DragStop, PointerMove, Confirm, Abort),
115119
}
116120
}
117121
}
@@ -123,11 +127,12 @@ impl Default for PenToolFsmState {
123127
}
124128
#[derive(Clone, Debug, Default)]
125129
struct PenToolData {
126-
points: Vec<DVec2>,
127-
next_point: DVec2,
128130
weight: u32,
129131
path: Option<Vec<LayerId>>,
132+
curve_shape: VectorShape,
133+
bez_path: Vec<PathEl>,
130134
snap_handler: SnapHandler,
135+
shape_editor: ShapeEditor,
131136
}
132137

133138
impl Fsm for PenToolFsmState {
@@ -151,67 +156,92 @@ impl Fsm for PenToolFsmState {
151156

152157
if let ToolMessage::Pen(event) = event {
153158
match (self, event) {
159+
(_, DocumentIsDirty) => {
160+
data.shape_editor.update_shapes(document, responses);
161+
self
162+
}
154163
(Ready, DragStart) => {
155164
responses.push_back(DocumentMessage::StartTransaction.into());
156165
responses.push_back(DocumentMessage::DeselectAllLayers.into());
157-
data.path = Some(document.get_path_for_new_layer());
158166

167+
// Create a new layer and prep snap system
168+
data.path = Some(document.get_path_for_new_layer());
159169
data.snap_handler.start_snap(document, document.bounding_boxes(None, None), true, true);
160170
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
161171

162-
let pos = transform.inverse().transform_point2(snapped_position);
163-
164-
data.points.push(pos);
165-
data.next_point = pos;
166-
172+
// Get the position and set properties
173+
let start_position = transform.inverse().transform_point2(snapped_position);
167174
data.weight = tool_options.line_weight;
168175

169-
responses.push_back(add_polyline(data, tool_data, true));
176+
// Create the initial shape with a bez_path (only contains a moveto initially)
177+
if let Some(layer_path) = &data.path {
178+
data.bez_path = start_bez_path(start_position);
179+
responses.push_back(
180+
Operation::AddShape {
181+
path: layer_path.clone(),
182+
transform: transform.to_cols_array(),
183+
insert_index: -1,
184+
bez_path: data.bez_path.clone().into_iter().collect(),
185+
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight as f32)), None),
186+
closed: false,
187+
}
188+
.into(),
189+
);
190+
}
170191

192+
add_to_curve(data, input, transform, document, responses);
193+
Drawing
194+
}
195+
(Drawing, DragStart) => {
196+
add_to_curve(data, input, transform, document, responses);
171197
Drawing
172198
}
173199
(Drawing, DragStop) => {
174-
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
175-
let pos = transform.inverse().transform_point2(snapped_position);
200+
// Deselect everything (this means we are no longer dragging the handle)
201+
data.shape_editor.deselect_all(responses);
176202

177-
if let Some(last_pos) = data.points.last() {
178-
if last_pos.distance(pos) > DRAG_THRESHOLD {
179-
data.points.push(pos);
180-
data.next_point = pos;
181-
}
203+
// Reselect the last point
204+
if let Some(last_anchor) = data.shape_editor.select_last_anchor() {
205+
last_anchor.select_point(0, true, responses);
182206
}
183207

184-
responses.push_back(remove_preview(data));
185-
responses.push_back(add_polyline(data, tool_data, true));
186-
187208
Drawing
188209
}
189210
(Drawing, PointerMove) => {
190211
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
191-
let pos = transform.inverse().transform_point2(snapped_position);
192-
data.next_point = pos;
193-
194-
responses.push_back(remove_preview(data));
195-
responses.push_back(add_polyline(data, tool_data, true));
212+
//data.shape_editor.update_shapes(document, responses);
213+
data.shape_editor.move_selected_points(snapped_position, false, responses);
196214

197215
Drawing
198216
}
199217
(Drawing, Confirm) | (Drawing, Abort) => {
200-
if data.points.len() >= 2 {
218+
// Add a curve to the path
219+
if let Some(layer_path) = &data.path {
220+
remove_curve_from_end(&mut data.bez_path);
221+
responses.push_back(apply_bez_path(layer_path.clone(), data.bez_path.clone(), transform));
222+
}
223+
224+
// Cleanup, we are either canceling or finished drawing
225+
if data.bez_path.len() >= 2 {
201226
responses.push_back(DocumentMessage::DeselectAllLayers.into());
202-
responses.push_back(remove_preview(data));
203-
responses.push_back(add_polyline(data, tool_data, false));
204227
responses.push_back(DocumentMessage::CommitTransaction.into());
205228
} else {
206229
responses.push_back(DocumentMessage::AbortTransaction.into());
207230
}
208231

232+
data.shape_editor.remove_overlays(responses);
233+
data.shape_editor.clear_shapes_to_modify();
234+
209235
data.path = None;
210-
data.points.clear();
211236
data.snap_handler.cleanup(responses);
212237

213238
Ready
214239
}
240+
(_, Abort) => {
241+
data.shape_editor.remove_overlays(responses);
242+
data.shape_editor.clear_shapes_to_modify();
243+
Ready
244+
}
215245
_ => self,
216246
}
217247
} else {
@@ -251,22 +281,70 @@ impl Fsm for PenToolFsmState {
251281
}
252282
}
253283

254-
fn remove_preview(data: &PenToolData) -> Message {
255-
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
256-
}
284+
// Add to the curve and select the second anchor of the last point and the newly added anchor point
285+
fn add_to_curve(data: &mut PenToolData, input: &InputPreprocessorMessageHandler, transform: DAffine2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
286+
// We need to make sure we have the most up-to-date bez_path
287+
// Would like to remove this hack eventually
288+
if !data.shape_editor.shapes_to_modify.is_empty() {
289+
// Hacky way of saving the curve changes
290+
data.bez_path = data.shape_editor.shapes_to_modify[0].bez_path.elements().to_vec();
291+
}
292+
293+
// Setup our position params
294+
let snapped_position = data.snap_handler.snap_position(responses, input.viewport_bounds.size(), document, input.mouse.position);
295+
let position = transform.inverse().transform_point2(snapped_position);
296+
297+
// Add a curve to the path
298+
if let Some(layer_path) = &data.path {
299+
add_curve_to_end(position, &mut data.bez_path);
300+
responses.push_back(apply_bez_path(layer_path.clone(), data.bez_path.clone(), transform));
301+
302+
// Clear previous overlays
303+
data.shape_editor.remove_overlays(responses);
304+
305+
// Create a new shape from the updated bez_path
306+
let bez_path = data.bez_path.clone().into_iter().collect();
307+
data.curve_shape = VectorShape::new(layer_path.to_vec(), transform, &bez_path, false, responses);
308+
data.shape_editor.set_shapes_to_modify(vec![data.curve_shape.clone()]);
309+
310+
// Select the second to last segment's handle
311+
data.shape_editor.set_shape_selected(0);
312+
let handle_element = data.shape_editor.select_nth_anchor(0, -2);
313+
handle_element.select_point(2, true, responses);
257314

258-
fn add_polyline(data: &PenToolData, tool_data: &DocumentToolData, show_preview: bool) -> Message {
259-
let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x, p.y)).collect();
260-
if show_preview {
261-
points.push((data.next_point.x, data.next_point.y))
315+
// Select the last segment's anchor point
316+
if let Some(last_anchor) = data.shape_editor.select_last_anchor() {
317+
last_anchor.select_point(0, true, responses);
318+
}
319+
data.shape_editor.set_selected_mirror_options(true, true);
262320
}
321+
}
322+
323+
// Create the initial moveto for the bez_path
324+
fn start_bez_path(start_position: DVec2) -> Vec<PathEl> {
325+
vec![PathEl::MoveTo(Point {
326+
x: start_position.x,
327+
y: start_position.y,
328+
})]
329+
}
330+
331+
// Add a curve to the bez_path
332+
fn add_curve_to_end(point: DVec2, bez_path: &mut Vec<PathEl>) {
333+
let point = Point { x: point.x, y: point.y };
334+
bez_path.push(PathEl::CurveTo(point, point, point));
335+
}
336+
337+
// Add a curve to the bez_path
338+
fn remove_curve_from_end(bez_path: &mut Vec<PathEl>) {
339+
bez_path.pop();
340+
}
263341

264-
Operation::AddPolyline {
265-
path: data.path.clone().unwrap(),
266-
insert_index: -1,
267-
transform: DAffine2::IDENTITY.to_cols_array(),
268-
points,
269-
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight as f32)), None),
342+
// Apply the bez_path to the shape in the viewport
343+
fn apply_bez_path(layer_path: Vec<LayerId>, bez_path: Vec<PathEl>, transform: DAffine2) -> Message {
344+
Operation::SetShapePathInViewport {
345+
path: layer_path,
346+
bez_path: bez_path.into_iter().collect(),
347+
transform: transform.to_cols_array(),
270348
}
271349
.into()
272350
}

0 commit comments

Comments
 (0)