Skip to content

Commit a31387e

Browse files
committed
#82 path-tool: WIP selecting control point working
1 parent 37be856 commit a31387e

File tree

3 files changed

+127
-13
lines changed

3 files changed

+127
-13
lines changed

editor/src/document/document_file.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ pub enum VectorManipulatorSegment {
5151

5252
#[derive(PartialEq, Clone, Debug)]
5353
pub struct VectorManipulatorShape {
54+
/// The outline of the shape.
5455
pub path: kurbo::BezPath,
56+
/// The control points / manipulator handles.
5557
pub segments: Vec<VectorManipulatorSegment>,
5658
pub transform: DAffine2,
5759
}

editor/src/input/input_mapper.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ impl Default for Mapping {
179179
entry! {action=LineMessage::Abort, key_down=Rmb},
180180
entry! {action=LineMessage::Abort, key_down=KeyEscape},
181181
entry! {action=LineMessage::Redraw{center: KeyAlt, lock_angle: KeyControl, snap_angle: KeyShift}, triggers=[KeyAlt, KeyShift, KeyControl]},
182+
// Path
183+
entry! {action=PathMessage::MouseDown, key_down=Lmb},
182184
// Pen
183185
entry! {action=PenMessage::PointerMove, message=InputMapperMessage::PointerMove},
184186
entry! {action=PenMessage::DragStart, key_down=Lmb},

editor/src/tool/tools/path.rs

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct Path {
2525
#[impl_message(Message, ToolMessage, Path)]
2626
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
2727
pub enum PathMessage {
28+
MouseDown,
2829
RedrawOverlay,
2930
Abort,
3031
}
@@ -33,12 +34,15 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
3334
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
3435
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
3536
}
36-
fn actions(&self) -> ActionList {
37-
use PathToolFsmState::*;
38-
match self.fsm_state {
39-
Ready => actions!(PathMessageDiscriminant;),
40-
}
41-
}
37+
38+
// different actions depending on state may be wanted:
39+
// fn actions(&self) -> ActionList {
40+
// use PathToolFsmState::*;
41+
// match self.fsm_state {
42+
// Ready => actions!(PathMessageDiscriminant; MouseDown),
43+
// }
44+
// }
45+
advertise_actions!(PathMessageDiscriminant; MouseDown);
4246
}
4347

4448
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -71,7 +75,7 @@ impl Fsm for PathToolFsmState {
7175
document: &DocumentMessageHandler,
7276
_tool_data: &DocumentToolData,
7377
data: &mut Self::ToolData,
74-
_input: &InputPreprocessor,
78+
input: &InputPreprocessor,
7579
responses: &mut VecDeque<Message>,
7680
) -> Self {
7781
if let ToolMessage::Path(event) = event {
@@ -141,30 +145,28 @@ impl Fsm for PathToolFsmState {
141145

142146
// Draw the draggable square points on the end of every line segment or bezier curve segment
143147
for anchor in anchors {
144-
let marker = data.anchor_marker_pool[anchor_i].clone();
145-
146148
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
147149
let angle = 0.;
148150
let translation = (anchor - (scale / 2.) + BIAS).round();
149151
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
150152

153+
let marker = &data.anchor_marker_pool[anchor_i];
151154
responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into());
152-
responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into());
153-
155+
responses.push_back(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into());
154156
anchor_i += 1;
155157
}
156158

157159
// Draw the draggable handle for cubic and quadratic bezier segments
158160
for handle in handles {
159-
let marker = data.handle_marker_pool[handle_i].clone();
161+
let marker = &data.handle_marker_pool[handle_i];
160162

161163
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
162164
let angle = 0.;
163165
let translation = (handle - (scale / 2.) + BIAS).round();
164166
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
165167

166168
responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into());
167-
responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into());
169+
responses.push_back(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into());
168170

169171
handle_i += 1;
170172
}
@@ -191,6 +193,114 @@ impl Fsm for PathToolFsmState {
191193

192194
self
193195
}
196+
(_, MouseDown) => {
197+
// todo: DRY refactor (this arm is very similar to the (_, RedrawOverlay) arm)
198+
// WIP: selecting control point working
199+
// next: correctly modifying path
200+
// to test: E, make ellipse, A, click control point, see it change color
201+
// todo: use cargo clippy?
202+
203+
let mouse_pos = input.mouse.position;
204+
let mut points = Vec::new();
205+
206+
let (mut anchor_i, mut handle_i, _line_i, mut shape_i) = (0, 0, 0, 0);
207+
let shapes_to_draw = document.selected_layers_vector_points();
208+
let (total_anchors, total_handles, _total_anchor_handle_lines) = calculate_total_overlays_per_type(&shapes_to_draw);
209+
grow_overlay_pool_entries(&mut data.anchor_marker_pool, total_anchors, add_anchor_marker, responses);
210+
grow_overlay_pool_entries(&mut data.handle_marker_pool, total_handles, add_handle_marker, responses);
211+
212+
#[derive(Debug)]
213+
enum PointRef {
214+
Anchor(usize),
215+
Handle(usize),
216+
}
217+
#[derive(Debug)]
218+
struct Point(DVec2, PointRef, f64);
219+
220+
// todo: use const?
221+
// use crate::consts::SELECTION_TOLERANCE;
222+
// let tolerance = DVec2::splat(SELECTION_TOLERANCE);
223+
// let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
224+
let select_threshold = 6.;
225+
let select_threshold_squared = select_threshold * select_threshold;
226+
227+
for shape_to_draw in &shapes_to_draw {
228+
let shape_layer_path = &data.shape_outline_pool[shape_i];
229+
shape_i += 1;
230+
231+
let bez = {
232+
use kurbo::PathEl;
233+
let mut bez: Vec<_> = shape_to_draw.path.clone().into_iter().collect();
234+
for a in &mut bez {
235+
match a {
236+
PathEl::LineTo(p) => {
237+
p.x += 5.;
238+
}
239+
_ => {}
240+
}
241+
}
242+
let bez: BezPath = bez.into_iter().collect();
243+
bez
244+
};
245+
246+
// todo: change path correctly
247+
responses.push_back(
248+
Operation::SetShapePathInViewport {
249+
path: shape_layer_path.clone(),
250+
bez_path: bez,
251+
transform: shape_to_draw.transform.to_cols_array(),
252+
}
253+
.into(),
254+
);
255+
256+
for segment in &shape_to_draw.segments {
257+
// TODO: We draw each anchor point twice because segment has it on both ends, fix this
258+
// TODO: DRY, see above
259+
let (anchors, handles, _anchor_handle_lines) = match segment {
260+
VectorManipulatorSegment::Line(a1, a2) => (vec![*a1, *a2], vec![], vec![]),
261+
VectorManipulatorSegment::Quad(a1, h1, a2) => (vec![*a1, *a2], vec![*h1], vec![(*h1, *a1)]),
262+
VectorManipulatorSegment::Cubic(a1, h1, h2, a2) => (vec![*a1, *a2], vec![*h1, *h2], vec![(*h1, *a1), (*h2, *a2)]),
263+
};
264+
265+
for anchor in anchors {
266+
let d2 = mouse_pos.distance_squared(anchor.into());
267+
if d2 < select_threshold_squared {
268+
points.push(Point(anchor.clone(), PointRef::Anchor(anchor_i), d2));
269+
}
270+
anchor_i += 1;
271+
}
272+
for handle in handles {
273+
let d2 = mouse_pos.distance_squared(handle.into());
274+
if d2 < select_threshold_squared {
275+
points.push(Point(handle.clone(), PointRef::Handle(handle_i), d2));
276+
}
277+
handle_i += 1;
278+
}
279+
}
280+
}
281+
282+
points.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal));
283+
let closest_point_within_click_threshold = points.first();
284+
// log::debug!("PATH TOOL: {:?} {:?}", mouse_pos, points);
285+
286+
if let Some(point) = closest_point_within_click_threshold {
287+
let path = match point.1 {
288+
PointRef::Anchor(i) => data.anchor_marker_pool[i].clone(),
289+
PointRef::Handle(i) => data.handle_marker_pool[i].clone(),
290+
};
291+
// todo: use Operation::SetShapePathInViewport instead
292+
// currently using SetLayerFill just to show some effect
293+
responses.push_back(
294+
Operation::SetLayerFill {
295+
path,
296+
color: Color::from_rgb8(if mouse_pos.x > 500. { 0 } else { 255 }, if mouse_pos.x > 500. { 255 } else { 0 }, 0),
297+
}
298+
.into(),
299+
);
300+
}
301+
302+
self
303+
}
194304
(_, Abort) => {
195305
// Destory the overlay layer pools
196306
while let Some(layer) = data.anchor_marker_pool.pop() {

0 commit comments

Comments
 (0)