mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 15:28:04 +08:00
Implement separate segment
This commit is contained in:
@@ -108,6 +108,8 @@ pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
|
||||
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
|
||||
pub const SEGMENT_SELECTED_THICKNESS: f64 = 3.;
|
||||
pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;
|
||||
pub const PEEL_SEGMENT_OFFSET_FACTOR: f64 = 20.;
|
||||
pub const SEPARATE_SEGMENT_OFFSET_FACTOR: f64 = 10.;
|
||||
|
||||
// PEN TOOL
|
||||
pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
|
||||
|
||||
@@ -2,7 +2,8 @@ use super::select_tool::extend_lasso;
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD,
|
||||
DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, PEEL_SEGMENT_OFFSET_FACTOR, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
SEPARATE_SEGMENT_OFFSET_FACTOR,
|
||||
};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
@@ -27,7 +28,7 @@ use graphene_std::renderer::Quad;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::misc::{HandleId, HandleType, ManipulatorPointId};
|
||||
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use std::vec;
|
||||
|
||||
@@ -338,8 +339,8 @@ impl LayoutHolder for PathTool {
|
||||
path_overlay_mode_widget,
|
||||
unrelated_seperator.clone(),
|
||||
path_node_button,
|
||||
separate_segment_button,
|
||||
unrelated_seperator.clone(),
|
||||
separate_segment_button,
|
||||
peel_segment_button,
|
||||
unrelated_seperator.clone(),
|
||||
// checkbox.clone(),
|
||||
@@ -655,6 +656,11 @@ impl PathToolData {
|
||||
|
||||
fn update_separate_segment_toggle(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler) {
|
||||
//TODO: Implement this
|
||||
if single_segment_selected(shape_editor, document).is_some() {
|
||||
self.segment_separation_enabled = true;
|
||||
} else {
|
||||
self.segment_separation_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_saved_points(&mut self) {
|
||||
@@ -3034,6 +3040,7 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
tool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
|
||||
tool_data.update_selection_status(shape_editor, document);
|
||||
tool_data.update_separate_segment_toggle(shape_editor, document);
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
||||
@@ -3062,16 +3069,187 @@ impl Fsm for PathToolFsmState {
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::SeparateSegment) => {
|
||||
// TODO: Implement this
|
||||
let Some((layer, vector, old_segment)) = single_segment_selected(shape_editor, document) else {
|
||||
return self;
|
||||
};
|
||||
|
||||
// It only makes sense to use this if we have more than two segments connected
|
||||
// Basically we will have a segment selected and we need to first remove the segment, add two segments with same bezier on both of them
|
||||
// Collect the segments that will be connected to start from
|
||||
let (_, bezier, start, end) = vector.segment_bezier_iter().find(|(id, _, _, _)| *id == old_segment).expect(" Couldn't get segment data");
|
||||
|
||||
// Need to collect all of the connections of start and end points
|
||||
let start_conections = vector.all_connected(start).collect::<Vec<_>>();
|
||||
let end_connections = vector.all_connected(end).collect::<Vec<_>>();
|
||||
|
||||
// TODO: Prevent all edge cases of a hanging segmentS
|
||||
if start_conections.len() < 2 || end_connections.len() < 2 {
|
||||
return self;
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let start_pos = ManipulatorPointId::Anchor(start).get_position(&vector).expect("No position for point");
|
||||
let end_pos = ManipulatorPointId::Anchor(end).get_position(&vector).expect("No position for point");
|
||||
|
||||
let direction = start_pos - end_pos;
|
||||
let perp = direction.perp();
|
||||
|
||||
// first remove the points start and end
|
||||
let modification_type = VectorModificationType::RemovePoint { id: start };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let modification_type = VectorModificationType::RemovePoint { id: end };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let offset = perp.try_normalize().unwrap_or_default() * SEPARATE_SEGMENT_OFFSET_FACTOR;
|
||||
|
||||
// (start point, end point) for both new segments
|
||||
let new_points = [(PointId::generate(), PointId::generate()), (PointId::generate(), PointId::generate())];
|
||||
|
||||
let handles = |bezier: Bezier| -> [Option<DVec2>; 2] {
|
||||
match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
}
|
||||
};
|
||||
|
||||
for ((new_start, new_end), factor) in new_points.iter().zip([-1., 1.]) {
|
||||
let offset = offset * factor;
|
||||
let new_start_pos = start_pos + offset;
|
||||
let new_end_pos = end_pos + offset;
|
||||
|
||||
let modification_type = VectorModificationType::InsertPoint {
|
||||
id: *new_start,
|
||||
position: new_start_pos,
|
||||
};
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let modification_type = VectorModificationType::InsertPoint { id: *new_end, position: new_end_pos };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
// Insert the bezier between new start and new end
|
||||
let id = SegmentId::generate();
|
||||
let points = [*new_start, *new_end];
|
||||
let handles = handles(bezier);
|
||||
let modification_type = VectorModificationType::InsertSegment { id, points, handles };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
// Now add connections
|
||||
for connection in start_conections {
|
||||
let segment = connection.segment;
|
||||
let other_point_id = connection.opposite().to_manipulator_point().get_anchor(&vector).unwrap();
|
||||
let connection_bez = vector.segment_from_id(segment).unwrap();
|
||||
let other_pos = match (connection_bez.handles, connection.ty) {
|
||||
(BezierHandles::Cubic { handle_start, handle_end: _ }, HandleType::Primary) => {
|
||||
if handle_start.distance(connection_bez.start) < 1e-8 {
|
||||
connection_bez.end
|
||||
} else {
|
||||
handle_start
|
||||
}
|
||||
}
|
||||
(BezierHandles::Cubic { handle_start: _, handle_end }, HandleType::End) => {
|
||||
if handle_end.distance(connection_bez.end) < 1e-8 {
|
||||
connection_bez.start
|
||||
} else {
|
||||
handle_end
|
||||
}
|
||||
}
|
||||
(BezierHandles::Quadratic { handle }, _) => handle,
|
||||
(BezierHandles::Linear, _) => connection.opposite().to_manipulator_point().get_anchor_position(&vector).unwrap(),
|
||||
};
|
||||
let connection_direction = start_pos - other_pos;
|
||||
let new_segment = SegmentId::generate();
|
||||
let points = if connection_direction.dot(offset) > 0. {
|
||||
if connection.ty == HandleType::Primary {
|
||||
[new_points[0].0, other_point_id]
|
||||
} else {
|
||||
[other_point_id, new_points[0].0]
|
||||
}
|
||||
} else {
|
||||
if connection.ty == HandleType::Primary {
|
||||
[new_points[1].0, other_point_id]
|
||||
} else {
|
||||
[other_point_id, new_points[1].0]
|
||||
}
|
||||
};
|
||||
let handles = handles(connection_bez);
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment, points, handles };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
for connection in end_connections {
|
||||
let other_point_id = connection.opposite().to_manipulator_point().get_anchor(&vector).unwrap();
|
||||
let connection_bez = vector.segment_from_id(connection.segment).unwrap();
|
||||
let other_pos = match (connection_bez.handles, connection.ty) {
|
||||
(BezierHandles::Cubic { handle_start, handle_end: _ }, HandleType::Primary) => {
|
||||
// handle_start
|
||||
if handle_start.distance(connection_bez.start) < 1e-8 { connection_bez.end } else { handle_start }
|
||||
}
|
||||
(BezierHandles::Cubic { handle_start: _, handle_end }, HandleType::End) => {
|
||||
if handle_end.distance(connection_bez.end) < 1e-8 {
|
||||
connection_bez.start
|
||||
} else {
|
||||
handle_end
|
||||
}
|
||||
}
|
||||
(BezierHandles::Quadratic { handle }, _) => handle,
|
||||
(BezierHandles::Linear, _) => connection.opposite().to_manipulator_point().get_anchor_position(&vector).unwrap(),
|
||||
};
|
||||
let connection_direction = end_pos - other_pos;
|
||||
let new_segment = SegmentId::generate();
|
||||
let points = if connection_direction.dot(offset) > 0. {
|
||||
if connection.ty == HandleType::Primary {
|
||||
[new_points[0].1, other_point_id]
|
||||
} else {
|
||||
[other_point_id, new_points[0].1]
|
||||
}
|
||||
} else {
|
||||
if connection.ty == HandleType::Primary {
|
||||
[new_points[1].1, other_point_id]
|
||||
} else {
|
||||
[other_point_id, new_points[1].1]
|
||||
}
|
||||
};
|
||||
let handles = handles(connection_bez);
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment, points, handles };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::PeelSegment) => {
|
||||
//TODO: Implement this
|
||||
let Some((layer, vector, old_segment)) = single_segment_selected(shape_editor, document) else {
|
||||
return self;
|
||||
};
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let (_, bezier, start, end) = vector.segment_bezier_iter().find(|(id, _, _, _)| *id == old_segment).expect(" Couldn't get segment data");
|
||||
|
||||
// Calculate the perpendicular direction to the start and end point and nudge the handles in that direction to give peel effect
|
||||
let start_pos = ManipulatorPointId::Anchor(start).get_position(&vector).expect("No position for point");
|
||||
let end_pos = ManipulatorPointId::Anchor(end).get_position(&vector).expect("No position for point");
|
||||
|
||||
let direction = start_pos - end_pos;
|
||||
let perp = direction.perp();
|
||||
let offset = perp.try_normalize().unwrap_or_default() * PEEL_SEGMENT_OFFSET_FACTOR;
|
||||
|
||||
// Generate a new id and insert new bezier
|
||||
let id = SegmentId::generate();
|
||||
let points = [start, end];
|
||||
let handles = |bezier: Bezier| -> [Option<DVec2>; 2] {
|
||||
match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start + offset), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start + offset), Some(handle_end - bezier.end + offset)],
|
||||
}
|
||||
};
|
||||
let handles = handles(bezier);
|
||||
let modification_type = VectorModificationType::InsertSegment { id, points, handles };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
|
||||
self
|
||||
}
|
||||
@@ -3516,3 +3694,23 @@ fn update_dynamic_hints(
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
|
||||
fn single_segment_selected(shape_editor: &mut ShapeState, document: &DocumentMessageHandler) -> Option<(LayerNodeIdentifier, Vector, SegmentId)> {
|
||||
let mut non_empty_layers = shape_editor.selected_shape_state.iter().filter(|(_, state)| !state.is_empty());
|
||||
|
||||
let (layer, _) = non_empty_layers.next()?;
|
||||
if non_empty_layers.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let segments = shape_editor.selected_segments().collect::<Vec<_>>();
|
||||
if segments.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let old_segment = segments[0];
|
||||
|
||||
let vector = document.network_interface.compute_modified_vector(*layer)?;
|
||||
|
||||
Some((*layer, vector, *old_segment))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user