Rename handle mirroring to colinear

This commit is contained in:
Keavon Chambers
2024-03-14 07:44:55 -07:00
parent ea4f3d8bba
commit 5bca931813
20 changed files with 326 additions and 305 deletions
@@ -47,22 +47,20 @@ pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent:
LayerNodeIdentifier::new_unchecked(id)
}
/// Batch set all of the manipulator groups to mirror on a specific layer
pub fn set_manipulator_mirror_angle(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>], layer: LayerNodeIdentifier, mirror_angle: bool, responses: &mut VecDeque<Message>) {
/// Batch set all of the manipulator groups to set their colinear handle state on a specific layer
pub fn set_manipulator_colinear_handles_state(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>], layer: LayerNodeIdentifier, colinear: bool, responses: &mut VecDeque<Message>) {
for manipulator_group in manipulator_groups {
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring {
id: manipulator_group.id,
mirror_angle,
},
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator_group.id, colinear },
});
}
}
/// Locate the subpaths from the shape nodes of a particular layer
pub fn get_subpaths(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<Subpath<ManipulatorGroupId>>> {
if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document_network)?.find_input("Shape", 0)? {
let path_data_node_input_index = 0;
if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document_network).find_input("Shape", path_data_node_input_index)? {
Some(subpaths)
} else {
None
@@ -71,7 +69,8 @@ pub fn get_subpaths(layer: LayerNodeIdentifier, document_network: &NodeNetwork)
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
pub fn get_pivot(layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<DVec2> {
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network)?.find_input("Transform", 5)? {
let pivot_node_input_index = 5;
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network).find_input("Transform", pivot_node_input_index)? {
Some(*pivot)
} else {
None
@@ -84,10 +83,11 @@ pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document_network: &NodeNet
document_metadata.transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
}
/// Get the currently mirrored handles for a particular layer from the shape node
pub fn get_mirror_handles(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<ManipulatorGroupId>> {
if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document_network)?.find_input("Shape", 1)? {
Some(mirror_handles)
/// Get the manipulator groups that currently have colinear handles for a particular layer from the shape node
pub fn get_colinear_manipulators(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<ManipulatorGroupId>> {
let colinear_manipulators_node_input_index = 1;
if let TaggedValue::ManipulatorGroupIds(manipulator_groups) = NodeGraphLayer::new(layer, document_network).find_input("Shape", colinear_manipulators_node_input_index)? {
Some(manipulator_groups)
} else {
None
}
@@ -95,7 +95,7 @@ pub fn get_mirror_handles(layer: LayerNodeIdentifier, document_network: &NodeNet
/// Get the current gradient of a layer from the closest Fill node
pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Gradient> {
let inputs = NodeGraphLayer::new(layer, document_network)?.find_node_inputs("Fill")?;
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Fill")?;
let TaggedValue::FillType(FillType::Gradient) = inputs.get(1)?.as_value()? else {
return None;
};
@@ -125,7 +125,7 @@ pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork)
/// Get the current fill of a layer from the closest Fill node
pub fn get_fill_color(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Color> {
let inputs = NodeGraphLayer::new(layer, document_network)?.find_node_inputs("Fill")?;
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Fill")?;
let TaggedValue::Color(color) = inputs.get(2)?.as_value()? else {
return None;
};
@@ -134,7 +134,7 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, document_network: &NodeNetwork
/// Get the current blend mode of a layer from the closest Blend Mode node
pub fn get_blend_mode(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<BlendMode> {
let inputs = NodeGraphLayer::new(layer, document_network)?.find_node_inputs("Blend Mode")?;
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Blend Mode")?;
let TaggedValue::BlendMode(blend_mode) = inputs.get(1)?.as_value()? else {
return None;
};
@@ -149,7 +149,7 @@ pub fn get_blend_mode(layer: LayerNodeIdentifier, document_network: &NodeNetwork
/// - The default value of 100% if no Opacity node is present, but this function returns None in that case
/// With those limitations in mind, the intention of this function is to show just the value already present in an upstream Opacity node so that value can be directly edited.
pub fn get_opacity(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<f64> {
let inputs = NodeGraphLayer::new(layer, document_network)?.find_node_inputs("Opacity")?;
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Opacity")?;
let TaggedValue::F64(opacity) = inputs.get(1)?.as_value()? else {
return None;
};
@@ -157,16 +157,16 @@ pub fn get_opacity(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -
}
pub fn get_fill_id(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<NodeId> {
NodeGraphLayer::new(layer, document_network)?.node_id("Fill")
NodeGraphLayer::new(layer, document_network).node_id("Fill")
}
pub fn get_text_id(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<NodeId> {
NodeGraphLayer::new(layer, document_network)?.node_id("Text")
NodeGraphLayer::new(layer, document_network).node_id("Text")
}
/// Gets properties from the Text node
pub fn get_text(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<(&String, &Font, f64)> {
let inputs = NodeGraphLayer::new(layer, document_network)?.find_node_inputs("Text")?;
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Text")?;
let NodeInput::Value {
tagged_value: TaggedValue::String(text),
..
@@ -195,7 +195,8 @@ pub fn get_text(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> O
}
pub fn get_stroke_width(layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<f64> {
if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network)?.find_input("Stroke", 2)? {
let weight_node_input_index = 2;
if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network).find_input("Stroke", weight_node_input_index)? {
Some(*width)
} else {
None
@@ -204,7 +205,7 @@ pub fn get_stroke_width(layer: LayerNodeIdentifier, network: &NodeNetwork) -> Op
/// Checks if a specified layer uses an upstream node matching the given name.
pub fn is_layer_fed_by_node_of_name(layer: LayerNodeIdentifier, document_network: &NodeNetwork, node_name: &str) -> bool {
NodeGraphLayer::new(layer, document_network).is_some_and(|layer| layer.find_node_inputs(node_name).is_some())
NodeGraphLayer::new(layer, document_network).find_node_inputs(node_name).is_some()
}
/// Convert subpaths to an iterator of manipulator groups
@@ -220,20 +221,16 @@ pub fn get_manipulator_from_id(subpaths: &[Subpath<ManipulatorGroupId>], id: Man
/// An immutable reference to a layer within the document node graph for easy access.
pub struct NodeGraphLayer<'a> {
node_graph: &'a NodeNetwork,
_outwards_links: HashMap<NodeId, Vec<NodeId>>,
layer_node: NodeId,
}
impl<'a> NodeGraphLayer<'a> {
/// Get the layer node from the document
pub fn new(layer: LayerNodeIdentifier, network: &'a NodeNetwork) -> Option<Self> {
let outwards_links = network.collect_outwards_links();
Some(Self {
pub fn new(layer: LayerNodeIdentifier, network: &'a NodeNetwork) -> Self {
Self {
node_graph: network,
_outwards_links: outwards_links,
layer_node: layer.to_node(),
})
}
}
/// Return an iterator up the primary flow of the layer
@@ -257,6 +254,7 @@ impl<'a> NodeGraphLayer<'a> {
/// Find a specific input of a node within the layer's primary flow
pub fn find_input(&self, node_name: &str, index: usize) -> Option<&'a TaggedValue> {
// TODO: Find a better way to accept a node input rather than using its index (which is quite unclear and fragile)
self.find_node_inputs(node_name)?.get(index)?.as_value()
}
}
@@ -1,11 +1,11 @@
use super::graph_modification_utils;
use super::snapping::{group_smooth, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
use super::snapping::{are_manipulator_handles_colinear, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
use crate::consts::{DRAG_THRESHOLD, INSERT_POINT_ON_SEGMENT_TOO_CLOSE_DISTANCE};
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_manipulator_groups, get_mirror_handles, get_subpaths};
use crate::messages::tool::common_functionality::graph_modification_utils::{get_colinear_manipulators, get_manipulator_from_id, get_manipulator_groups, get_subpaths};
use bezier_rs::{Bezier, ManipulatorGroup, TValue};
use graph_craft::document::NodeNetwork;
@@ -17,8 +17,8 @@ use glam::DVec2;
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ManipulatorAngle {
Smooth,
Sharp,
Colinear,
Free,
Mixed,
}
@@ -277,10 +277,10 @@ impl ShapeState {
}
let source = if handle.is_handle() {
SnapSource::Geometry(GeometrySnapSource::Handle)
} else if group_smooth(group, to_document, subpath, index) {
SnapSource::Geometry(GeometrySnapSource::Smooth)
} else if are_manipulator_handles_colinear(group, to_document, subpath, index) {
SnapSource::Geometry(GeometrySnapSource::HandlesColinear)
} else {
SnapSource::Geometry(GeometrySnapSource::Sharp)
SnapSource::Geometry(GeometrySnapSource::HandlesFree)
};
let Some(position) = handle.get_position(group) else { continue };
let mut point = SnapCandidatePoint::new_source(to_document.transform_point2(position) + mouse_delta, source);
@@ -467,7 +467,7 @@ impl ShapeState {
if point.manipulator_type.is_handle() {
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: false },
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: group.id, colinear: false },
});
}
@@ -490,28 +490,28 @@ impl ShapeState {
Some(())
}
// Iterates over the selected manipulator groups, returning whether they have mixed, sharp, or smooth angles.
// If there are no points selected this function returns mixed.
/// Iterates over the selected manipulator groups, returning whether their handles have mixed, colinear, or free angles.
/// If there are no points selected this function returns mixed.
pub fn selected_manipulator_angles(&self, document_network: &NodeNetwork) -> ManipulatorAngle {
// This iterator contains a bool indicating whether or not every selected point has a smooth manipulator angle.
let mut point_smoothness_status = self
// This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles.
let mut points_colinear_status = self
.selected_shape_state
.iter()
.filter_map(|(&layer, selection_state)| Some((graph_modification_utils::get_mirror_handles(layer, document_network)?, selection_state)))
.flat_map(|(mirror, selection_state)| selection_state.selected_points.iter().map(|selected_point| mirror.contains(&selected_point.group)));
.filter_map(|(&layer, selection_state)| Some((graph_modification_utils::get_colinear_manipulators(layer, document_network)?, selection_state)))
.flat_map(|(colinear_manipulators, selection_state)| selection_state.selected_points.iter().map(|selected_point| colinear_manipulators.contains(&selected_point.group)));
let Some(first_is_smooth) = point_smoothness_status.next() else { return ManipulatorAngle::Mixed };
if point_smoothness_status.any(|point| first_is_smooth != point) {
let Some(first_is_colinear) = points_colinear_status.next() else { return ManipulatorAngle::Mixed };
if points_colinear_status.any(|point| first_is_colinear != point) {
return ManipulatorAngle::Mixed;
}
match first_is_smooth {
false => ManipulatorAngle::Sharp,
true => ManipulatorAngle::Smooth,
match first_is_colinear {
false => ManipulatorAngle::Free,
true => ManipulatorAngle::Colinear,
}
}
pub fn smooth_manipulator_group(&self, subpath: &bezier_rs::Subpath<ManipulatorGroupId>, index: usize, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
pub fn convert_manipulator_handles_to_colinear(&self, subpath: &bezier_rs::Subpath<ManipulatorGroupId>, index: usize, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
let manipulator_groups = subpath.manipulator_groups();
let manipulator = manipulator_groups[index];
@@ -542,13 +542,10 @@ impl ShapeState {
(None, None) => return,
};
// Mirror the angle but not the distance
// Set the manipulator to have colinear handles
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring {
id: manipulator.id,
mirror_angle: true,
},
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator.id, colinear: true },
});
let (sin, cos) = handle_direction.sin_cos();
@@ -569,7 +566,6 @@ impl ShapeState {
modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle },
});
}
if let Some(out_handle) = length_next.map(|length| anchor_position - handle_vector * length) {
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
responses.add(GraphOperationMessage::Vector {
@@ -579,8 +575,11 @@ impl ShapeState {
}
}
/// Smooths the set of selected control points, assuming that the selected set is homogeneously sharp.
pub fn smooth_selected_groups(&self, responses: &mut VecDeque<Message>, document_network: &NodeNetwork) -> Option<()> {
/// Converts all selected points to colinear while moving the handles to ensure their 180° angle separation.
/// If only one handle is selected, the other handle will be moved to match the angle of the selected handle.
/// If both or neither handles are selected, the angle of both handles will be averaged from their current angles, weighted by their lengths.
/// Assumes all selected manipulators have handles that are already not colinear.
pub fn convert_selected_manipulators_to_colinear_handles(&self, responses: &mut VecDeque<Message>, document_network: &NodeNetwork) -> Option<()> {
let mut skip_set = HashSet::new();
for (&layer, layer_state) in self.selected_shape_state.iter() {
@@ -593,10 +592,6 @@ impl ShapeState {
skip_set.insert(point.group);
let anchor_selected = layer_state.selected_points.contains(&ManipulatorPointId {
group: point.group,
manipulator_type: SelectedType::Anchor,
});
let out_selected = layer_state.selected_points.contains(&ManipulatorPointId {
group: point.group,
manipulator_type: SelectedType::OutHandle,
@@ -607,8 +602,9 @@ impl ShapeState {
});
let group = graph_modification_utils::get_manipulator_from_id(subpaths, point.group)?;
match (anchor_selected, out_selected, in_selected) {
(_, true, false) => {
match (out_selected, in_selected) {
// If the out handle is selected, only move the angle of the in handle
(true, false) => {
let out_handle = ManipulatorPointId::new(point.group, SelectedType::OutHandle);
if let Some(position) = group.out_handle {
responses.add(GraphOperationMessage::Vector {
@@ -617,7 +613,8 @@ impl ShapeState {
});
}
}
(_, false, true) => {
// If the in handle is selected, only move the angle of the out handle
(false, true) => {
let in_handle = ManipulatorPointId::new(point.group, SelectedType::InHandle);
if let Some(position) = group.in_handle {
responses.add(GraphOperationMessage::Vector {
@@ -626,7 +623,9 @@ impl ShapeState {
});
}
}
(_, _, _) => {
// If both or neither handles are selected, average the angles of the handles weighted proportional to their lengths
// TODO: This is bugged, it doesn't successfully average the angles
(_, _) => {
let found = subpaths.iter().find_map(|subpath| {
let group_slice = subpath.manipulator_groups();
let index = group_slice.iter().position(|manipulator| manipulator.id == group.id)?;
@@ -635,7 +634,7 @@ impl ShapeState {
});
if let Some((subpath, index)) = found {
self.smooth_manipulator_group(subpath, index, responses, layer);
self.convert_manipulator_handles_to_colinear(subpath, index, responses, layer);
}
}
}
@@ -646,10 +645,12 @@ impl ShapeState {
}
/// Move the selected points by dragging the mouse.
pub fn move_selected_points(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
pub fn move_selected_points(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, delta: DVec2, equidistant: bool, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(subpaths) = get_subpaths(layer, document_network) else { continue };
let Some(mirror_angle) = get_mirror_handles(layer, document_network) else { continue };
let Some(colinear_manipulators) = get_colinear_manipulators(layer, document_network) else {
continue;
};
let transform = document_metadata.transform_to_viewport(layer);
let delta = transform.inverse().transform_vector2(delta);
@@ -677,20 +678,19 @@ impl ShapeState {
move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle));
}
if mirror_distance && point.manipulator_type != SelectedType::Anchor {
let mut mirror = mirror_angle.contains(&point.group);
if equidistant && point.manipulator_type != SelectedType::Anchor {
let mut colinear = colinear_manipulators.contains(&point.group);
// If there is no opposing handle, we mirror even if mirror_angle doesn't contain the group
// and set angle mirroring to true.
if !mirror && point.manipulator_type.opposite().get_position(group).is_none() {
// If there is no opposing handle, we consider it colinear
if !colinear && point.manipulator_type.opposite().get_position(group).is_none() {
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: true },
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: group.id, colinear: true },
});
mirror = true;
colinear = true;
}
if mirror {
if colinear {
let Some(mut original_handle_position) = point.manipulator_type.get_position(group) else {
continue;
};
@@ -711,7 +711,7 @@ impl ShapeState {
}
}
/// Delete selected and mirrored handles with zero length when the drag stops.
/// Delete selected and colinear handles with zero length when the drag stops.
pub fn delete_selected_handles_with_zero_length(
&self,
document_network: &NodeNetwork,
@@ -721,7 +721,9 @@ impl ShapeState {
) {
for (&layer, state) in &self.selected_shape_state {
let Some(subpaths) = get_subpaths(layer, document_network) else { continue };
let Some(mirror_angle) = get_mirror_handles(layer, document_network) else { continue };
let Some(colinear_manipulators) = get_colinear_manipulators(layer, document_network) else {
continue;
};
let opposing_handle_lengths = opposing_handle_lengths.as_ref().and_then(|lengths| lengths.get(&layer));
@@ -749,9 +751,9 @@ impl ShapeState {
modification: VectorDataModification::RemoveManipulatorPoint { point },
});
// Remove opposing handle if it is not selected and is mirrored.
// Remove opposing handle if it is not selected and is colinear.
let opposite_point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
if !state.is_selected(opposite_point) && mirror_angle.contains(&point.group) {
if !state.is_selected(opposite_point) && colinear_manipulators.contains(&point.group) {
if let Some(lengths) = opposing_handle_lengths {
if lengths.contains_key(&point.group) {
responses.add(GraphOperationMessage::Vector {
@@ -812,12 +814,14 @@ impl ShapeState {
pub fn reset_opposing_handle_lengths(&self, document_network: &NodeNetwork, opposing_handle_lengths: &OpposingHandleLengths, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(subpaths) = get_subpaths(layer, document_network) else { continue };
let Some(mirror_angle) = get_mirror_handles(layer, document_network) else { continue };
let Some(colinear_manipulators) = get_colinear_manipulators(layer, document_network) else {
continue;
};
let Some(opposing_handle_lengths) = opposing_handle_lengths.get(&layer) else { continue };
for subpath in subpaths {
for manipulator_group in subpath.manipulator_groups() {
if !mirror_angle.contains(&manipulator_group.id) {
if !colinear_manipulators.contains(&manipulator_group.id) {
continue;
}
@@ -1014,33 +1018,27 @@ impl ShapeState {
broken_subpaths.push(bezier_rs::Subpath::new(final_segment, false));
}
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::UpdateSubpaths { subpaths: broken_subpaths },
});
let modification = VectorDataModification::UpdateSubpaths { subpaths: broken_subpaths };
responses.add(GraphOperationMessage::Vector { layer, modification });
}
}
/// Toggle if the handles should mirror angle across the anchor position.
pub fn toggle_handle_mirroring_on_selected(&self, responses: &mut VecDeque<Message>) {
/// Toggle if the handles of the selected points should be colinear.
pub fn toggle_colinear_handles_state_on_selected(&self, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
for point in &state.selected_points {
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group },
})
let modification = VectorDataModification::ToggleManipulatorColinearHandlesState { id: point.group };
responses.add(GraphOperationMessage::Vector { layer, modification })
}
}
}
/// Toggle if the handles should mirror angle across the anchor position.
pub fn set_handle_mirroring_on_selected(&self, mirror_angle: bool, responses: &mut VecDeque<Message>) {
/// Set whether the handles of the selected points should be colinear.
pub fn set_colinear_handles_state_on_selected(&self, colinear: bool, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
for point in &state.selected_points {
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring { id: point.group, mirror_angle },
});
let modification = VectorDataModification::SetManipulatorColinearHandlesState { id: point.group, colinear };
responses.add(GraphOperationMessage::Vector { layer, modification });
}
}
}
@@ -1160,8 +1158,10 @@ impl ShapeState {
}
}
/// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool).
pub fn flip_sharp(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
/// Converts a nearby clicked anchor point's handles between sharp (zero-length handles) and smooth (pulled-apart handle(s)).
/// If both handles aren't zero-length, they are set that. If both are zero-length, they are stretched apart by a reasonable amount.
/// This can can be activated by double clicking on an anchor with the Path tool.
pub fn flip_smooth_sharp(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer| {
let subpaths = get_subpaths(layer, document_network)?;
@@ -1188,31 +1188,30 @@ impl ShapeState {
// Check by comparing the handle positions to the anchor if this manipulator group is a point
let already_sharp = match (manipulator.in_handle, manipulator.out_handle) {
// Check if both handles are zero-length (sharp)
(Some(in_handle), Some(out_handle)) => anchor_position.abs_diff_eq(in_handle, 1e-10) && anchor_position.abs_diff_eq(out_handle, 1e-10),
// Check if the only one handle is zero-length (sharp)
(Some(handle), None) | (None, Some(handle)) => anchor_position.abs_diff_eq(handle, 1e-10),
// No handles mean zero-length (sharp)
(None, None) => true,
};
if already_sharp {
self.smooth_manipulator_group(subpath, index, responses, layer);
self.convert_manipulator_handles_to_colinear(subpath, index, responses, layer);
} else {
// Set in handle position to anchor position
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position },
});
let modification = VectorDataModification::SetManipulatorPosition { point, position: anchor_position };
responses.add(GraphOperationMessage::Vector { layer, modification });
// Set out handle position to anchor position
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position },
});
responses.add(GraphOperationMessage::Vector {
layer,
modification: VectorDataModification::SetManipulatorHandleMirroring {
id: manipulator.id,
mirror_angle: false,
},
});
let modification = VectorDataModification::SetManipulatorPosition { point, position: anchor_position };
responses.add(GraphOperationMessage::Vector { layer, modification });
// Set the manipulator to have non-colinear handles
let modification = VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator.id, colinear: false };
responses.add(GraphOperationMessage::Vector { layer, modification });
};
Some(true)
@@ -327,10 +327,10 @@ impl SnapCandidatePoint {
Self::new(document_point, source, SnapTarget::None)
}
pub fn handle(document_point: DVec2) -> Self {
Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::Sharp))
Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::HandlesFree))
}
pub fn handle_neighbors(document_point: DVec2, neighbors: impl Into<Vec<DVec2>>) -> Self {
let mut point = Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::Sharp));
let mut point = Self::new_source(document_point, SnapSource::Geometry(GeometrySnapSource::HandlesFree));
point.neighbors = neighbors.into();
point
}
@@ -399,34 +399,37 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
continue;
}
let smooth = group_smooth(group, to_document, subpath, index);
let colinear = are_manipulator_handles_colinear(group, to_document, subpath, index);
if smooth && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Smooth)) {
// Smooth points
if colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::HandlesColinear)) {
// Colinear handles
points.push(SnapCandidatePoint::new(
to_document.transform_point2(group.anchor),
SnapSource::Geometry(GeometrySnapSource::Smooth),
SnapTarget::Geometry(GeometrySnapTarget::Smooth),
SnapSource::Geometry(GeometrySnapSource::HandlesColinear),
SnapTarget::Geometry(GeometrySnapTarget::HandlesColinear),
));
} else if !smooth && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Sharp)) {
// Sharp points
} else if !colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::HandlesFree)) {
// Free handles
points.push(SnapCandidatePoint::new(
to_document.transform_point2(group.anchor),
SnapSource::Geometry(GeometrySnapSource::Sharp),
SnapTarget::Geometry(GeometrySnapTarget::Sharp),
SnapSource::Geometry(GeometrySnapSource::HandlesFree),
SnapTarget::Geometry(GeometrySnapTarget::HandlesFree),
));
}
}
}
pub fn group_smooth<Id: bezier_rs::Identifier>(group: &bezier_rs::ManipulatorGroup<Id>, to_document: DAffine2, subpath: &Subpath<Id>, index: usize) -> bool {
/// Returns true if both handles in a manipulator group are colinear, unless the anchor is an endpoint. Endpoint anchors are never considered colinear.
pub fn are_manipulator_handles_colinear<Id: bezier_rs::Identifier>(group: &bezier_rs::ManipulatorGroup<Id>, to_document: DAffine2, subpath: &Subpath<Id>, index: usize) -> bool {
let anchor = group.anchor;
let handle_in = group.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
let handle_out = group.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
let at_end = !subpath.closed() && (index == 0 || index == subpath.len() - 1);
let anchor_is_endpoint = !subpath.closed() && (index == 0 || index == subpath.len() - 1);
handle_in.is_some_and(|handle_in| handle_out.is_some_and(|handle_out| handle_in.angle_between(handle_out) < 1e-5)) && !at_end
// Unless this is an endpoint, check if both handles are colinear (within an angular epsilon)
!anchor_is_endpoint && handle_in.is_some_and(|handle_in| handle_out.is_some_and(|handle_out| handle_in.angle_between(handle_out) < 1e-5))
}
pub fn get_layer_snap_points(layer: LayerNodeIdentifier, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>) {
let document = snap_data.document;
if document.metadata().is_artboard(layer) {