Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion editor/src/messages/input_mapper/default_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,13 @@ pub fn default_mapping() -> Mapping {
entry!(KeyUp(Lmb); action_dispatch=LineToolMessage::DragStop),
entry!(KeyDown(Rmb); action_dispatch=LineToolMessage::Abort),
entry!(KeyDown(Escape); action_dispatch=LineToolMessage::Abort),
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=LineToolMessage::PointerMove { center: Alt, lock_angle: Control, snap_angle: Shift }),
entry!(PointerMove; refresh_keys=[Alt, Control, Shift], action_dispatch=LineToolMessage::PointerMove { center: Alt, lock_angle: Control, snap_angle: Shift }),
//
// PathToolMessage
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
entry!(KeyDown(Delete); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Backspace); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Lmb); action_dispatch=PathToolMessage::DragStart { add_to_selection: Shift }),
entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=PathToolMessage::PointerMove { alt: Alt, shift: Shift }),
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
Expand Down
141 changes: 141 additions & 0 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,147 @@ impl ShapeState {
}
}

pub fn break_path_at_selected_point(&self, document_network: &NodeNetwork, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(subpaths) = get_subpaths(layer, document_network) else {
continue;
};

let mut broken_subpaths = Vec::<bezier_rs::Subpath<ManipulatorGroupId>>::new();

for subpath in subpaths {
let mut points: Vec<_> = state
.selected_points
.iter()
.filter_map(|&point| {
let Some(manipulator_index) = subpath.manipulator_index_from_id(point.group) else {
return None;
};
let Some(manipulator) = subpath.manipulator_from_id(point.group) else {
return None;
};
Some((manipulator_index, manipulator))
})
.collect();

if points.is_empty() {
broken_subpaths.push(subpath.clone());
continue;
}

points.sort_by(|&a, &b| match a.0 > b.0 {
true => std::cmp::Ordering::Greater,
false => std::cmp::Ordering::Less,
});

let mut last_manipulator_index = 0;
let mut to_extend_with_last_group: Option<Vec<ManipulatorGroup<ManipulatorGroupId>>> = None;
let mut last_manipulator_group: Option<&ManipulatorGroup<ManipulatorGroupId>> = None;
for (i, &(manipulator_index, group)) in points.iter().enumerate() {
if manipulator_index == 0 && !subpath.closed {
last_manipulator_index = manipulator_index + 1;
last_manipulator_group = Some(group);
continue;
}

let mut segment = subpath.manipulator_groups()[last_manipulator_index..manipulator_index].to_vec();
if i != 0 {
segment.insert(0, ManipulatorGroup::new(last_manipulator_group.unwrap().anchor, None, last_manipulator_group.unwrap().out_handle));
}

segment.push(ManipulatorGroup::new(group.anchor, group.in_handle, None));

if subpath.closed && i == 0 {
to_extend_with_last_group = Some(segment);
} else {
broken_subpaths.push(bezier_rs::Subpath::new(segment, false));
}

last_manipulator_index = manipulator_index + 1;
last_manipulator_group = Some(group);
}

if last_manipulator_index == subpath.len() && !subpath.closed {
continue;
}

let mut final_segment = subpath.manipulator_groups()[last_manipulator_index..].to_vec();
final_segment.insert(0, ManipulatorGroup::new(last_manipulator_group.unwrap().anchor, None, last_manipulator_group.unwrap().out_handle));

if let Some(group) = to_extend_with_last_group {
final_segment.extend(group);
}

broken_subpaths.push(bezier_rs::Subpath::new(final_segment, false));
}

responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::UpdateSubpaths { subpaths: broken_subpaths },
});
}
}

/// Delete point(s) and adjacent segments, which breaks a closed path as open, or an open path into multiple.
pub fn delete_point_and_break_path(&self, document_network: &NodeNetwork, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(subpaths) = get_subpaths(layer, document_network) else {
continue;
};

let mut broken_subpaths = Vec::<bezier_rs::Subpath<ManipulatorGroupId>>::with_capacity(subpaths.len());

for subpath in subpaths {
let mut selected_points: Vec<_> = state.selected_points.iter().filter_map(|&point| subpath.manipulator_index_from_id(point.group)).collect();

if selected_points.is_empty() {
broken_subpaths.push(subpath.clone());
continue;
}

selected_points.sort_by(|&a, &b| match a > b {
true => std::cmp::Ordering::Greater,
false => std::cmp::Ordering::Less,
});

let mut last_manipulator_index = 0;
let mut to_extend_with_last_group: Option<Vec<ManipulatorGroup<ManipulatorGroupId>>> = None;
for (i, &manipulator_index) in selected_points.iter().enumerate() {
if (manipulator_index == 0 || manipulator_index == 1) && !subpath.closed {
last_manipulator_index = manipulator_index + 1;
continue;
}

let segment = subpath.manipulator_groups()[last_manipulator_index..manipulator_index].to_vec();
if subpath.closed && i == 0 {
to_extend_with_last_group = Some(segment);
} else {
broken_subpaths.push(bezier_rs::Subpath::new(segment, false));
}

last_manipulator_index = manipulator_index + 1;
}

if (last_manipulator_index == subpath.len() || last_manipulator_index == subpath.len() - 1) && !subpath.closed {
continue;
}

let mut final_segment = subpath.manipulator_groups()[last_manipulator_index..].to_vec();

if let Some(group) = to_extend_with_last_group {
final_segment.extend(group);
}

broken_subpaths.push(bezier_rs::Subpath::new(final_segment, false));
}

responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::UpdateSubpaths { subpaths: broken_subpaths },
});
}
}

/// Toggle if the handles should mirror angle across the anchor position.
pub fn toggle_handle_mirroring_on_selected(&self, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
Expand Down
16 changes: 16 additions & 0 deletions editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ pub enum PathToolMessage {
SelectionChanged,

// Tool-specific messages
BreakPath,
Delete,
DeleteAndBreakPath,
DragStart {
add_to_selection: Key,
},
Expand Down Expand Up @@ -159,13 +161,17 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
NudgeSelectedPoints,
Enter,
SelectAllPoints,
BreakPath,
DeleteAndBreakPath,
),
Dragging => actions!(PathToolMessageDiscriminant;
InsertPoint,
DragStop,
PointerMove,
Delete,
SelectAllPoints,
BreakPath,
DeleteAndBreakPath,
),
DrawingBox => actions!(PathToolMessageDiscriminant;
InsertPoint,
Expand All @@ -174,6 +180,8 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
Delete,
Enter,
SelectAllPoints,
BreakPath,
DeleteAndBreakPath,
),
}
}
Expand Down Expand Up @@ -418,6 +426,14 @@ impl Fsm for PathToolFsmState {

PathToolFsmState::Ready
}
(_, PathToolMessage::BreakPath) => {
shape_editor.break_path_at_selected_point(&document.network, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::DeleteAndBreakPath) => {
shape_editor.delete_point_and_break_path(&document.network, responses);
PathToolFsmState::Ready
}
(_, PathToolMessage::InsertPoint) => {
// First we try and flip the sharpness (if they have clicked on an anchor)
if !shape_editor.flip_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses) {
Expand Down