mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-23 23:55:06 +00:00
Break handle of path being drawn by placing anchor on previous anchor with Pen tool (#814)
* Break path by placing anchor on previous one * Fix offset on click without dragging * Fix bug where ids reassigned from saved document * Fix stuck overlay Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
0553cc5100
commit
cee1add3a4
6 changed files with 95 additions and 17 deletions
|
@ -1 +1 @@
|
|||
{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":10689566813179949075,"layer_ids":[10689566813179949074],"layers":[{"visible":true,"name":"Folder 1","data":{"Folder":{"next_assignment_id":17222868332373661780,"layer_ids":[17222868332373661779],"layers":[{"visible":true,"name":"Shape 1","data":{"Shape":{"shape":{"elements":[{"points":[{"position":[0.5,1.0],"manipulator_type":"Anchor"},{"position":[0.7761415,1.0],"manipulator_type":"InHandle"},{"position":[0.22385850000000002,1.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.0,0.5],"manipulator_type":"Anchor"},{"position":[0.0,0.7761415],"manipulator_type":"InHandle"},{"position":[0.0,0.22385850000000002],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.5,0.0],"manipulator_type":"Anchor"},{"position":[0.22385850000000002,0.0],"manipulator_type":"InHandle"},{"position":[0.7761415,0.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[1.0,0.5],"manipulator_type":"Anchor"},{"position":[1.0,0.22385850000000002],"manipulator_type":"InHandle"},{"position":[1.0,0.7761415],"manipulator_type":"OutHandle"}]},{"points":[null,null,null]}],"element_ids":[1,2,3,4,5]},"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1}},"transform":{"matrix2":[379.0,0.0,-0.0,239.0],"translation":[-479.3046875,-99.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":6520881531418194372,"name":"Untitled Document","version":"0.0.13","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[10689566813179949074],{"selected":false,"expanded":true}],[[10689566813179949074,17222868332373661779],{"selected":true,"expanded":false}]],"layer_range_selection_reference":[10689566813179949074,17222868332373661779],"navigation_handler":{"pan":[0.0,0.0],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[10689566813179949074,17222868332373661779],"Artwork"]}}
|
||||
{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":10689566813179949075,"layer_ids":[10689566813179949074],"layers":[{"visible":true,"name":"Folder 1","data":{"Folder":{"next_assignment_id":17222868332373661780,"layer_ids":[17222868332373661779],"layers":[{"visible":true,"name":"Shape 1","data":{"Shape":{"shape":{"elements":[{"points":[{"position":[0.5,1.0],"manipulator_type":"Anchor"},{"position":[0.7761415,1.0],"manipulator_type":"InHandle"},{"position":[0.22385850000000002,1.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.0,0.5],"manipulator_type":"Anchor"},{"position":[0.0,0.7761415],"manipulator_type":"InHandle"},{"position":[0.0,0.22385850000000002],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.5,0.0],"manipulator_type":"Anchor"},{"position":[0.22385850000000002,0.0],"manipulator_type":"InHandle"},{"position":[0.7761415,0.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[1.0,0.5],"manipulator_type":"Anchor"},{"position":[1.0,0.22385850000000002],"manipulator_type":"InHandle"},{"position":[1.0,0.7761415],"manipulator_type":"OutHandle"}]},{"points":[null,null,null]}],"element_ids":[1,2,3,4,5],"next_id":6},"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1}},"transform":{"matrix2":[379.0,0.0,-0.0,239.0],"translation":[-479.3046875,-99.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":6520881531418194372,"name":"Untitled Document","version":"0.0.14","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[10689566813179949074],{"selected":false,"expanded":true}],[[10689566813179949074,17222868332373661779],{"selected":true,"expanded":false}]],"layer_range_selection_reference":[10689566813179949074,17222868332373661779],"navigation_handler":{"pan":[0.0,0.0],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[10689566813179949074,17222868332373661779],"Artwork"]}}
|
||||
|
|
|
@ -74,7 +74,7 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
|
|||
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
|
||||
|
||||
// Document
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.13"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.14"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
|
||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ pub struct ShapeEditor {
|
|||
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
||||
impl ShapeEditor {
|
||||
/// Select the first point within the selection threshold.
|
||||
/// Returns the points if found, None otherwise.
|
||||
/// Returns a tuple of the points if found and the offset, or None otherwise.
|
||||
pub fn select_point(
|
||||
&self,
|
||||
document: &Document,
|
||||
|
@ -39,7 +39,7 @@ impl ShapeEditor {
|
|||
select_threshold: f64,
|
||||
add_to_selection: bool,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Option<Vec<(&[LayerId], u64, ManipulatorType)>> {
|
||||
) -> Option<(Vec<(&[LayerId], u64, ManipulatorType)>, DVec2)> {
|
||||
if self.selected_layers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
@ -98,12 +98,15 @@ impl ShapeEditor {
|
|||
}
|
||||
.into(),
|
||||
);
|
||||
// Snap the selected point to the cursor
|
||||
if let Ok(viewspace) = document.generate_transform_relative_to_viewport(shape_layer_path) {
|
||||
self.move_selected_points(mouse_position - viewspace.transform_point2(point_position), responses)
|
||||
}
|
||||
|
||||
return Some(points);
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = if let Ok(viewspace) = document.generate_transform_relative_to_viewport(shape_layer_path) {
|
||||
mouse_position - viewspace.transform_point2(point_position)
|
||||
} else {
|
||||
DVec2::ZERO
|
||||
};
|
||||
|
||||
return Some((points, offset));
|
||||
} else {
|
||||
responses.push_back(
|
||||
Operation::DeselectManipulatorPoints {
|
||||
|
|
|
@ -179,9 +179,10 @@ impl Fsm for PathToolFsmState {
|
|||
let toggle_add_to_selection = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if let Some(mut new_selected) = tool_data
|
||||
.shape_editor
|
||||
.select_point(&document.graphene_document, input.mouse.position, SELECTION_THRESHOLD, toggle_add_to_selection, responses)
|
||||
if let Some((mut new_selected, offset)) =
|
||||
tool_data
|
||||
.shape_editor
|
||||
.select_point(&document.graphene_document, input.mouse.position, SELECTION_THRESHOLD, toggle_add_to_selection, responses)
|
||||
{
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
|
@ -203,7 +204,7 @@ impl Fsm for PathToolFsmState {
|
|||
let include_handles = tool_data.shape_editor.selected_layers_ref();
|
||||
tool_data.snap_manager.add_all_document_handles(document, &include_handles, &[], &new_selected);
|
||||
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
tool_data.drag_start_pos = input.mouse.position - offset;
|
||||
PathToolFsmState::Dragging
|
||||
}
|
||||
// We didn't find a point nearby, so consider selecting the nearest shape instead
|
||||
|
|
|
@ -279,7 +279,67 @@ impl Fsm for PenToolFsmState {
|
|||
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => PenToolFsmState::DraggingHandle,
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
||||
// If you place the anchor on top of the previous anchor then you break the mirror
|
||||
let mut check_break = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.graphene_document.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
|
||||
if let Some((previous_id, previous_anchor)) = previous
|
||||
.as_ref()
|
||||
.and_then(|(&id, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|x| (id, x)))
|
||||
{
|
||||
// Break the control
|
||||
if transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(previous_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
// Remove the point that has just been placed
|
||||
let op = Operation::RemoveManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Move the in handle of the previous anchor to on top of the previous position
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: previous_id,
|
||||
manipulator_type: outwards_handle,
|
||||
position: previous_anchor.position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Stop the handles on the last point from mirroring
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: previous_id,
|
||||
mirror_distance: false,
|
||||
mirror_angle: false,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// The overlay system cannot detect deleted points so we must just delete all the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.graphene_document, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
tool_data.should_mirror = false;
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
check_break().unwrap_or(PenToolFsmState::DraggingHandle)
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||
let mut process = || {
|
||||
// Get subpath
|
||||
|
@ -391,8 +451,9 @@ impl Fsm for PenToolFsmState {
|
|||
};
|
||||
responses.push_back(msg.into());
|
||||
|
||||
let should_mirror = !input.keyboard.get(break_handle as usize) && tool_data.should_mirror;
|
||||
// Mirror handle of last segment
|
||||
if !input.keyboard.get(break_handle as usize) && tool_data.should_mirror {
|
||||
if should_mirror {
|
||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||
let pos = last_anchor.position - (pos - last_anchor.position);
|
||||
|
||||
|
@ -405,6 +466,15 @@ impl Fsm for PenToolFsmState {
|
|||
responses.push_back(msg.into());
|
||||
}
|
||||
|
||||
// Update the mirror status of the currently modifying point
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
mirror_distance: should_mirror,
|
||||
mirror_angle: should_mirror,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
Some(())
|
||||
};
|
||||
if process().is_none() {
|
||||
|
@ -440,7 +510,12 @@ impl Fsm for PenToolFsmState {
|
|||
}
|
||||
|
||||
if let Some(relative) = previous.as_ref().and_then(|(_, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref()) {
|
||||
pos = compute_snapped_angle(input, snap_angle, pos, relative.position);
|
||||
// Snap to the previously placed point (to show break control)
|
||||
if mouse.distance_squared(transform.transform_point2(relative.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = relative.position;
|
||||
} else {
|
||||
pos = compute_snapped_angle(input, snap_angle, pos, relative.position);
|
||||
}
|
||||
}
|
||||
|
||||
for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] {
|
||||
|
|
|
@ -21,7 +21,6 @@ pub struct IdBackedVec<T> {
|
|||
/// The IDs of the [Elements] contained within this
|
||||
element_ids: Vec<ElementId>,
|
||||
/// The ID that will be assigned to the next element that is added to this
|
||||
#[serde(skip)]
|
||||
next_id: ElementId,
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue