Rename handle mirroring to colinear
This commit is contained in:
parent
ea4f3d8bba
commit
5bca931813
|
|
@ -200,11 +200,9 @@ pub fn default_mapping() -> Mapping {
|
||||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||||
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop { shift_mirror_distance: Shift }),
|
entry!(KeyUp(Lmb); action_dispatch=PathToolMessage::DragStop { equidistant: Shift }),
|
||||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter {
|
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { add_to_selection: Shift }),
|
||||||
add_to_selection: Shift
|
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
||||||
}),
|
|
||||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSharp),
|
|
||||||
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||||
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
||||||
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||||
|
|
|
||||||
|
|
@ -132,8 +132,8 @@ pub enum VectorDataModification {
|
||||||
RemoveManipulatorGroup { id: ManipulatorGroupId },
|
RemoveManipulatorGroup { id: ManipulatorGroupId },
|
||||||
RemoveManipulatorPoint { point: ManipulatorPointId },
|
RemoveManipulatorPoint { point: ManipulatorPointId },
|
||||||
SetClosed { index: usize, closed: bool },
|
SetClosed { index: usize, closed: bool },
|
||||||
SetManipulatorHandleMirroring { id: ManipulatorGroupId, mirror_angle: bool },
|
SetManipulatorColinearHandlesState { id: ManipulatorGroupId, colinear: bool },
|
||||||
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
|
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
|
||||||
ToggleManipulatorHandleMirroring { id: ManipulatorGroupId },
|
ToggleManipulatorColinearHandlesState { id: ManipulatorGroupId },
|
||||||
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> },
|
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -462,8 +462,8 @@ impl<'a> ModifyInputsContext<'a> {
|
||||||
let mut empty = false;
|
let mut empty = false;
|
||||||
|
|
||||||
self.modify_inputs("Shape", false, |inputs, _node_id, _metadata| {
|
self.modify_inputs("Shape", false, |inputs, _node_id, _metadata| {
|
||||||
let [subpaths, mirror_angle_groups] = inputs.as_mut_slice() else {
|
let [subpaths, colinear_manipulators] = inputs.as_mut_slice() else {
|
||||||
panic!("Shape does not have subpath and mirror angle inputs");
|
panic!("Shape does not have both `subpath` and `colinear_manipulators` inputs");
|
||||||
};
|
};
|
||||||
|
|
||||||
let NodeInput::Value {
|
let NodeInput::Value {
|
||||||
|
|
@ -474,16 +474,16 @@ impl<'a> ModifyInputsContext<'a> {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let NodeInput::Value {
|
let NodeInput::Value {
|
||||||
tagged_value: TaggedValue::ManipulatorGroupIds(mirror_angle_groups),
|
tagged_value: TaggedValue::ManipulatorGroupIds(colinear_manipulators),
|
||||||
..
|
..
|
||||||
} = mirror_angle_groups
|
} = colinear_manipulators
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
[old_bounds_min, old_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
[old_bounds_min, old_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||||
|
|
||||||
transform_utils::VectorModificationState { subpaths, mirror_angle_groups }.modify(modification);
|
transform_utils::VectorModificationState { subpaths, colinear_manipulators }.modify(modification);
|
||||||
empty = !subpaths.iter().any(|subpath| !subpath.is_empty());
|
empty = !subpaths.iter().any(|subpath| !subpath.is_empty());
|
||||||
|
|
||||||
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ pub fn nonzero_subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec
|
||||||
|
|
||||||
pub struct VectorModificationState<'a> {
|
pub struct VectorModificationState<'a> {
|
||||||
pub subpaths: &'a mut Vec<Subpath<ManipulatorGroupId>>,
|
pub subpaths: &'a mut Vec<Subpath<ManipulatorGroupId>>,
|
||||||
pub mirror_angle_groups: &'a mut Vec<ManipulatorGroupId>,
|
pub colinear_manipulators: &'a mut Vec<ManipulatorGroupId>,
|
||||||
}
|
}
|
||||||
impl<'a> VectorModificationState<'a> {
|
impl<'a> VectorModificationState<'a> {
|
||||||
fn insert_start(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup<ManipulatorGroupId>) {
|
fn insert_start(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||||
|
|
@ -246,19 +246,19 @@ impl<'a> VectorModificationState<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_mirror(&mut self, id: ManipulatorGroupId, mirror_angle: bool) {
|
fn set_manipulator_colinear_handles_state(&mut self, id: ManipulatorGroupId, colinear: bool) {
|
||||||
if !mirror_angle {
|
if !colinear {
|
||||||
self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id);
|
self.colinear_manipulators.retain(|&manipulator_group_id| manipulator_group_id != id);
|
||||||
} else if !self.mirror_angle_groups.contains(&id) {
|
} else if !self.colinear_manipulators.contains(&id) {
|
||||||
self.mirror_angle_groups.push(id);
|
self.colinear_manipulators.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_mirror(&mut self, id: ManipulatorGroupId) {
|
fn toggle_manipulator_colinear_handles_state(&mut self, id: ManipulatorGroupId) {
|
||||||
if self.mirror_angle_groups.contains(&id) {
|
if self.colinear_manipulators.contains(&id) {
|
||||||
self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id);
|
self.colinear_manipulators.retain(|&manipulator_group_id| manipulator_group_id != id);
|
||||||
} else {
|
} else {
|
||||||
self.mirror_angle_groups.push(id);
|
self.colinear_manipulators.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,7 +271,7 @@ impl<'a> VectorModificationState<'a> {
|
||||||
SelectedType::InHandle => manipulator.in_handle = Some(position),
|
SelectedType::InHandle => manipulator.in_handle = Some(position),
|
||||||
SelectedType::OutHandle => manipulator.out_handle = Some(position),
|
SelectedType::OutHandle => manipulator.out_handle = Some(position),
|
||||||
}
|
}
|
||||||
if point.manipulator_type != SelectedType::Anchor && self.mirror_angle_groups.contains(&point.group) {
|
if point.manipulator_type != SelectedType::Anchor && self.colinear_manipulators.contains(&point.group) {
|
||||||
let reflect = |opposite: DVec2| {
|
let reflect = |opposite: DVec2| {
|
||||||
(manipulator.anchor - position)
|
(manipulator.anchor - position)
|
||||||
.try_normalize()
|
.try_normalize()
|
||||||
|
|
@ -298,9 +298,9 @@ impl<'a> VectorModificationState<'a> {
|
||||||
VectorDataModification::RemoveManipulatorGroup { id } => self.remove_group(id),
|
VectorDataModification::RemoveManipulatorGroup { id } => self.remove_group(id),
|
||||||
VectorDataModification::RemoveManipulatorPoint { point } => self.remove_point(point),
|
VectorDataModification::RemoveManipulatorPoint { point } => self.remove_point(point),
|
||||||
VectorDataModification::SetClosed { index, closed } => self.subpaths[index].set_closed(closed),
|
VectorDataModification::SetClosed { index, closed } => self.subpaths[index].set_closed(closed),
|
||||||
VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle } => self.set_mirror(id, mirror_angle),
|
VectorDataModification::SetManipulatorColinearHandlesState { id, colinear } => self.set_manipulator_colinear_handles_state(id, colinear),
|
||||||
VectorDataModification::SetManipulatorPosition { point, position } => self.set_position(point, position),
|
VectorDataModification::SetManipulatorPosition { point, position } => self.set_position(point, position),
|
||||||
VectorDataModification::ToggleManipulatorHandleMirroring { id } => self.toggle_mirror(id),
|
VectorDataModification::ToggleManipulatorColinearHandlesState { id } => self.toggle_manipulator_colinear_handles_state(id),
|
||||||
VectorDataModification::UpdateSubpaths { subpaths } => *self.subpaths = subpaths,
|
VectorDataModification::UpdateSubpaths { subpaths } => *self.subpaths = subpaths,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2442,7 +2442,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
}),
|
}),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false),
|
DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false),
|
||||||
DocumentInputType::value("Mirror", TaggedValue::ManipulatorGroupIds(vec![]), false),
|
DocumentInputType::value("Colinear Manipulators", TaggedValue::ManipulatorGroupIds(vec![]), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ pub struct SnappingState {
|
||||||
pub geometry_snapping: bool,
|
pub geometry_snapping: bool,
|
||||||
pub grid_snapping: bool,
|
pub grid_snapping: bool,
|
||||||
pub bounds: BoundsSnapping,
|
pub bounds: BoundsSnapping,
|
||||||
pub nodes: NodeSnapping,
|
pub nodes: PointSnapping,
|
||||||
pub grid: GridSnapping,
|
pub grid: GridSnapping,
|
||||||
pub tolerance: f64,
|
pub tolerance: f64,
|
||||||
pub artboards: bool,
|
pub artboards: bool,
|
||||||
|
|
@ -79,11 +79,11 @@ impl Default for SnappingState {
|
||||||
edge_midpoints: false,
|
edge_midpoints: false,
|
||||||
centers: true,
|
centers: true,
|
||||||
},
|
},
|
||||||
nodes: NodeSnapping {
|
nodes: PointSnapping {
|
||||||
paths: true,
|
paths: true,
|
||||||
path_intersections: true,
|
path_intersections: true,
|
||||||
sharp_nodes: true,
|
point_handles_free: true,
|
||||||
smooth_nodes: true,
|
point_handles_colinear: true,
|
||||||
line_midpoints: true,
|
line_midpoints: true,
|
||||||
normals: true,
|
normals: true,
|
||||||
tangents: true,
|
tangents: true,
|
||||||
|
|
@ -110,8 +110,8 @@ impl SnappingState {
|
||||||
BoundingBoxSnapTarget::Center => self.bounds.centers,
|
BoundingBoxSnapTarget::Center => self.bounds.centers,
|
||||||
},
|
},
|
||||||
SnapTarget::Geometry(nodes) if self.geometry_snapping => match nodes {
|
SnapTarget::Geometry(nodes) if self.geometry_snapping => match nodes {
|
||||||
GeometrySnapTarget::Smooth => self.nodes.smooth_nodes,
|
GeometrySnapTarget::HandlesColinear => self.nodes.point_handles_colinear,
|
||||||
GeometrySnapTarget::Sharp => self.nodes.sharp_nodes,
|
GeometrySnapTarget::HandlesFree => self.nodes.point_handles_free,
|
||||||
GeometrySnapTarget::LineMidpoint => self.nodes.line_midpoints,
|
GeometrySnapTarget::LineMidpoint => self.nodes.line_midpoints,
|
||||||
GeometrySnapTarget::Path => self.nodes.paths,
|
GeometrySnapTarget::Path => self.nodes.paths,
|
||||||
GeometrySnapTarget::Normal => self.nodes.normals,
|
GeometrySnapTarget::Normal => self.nodes.normals,
|
||||||
|
|
@ -132,11 +132,11 @@ pub struct BoundsSnapping {
|
||||||
pub centers: bool,
|
pub centers: bool,
|
||||||
}
|
}
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct NodeSnapping {
|
pub struct PointSnapping {
|
||||||
pub paths: bool,
|
pub paths: bool,
|
||||||
pub path_intersections: bool,
|
pub path_intersections: bool,
|
||||||
pub sharp_nodes: bool,
|
pub point_handles_free: bool,
|
||||||
pub smooth_nodes: bool,
|
pub point_handles_colinear: bool,
|
||||||
pub line_midpoints: bool,
|
pub line_midpoints: bool,
|
||||||
pub normals: bool,
|
pub normals: bool,
|
||||||
pub tangents: bool,
|
pub tangents: bool,
|
||||||
|
|
@ -227,8 +227,8 @@ pub enum BoardSnapSource {
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum GeometrySnapSource {
|
pub enum GeometrySnapSource {
|
||||||
Smooth,
|
HandlesColinear,
|
||||||
Sharp,
|
HandlesFree,
|
||||||
LineMidpoint,
|
LineMidpoint,
|
||||||
PathIntersection,
|
PathIntersection,
|
||||||
Handle,
|
Handle,
|
||||||
|
|
@ -258,8 +258,8 @@ pub enum BoundingBoxSnapTarget {
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum GeometrySnapTarget {
|
pub enum GeometrySnapTarget {
|
||||||
Smooth,
|
HandlesColinear,
|
||||||
Sharp,
|
HandlesFree,
|
||||||
LineMidpoint,
|
LineMidpoint,
|
||||||
Path,
|
Path,
|
||||||
Normal,
|
Normal,
|
||||||
|
|
|
||||||
|
|
@ -47,22 +47,20 @@ pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent:
|
||||||
LayerNodeIdentifier::new_unchecked(id)
|
LayerNodeIdentifier::new_unchecked(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Batch set all of the manipulator groups to mirror on a specific layer
|
/// Batch set all of the manipulator groups to set their colinear handle state on a specific layer
|
||||||
pub fn set_manipulator_mirror_angle(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>], layer: LayerNodeIdentifier, mirror_angle: bool, responses: &mut VecDeque<Message>) {
|
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 {
|
for manipulator_group in manipulator_groups {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
layer,
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator_group.id, colinear },
|
||||||
id: manipulator_group.id,
|
|
||||||
mirror_angle,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Locate the subpaths from the shape nodes of a particular layer
|
/// Locate the subpaths from the shape nodes of a particular layer
|
||||||
pub fn get_subpaths(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<Subpath<ManipulatorGroupId>>> {
|
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)
|
Some(subpaths)
|
||||||
} else {
|
} else {
|
||||||
None
|
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)
|
/// 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> {
|
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)
|
Some(*pivot)
|
||||||
} else {
|
} else {
|
||||||
None
|
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)
|
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
|
/// Get the manipulator groups that currently have colinear handles for a particular layer from the shape node
|
||||||
pub fn get_mirror_handles(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<ManipulatorGroupId>> {
|
pub fn get_colinear_manipulators(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<ManipulatorGroupId>> {
|
||||||
if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document_network)?.find_input("Shape", 1)? {
|
let colinear_manipulators_node_input_index = 1;
|
||||||
Some(mirror_handles)
|
if let TaggedValue::ManipulatorGroupIds(manipulator_groups) = NodeGraphLayer::new(layer, document_network).find_input("Shape", colinear_manipulators_node_input_index)? {
|
||||||
|
Some(manipulator_groups)
|
||||||
} else {
|
} else {
|
||||||
None
|
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
|
/// Get the current gradient of a layer from the closest Fill node
|
||||||
pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Gradient> {
|
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 {
|
let TaggedValue::FillType(FillType::Gradient) = inputs.get(1)?.as_value()? else {
|
||||||
return None;
|
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
|
/// Get the current fill of a layer from the closest Fill node
|
||||||
pub fn get_fill_color(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Color> {
|
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 {
|
let TaggedValue::Color(color) = inputs.get(2)?.as_value()? else {
|
||||||
return None;
|
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
|
/// 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> {
|
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 {
|
let TaggedValue::BlendMode(blend_mode) = inputs.get(1)?.as_value()? else {
|
||||||
return None;
|
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
|
/// - 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.
|
/// 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> {
|
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 {
|
let TaggedValue::F64(opacity) = inputs.get(1)?.as_value()? else {
|
||||||
return None;
|
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> {
|
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> {
|
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
|
/// Gets properties from the Text node
|
||||||
pub fn get_text(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<(&String, &Font, f64)> {
|
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 {
|
let NodeInput::Value {
|
||||||
tagged_value: TaggedValue::String(text),
|
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> {
|
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)
|
Some(*width)
|
||||||
} else {
|
} else {
|
||||||
None
|
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.
|
/// 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 {
|
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
|
/// 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.
|
/// An immutable reference to a layer within the document node graph for easy access.
|
||||||
pub struct NodeGraphLayer<'a> {
|
pub struct NodeGraphLayer<'a> {
|
||||||
node_graph: &'a NodeNetwork,
|
node_graph: &'a NodeNetwork,
|
||||||
_outwards_links: HashMap<NodeId, Vec<NodeId>>,
|
|
||||||
layer_node: NodeId,
|
layer_node: NodeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NodeGraphLayer<'a> {
|
impl<'a> NodeGraphLayer<'a> {
|
||||||
/// Get the layer node from the document
|
/// Get the layer node from the document
|
||||||
pub fn new(layer: LayerNodeIdentifier, network: &'a NodeNetwork) -> Option<Self> {
|
pub fn new(layer: LayerNodeIdentifier, network: &'a NodeNetwork) -> Self {
|
||||||
let outwards_links = network.collect_outwards_links();
|
Self {
|
||||||
|
|
||||||
Some(Self {
|
|
||||||
node_graph: network,
|
node_graph: network,
|
||||||
_outwards_links: outwards_links,
|
|
||||||
layer_node: layer.to_node(),
|
layer_node: layer.to_node(),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator up the primary flow of the layer
|
/// 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
|
/// 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> {
|
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()
|
self.find_node_inputs(node_name)?.get(index)?.as_value()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use super::graph_modification_utils;
|
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::consts::{DRAG_THRESHOLD, INSERT_POINT_ON_SEGMENT_TOO_CLOSE_DISTANCE};
|
||||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
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::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource};
|
use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource};
|
||||||
use crate::messages::prelude::*;
|
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 bezier_rs::{Bezier, ManipulatorGroup, TValue};
|
||||||
use graph_craft::document::NodeNetwork;
|
use graph_craft::document::NodeNetwork;
|
||||||
|
|
@ -17,8 +17,8 @@ use glam::DVec2;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
pub enum ManipulatorAngle {
|
pub enum ManipulatorAngle {
|
||||||
Smooth,
|
Colinear,
|
||||||
Sharp,
|
Free,
|
||||||
Mixed,
|
Mixed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,10 +277,10 @@ impl ShapeState {
|
||||||
}
|
}
|
||||||
let source = if handle.is_handle() {
|
let source = if handle.is_handle() {
|
||||||
SnapSource::Geometry(GeometrySnapSource::Handle)
|
SnapSource::Geometry(GeometrySnapSource::Handle)
|
||||||
} else if group_smooth(group, to_document, subpath, index) {
|
} else if are_manipulator_handles_colinear(group, to_document, subpath, index) {
|
||||||
SnapSource::Geometry(GeometrySnapSource::Smooth)
|
SnapSource::Geometry(GeometrySnapSource::HandlesColinear)
|
||||||
} else {
|
} else {
|
||||||
SnapSource::Geometry(GeometrySnapSource::Sharp)
|
SnapSource::Geometry(GeometrySnapSource::HandlesFree)
|
||||||
};
|
};
|
||||||
let Some(position) = handle.get_position(group) else { continue };
|
let Some(position) = handle.get_position(group) else { continue };
|
||||||
let mut point = SnapCandidatePoint::new_source(to_document.transform_point2(position) + mouse_delta, source);
|
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() {
|
if point.manipulator_type.is_handle() {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
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(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterates over the selected manipulator groups, returning whether they have mixed, sharp, or smooth angles.
|
/// 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.
|
/// If there are no points selected this function returns mixed.
|
||||||
pub fn selected_manipulator_angles(&self, document_network: &NodeNetwork) -> ManipulatorAngle {
|
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.
|
// This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles.
|
||||||
let mut point_smoothness_status = self
|
let mut points_colinear_status = self
|
||||||
.selected_shape_state
|
.selected_shape_state
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(&layer, selection_state)| Some((graph_modification_utils::get_mirror_handles(layer, document_network)?, selection_state)))
|
.filter_map(|(&layer, selection_state)| Some((graph_modification_utils::get_colinear_manipulators(layer, document_network)?, selection_state)))
|
||||||
.flat_map(|(mirror, selection_state)| selection_state.selected_points.iter().map(|selected_point| mirror.contains(&selected_point.group)));
|
.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 };
|
let Some(first_is_colinear) = points_colinear_status.next() else { return ManipulatorAngle::Mixed };
|
||||||
|
if points_colinear_status.any(|point| first_is_colinear != point) {
|
||||||
if point_smoothness_status.any(|point| first_is_smooth != point) {
|
|
||||||
return ManipulatorAngle::Mixed;
|
return ManipulatorAngle::Mixed;
|
||||||
}
|
}
|
||||||
match first_is_smooth {
|
|
||||||
false => ManipulatorAngle::Sharp,
|
match first_is_colinear {
|
||||||
true => ManipulatorAngle::Smooth,
|
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_groups = subpath.manipulator_groups();
|
||||||
let manipulator = manipulator_groups[index];
|
let manipulator = manipulator_groups[index];
|
||||||
|
|
||||||
|
|
@ -542,13 +542,10 @@ impl ShapeState {
|
||||||
(None, None) => return,
|
(None, None) => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mirror the angle but not the distance
|
// Set the manipulator to have colinear handles
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
layer,
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator.id, colinear: true },
|
||||||
id: manipulator.id,
|
|
||||||
mirror_angle: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let (sin, cos) = handle_direction.sin_cos();
|
let (sin, cos) = handle_direction.sin_cos();
|
||||||
|
|
@ -569,7 +566,6 @@ impl ShapeState {
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle },
|
modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(out_handle) = length_next.map(|length| anchor_position - handle_vector * length) {
|
if let Some(out_handle) = length_next.map(|length| anchor_position - handle_vector * length) {
|
||||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
|
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
|
||||||
responses.add(GraphOperationMessage::Vector {
|
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.
|
/// Converts all selected points to colinear while moving the handles to ensure their 180° angle separation.
|
||||||
pub fn smooth_selected_groups(&self, responses: &mut VecDeque<Message>, document_network: &NodeNetwork) -> Option<()> {
|
/// 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();
|
let mut skip_set = HashSet::new();
|
||||||
|
|
||||||
for (&layer, layer_state) in self.selected_shape_state.iter() {
|
for (&layer, layer_state) in self.selected_shape_state.iter() {
|
||||||
|
|
@ -593,10 +592,6 @@ impl ShapeState {
|
||||||
|
|
||||||
skip_set.insert(point.group);
|
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 {
|
let out_selected = layer_state.selected_points.contains(&ManipulatorPointId {
|
||||||
group: point.group,
|
group: point.group,
|
||||||
manipulator_type: SelectedType::OutHandle,
|
manipulator_type: SelectedType::OutHandle,
|
||||||
|
|
@ -607,8 +602,9 @@ impl ShapeState {
|
||||||
});
|
});
|
||||||
let group = graph_modification_utils::get_manipulator_from_id(subpaths, point.group)?;
|
let group = graph_modification_utils::get_manipulator_from_id(subpaths, point.group)?;
|
||||||
|
|
||||||
match (anchor_selected, out_selected, in_selected) {
|
match (out_selected, in_selected) {
|
||||||
(_, true, false) => {
|
// 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);
|
let out_handle = ManipulatorPointId::new(point.group, SelectedType::OutHandle);
|
||||||
if let Some(position) = group.out_handle {
|
if let Some(position) = group.out_handle {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
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);
|
let in_handle = ManipulatorPointId::new(point.group, SelectedType::InHandle);
|
||||||
if let Some(position) = group.in_handle {
|
if let Some(position) = group.in_handle {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
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 found = subpaths.iter().find_map(|subpath| {
|
||||||
let group_slice = subpath.manipulator_groups();
|
let group_slice = subpath.manipulator_groups();
|
||||||
let index = group_slice.iter().position(|manipulator| manipulator.id == group.id)?;
|
let index = group_slice.iter().position(|manipulator| manipulator.id == group.id)?;
|
||||||
|
|
@ -635,7 +634,7 @@ impl ShapeState {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((subpath, index)) = found {
|
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.
|
/// 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 {
|
for (&layer, state) in &self.selected_shape_state {
|
||||||
let Some(subpaths) = get_subpaths(layer, document_network) else { continue };
|
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 transform = document_metadata.transform_to_viewport(layer);
|
||||||
let delta = transform.inverse().transform_vector2(delta);
|
let delta = transform.inverse().transform_vector2(delta);
|
||||||
|
|
@ -677,20 +678,19 @@ impl ShapeState {
|
||||||
move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle));
|
move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle));
|
||||||
}
|
}
|
||||||
|
|
||||||
if mirror_distance && point.manipulator_type != SelectedType::Anchor {
|
if equidistant && point.manipulator_type != SelectedType::Anchor {
|
||||||
let mut mirror = mirror_angle.contains(&point.group);
|
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
|
// If there is no opposing handle, we consider it colinear
|
||||||
// and set angle mirroring to true.
|
if !colinear && point.manipulator_type.opposite().get_position(group).is_none() {
|
||||||
if !mirror && point.manipulator_type.opposite().get_position(group).is_none() {
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
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 {
|
let Some(mut original_handle_position) = point.manipulator_type.get_position(group) else {
|
||||||
continue;
|
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(
|
pub fn delete_selected_handles_with_zero_length(
|
||||||
&self,
|
&self,
|
||||||
document_network: &NodeNetwork,
|
document_network: &NodeNetwork,
|
||||||
|
|
@ -721,7 +721,9 @@ impl ShapeState {
|
||||||
) {
|
) {
|
||||||
for (&layer, state) in &self.selected_shape_state {
|
for (&layer, state) in &self.selected_shape_state {
|
||||||
let Some(subpaths) = get_subpaths(layer, document_network) else { continue };
|
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));
|
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 },
|
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());
|
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 let Some(lengths) = opposing_handle_lengths {
|
||||||
if lengths.contains_key(&point.group) {
|
if lengths.contains_key(&point.group) {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
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>) {
|
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 {
|
for (&layer, state) in &self.selected_shape_state {
|
||||||
let Some(subpaths) = get_subpaths(layer, document_network) else { continue };
|
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 };
|
let Some(opposing_handle_lengths) = opposing_handle_lengths.get(&layer) else { continue };
|
||||||
|
|
||||||
for subpath in subpaths {
|
for subpath in subpaths {
|
||||||
for manipulator_group in subpath.manipulator_groups() {
|
for manipulator_group in subpath.manipulator_groups() {
|
||||||
if !mirror_angle.contains(&manipulator_group.id) {
|
if !colinear_manipulators.contains(&manipulator_group.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1014,33 +1018,27 @@ impl ShapeState {
|
||||||
broken_subpaths.push(bezier_rs::Subpath::new(final_segment, false));
|
broken_subpaths.push(bezier_rs::Subpath::new(final_segment, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
let modification = VectorDataModification::UpdateSubpaths { subpaths: broken_subpaths };
|
||||||
layer,
|
responses.add(GraphOperationMessage::Vector { layer, modification });
|
||||||
modification: VectorDataModification::UpdateSubpaths { subpaths: broken_subpaths },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle if the handles should mirror angle across the anchor position.
|
/// Toggle if the handles of the selected points should be colinear.
|
||||||
pub fn toggle_handle_mirroring_on_selected(&self, responses: &mut VecDeque<Message>) {
|
pub fn toggle_colinear_handles_state_on_selected(&self, responses: &mut VecDeque<Message>) {
|
||||||
for (&layer, state) in &self.selected_shape_state {
|
for (&layer, state) in &self.selected_shape_state {
|
||||||
for point in &state.selected_points {
|
for point in &state.selected_points {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
let modification = VectorDataModification::ToggleManipulatorColinearHandlesState { id: point.group };
|
||||||
layer,
|
responses.add(GraphOperationMessage::Vector { layer, modification })
|
||||||
modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle if the handles should mirror angle across the anchor position.
|
/// Set whether the handles of the selected points should be colinear.
|
||||||
pub fn set_handle_mirroring_on_selected(&self, mirror_angle: bool, responses: &mut VecDeque<Message>) {
|
pub fn set_colinear_handles_state_on_selected(&self, colinear: bool, responses: &mut VecDeque<Message>) {
|
||||||
for (&layer, state) in &self.selected_shape_state {
|
for (&layer, state) in &self.selected_shape_state {
|
||||||
for point in &state.selected_points {
|
for point in &state.selected_points {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
let modification = VectorDataModification::SetManipulatorColinearHandlesState { id: point.group, colinear };
|
||||||
layer,
|
responses.add(GraphOperationMessage::Vector { layer, modification });
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id: point.group, mirror_angle },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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).
|
/// Converts a nearby clicked anchor point's handles between sharp (zero-length handles) and smooth (pulled-apart handle(s)).
|
||||||
pub fn flip_sharp(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
|
/// 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 mut process_layer = |layer| {
|
||||||
let subpaths = get_subpaths(layer, document_network)?;
|
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
|
// 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) {
|
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),
|
(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),
|
(Some(handle), None) | (None, Some(handle)) => anchor_position.abs_diff_eq(handle, 1e-10),
|
||||||
|
// No handles mean zero-length (sharp)
|
||||||
(None, None) => true,
|
(None, None) => true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if already_sharp {
|
if already_sharp {
|
||||||
self.smooth_manipulator_group(subpath, index, responses, layer);
|
self.convert_manipulator_handles_to_colinear(subpath, index, responses, layer);
|
||||||
} else {
|
} else {
|
||||||
|
// Set in handle position to anchor position
|
||||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
|
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
|
||||||
responses.add(GraphOperationMessage::Vector {
|
let modification = VectorDataModification::SetManipulatorPosition { point, position: anchor_position };
|
||||||
layer,
|
responses.add(GraphOperationMessage::Vector { layer, modification });
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position },
|
|
||||||
});
|
// Set out handle position to anchor position
|
||||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
|
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
|
||||||
responses.add(GraphOperationMessage::Vector {
|
let modification = VectorDataModification::SetManipulatorPosition { point, position: anchor_position };
|
||||||
layer,
|
responses.add(GraphOperationMessage::Vector { layer, modification });
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: anchor_position },
|
|
||||||
});
|
// Set the manipulator to have non-colinear handles
|
||||||
responses.add(GraphOperationMessage::Vector {
|
let modification = VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator.id, colinear: false };
|
||||||
layer,
|
responses.add(GraphOperationMessage::Vector { layer, modification });
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
|
||||||
id: manipulator.id,
|
|
||||||
mirror_angle: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(true)
|
Some(true)
|
||||||
|
|
|
||||||
|
|
@ -327,10 +327,10 @@ impl SnapCandidatePoint {
|
||||||
Self::new(document_point, source, SnapTarget::None)
|
Self::new(document_point, source, SnapTarget::None)
|
||||||
}
|
}
|
||||||
pub fn handle(document_point: DVec2) -> Self {
|
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 {
|
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.neighbors = neighbors.into();
|
||||||
point
|
point
|
||||||
}
|
}
|
||||||
|
|
@ -399,34 +399,37 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||||
continue;
|
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)) {
|
if colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::HandlesColinear)) {
|
||||||
// Smooth points
|
// Colinear handles
|
||||||
points.push(SnapCandidatePoint::new(
|
points.push(SnapCandidatePoint::new(
|
||||||
to_document.transform_point2(group.anchor),
|
to_document.transform_point2(group.anchor),
|
||||||
SnapSource::Geometry(GeometrySnapSource::Smooth),
|
SnapSource::Geometry(GeometrySnapSource::HandlesColinear),
|
||||||
SnapTarget::Geometry(GeometrySnapTarget::Smooth),
|
SnapTarget::Geometry(GeometrySnapTarget::HandlesColinear),
|
||||||
));
|
));
|
||||||
} else if !smooth && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Sharp)) {
|
} else if !colinear && document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::HandlesFree)) {
|
||||||
// Sharp points
|
// Free handles
|
||||||
points.push(SnapCandidatePoint::new(
|
points.push(SnapCandidatePoint::new(
|
||||||
to_document.transform_point2(group.anchor),
|
to_document.transform_point2(group.anchor),
|
||||||
SnapSource::Geometry(GeometrySnapSource::Sharp),
|
SnapSource::Geometry(GeometrySnapSource::HandlesFree),
|
||||||
SnapTarget::Geometry(GeometrySnapTarget::Sharp),
|
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 anchor = group.anchor;
|
||||||
let handle_in = group.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
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 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>) {
|
pub fn get_layer_snap_points(layer: LayerNodeIdentifier, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>) {
|
||||||
let document = snap_data.document;
|
let document = snap_data.document;
|
||||||
if document.metadata().is_artboard(layer) {
|
if document.metadata().is_artboard(layer) {
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ impl Fsm for EllipseToolFsmState {
|
||||||
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
|
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
|
||||||
let manipulator_groups = subpath.manipulator_groups().to_vec();
|
let manipulator_groups = subpath.manipulator_groups().to_vec();
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses);
|
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses);
|
||||||
graph_modification_utils::set_manipulator_mirror_angle(&manipulator_groups, layer, true, responses);
|
graph_modification_utils::set_manipulator_colinear_handles_state(&manipulator_groups, layer, true, responses);
|
||||||
shape_data.layer = Some(layer);
|
shape_data.layer = Some(layer);
|
||||||
|
|
||||||
let fill_color = tool_options.fill.active_color();
|
let fill_color = tool_options.fill.active_color();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::messages::portfolio::document::overlays::utility_functions::path_over
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_mirror_handles, get_subpaths};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{get_colinear_manipulators, get_manipulator_from_id, get_subpaths};
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{ClosestSegment, ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
use crate::messages::tool::common_functionality::shape_editor::{ClosestSegment, ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
||||||
|
|
||||||
|
|
@ -34,19 +34,19 @@ pub enum PathToolMessage {
|
||||||
Delete,
|
Delete,
|
||||||
DeleteAndBreakPath,
|
DeleteAndBreakPath,
|
||||||
DragStop {
|
DragStop {
|
||||||
shift_mirror_distance: Key,
|
equidistant: Key,
|
||||||
},
|
},
|
||||||
Enter {
|
Enter {
|
||||||
add_to_selection: Key,
|
add_to_selection: Key,
|
||||||
},
|
},
|
||||||
Escape,
|
Escape,
|
||||||
FlipSharp,
|
FlipSmoothSharp,
|
||||||
GRS {
|
GRS {
|
||||||
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
|
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
|
||||||
key: Key,
|
key: Key,
|
||||||
},
|
},
|
||||||
ManipulatorAngleMakeSharp,
|
ManipulatorMakeHandlesFree,
|
||||||
ManipulatorAngleMakeSmooth,
|
ManipulatorMakeHandlesColinear,
|
||||||
MouseDown {
|
MouseDown {
|
||||||
ctrl: Key,
|
ctrl: Key,
|
||||||
shift: Key,
|
shift: Key,
|
||||||
|
|
@ -126,23 +126,37 @@ impl LayoutHolder for PathTool {
|
||||||
let related_seperator = Separator::new(SeparatorType::Related).widget_holder();
|
let related_seperator = Separator::new(SeparatorType::Related).widget_holder();
|
||||||
let unrelated_seperator = Separator::new(SeparatorType::Unrelated).widget_holder();
|
let unrelated_seperator = Separator::new(SeparatorType::Unrelated).widget_holder();
|
||||||
|
|
||||||
let manipulator_angle_options = vec![
|
let colinear_handles_tooltip = "Ensures both handles remain 180° apart";
|
||||||
RadioEntryData::new("smooth").label("Smooth").on_update(|_| PathToolMessage::ManipulatorAngleMakeSmooth.into()),
|
let colinear_handles_state = manipulator_angle.and_then(|angle| match angle {
|
||||||
RadioEntryData::new("sharp").label("Sharp").on_update(|_| PathToolMessage::ManipulatorAngleMakeSharp.into()),
|
ManipulatorAngle::Colinear => Some(true),
|
||||||
];
|
ManipulatorAngle::Free => Some(false),
|
||||||
let manipulator_angle_index = manipulator_angle.and_then(|angle| match angle {
|
|
||||||
ManipulatorAngle::Smooth => Some(0),
|
|
||||||
ManipulatorAngle::Sharp => Some(1),
|
|
||||||
ManipulatorAngle::Mixed => None,
|
ManipulatorAngle::Mixed => None,
|
||||||
});
|
})
|
||||||
|
// TODO: Remove `unwrap_or_default` once checkboxes are capable of displaying a mixed state
|
||||||
let manipulator_angle_radio = RadioInput::new(manipulator_angle_options)
|
.unwrap_or_default();
|
||||||
|
let colinear_handle_checkbox = CheckboxInput::new(colinear_handles_state)
|
||||||
.disabled(self.tool_data.selection_status.is_none())
|
.disabled(self.tool_data.selection_status.is_none())
|
||||||
.selected_index(manipulator_angle_index)
|
.on_update(|&CheckboxInput { checked, .. }| {
|
||||||
|
if checked {
|
||||||
|
PathToolMessage::ManipulatorMakeHandlesColinear.into()
|
||||||
|
} else {
|
||||||
|
PathToolMessage::ManipulatorMakeHandlesFree.into()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.tooltip(colinear_handles_tooltip)
|
||||||
.widget_holder();
|
.widget_holder();
|
||||||
|
let colinear_handles_label = TextLabel::new("Colinear Handles").tooltip(colinear_handles_tooltip).widget_holder();
|
||||||
|
|
||||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
widgets: vec![x_location, related_seperator, y_location, unrelated_seperator, manipulator_angle_radio],
|
widgets: vec![
|
||||||
|
x_location,
|
||||||
|
related_seperator.clone(),
|
||||||
|
y_location,
|
||||||
|
unrelated_seperator,
|
||||||
|
colinear_handle_checkbox,
|
||||||
|
related_seperator,
|
||||||
|
colinear_handles_label,
|
||||||
|
],
|
||||||
}]))
|
}]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +178,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||||
|
|
||||||
match self.fsm_state {
|
match self.fsm_state {
|
||||||
Ready => actions!(PathToolMessageDiscriminant;
|
Ready => actions!(PathToolMessageDiscriminant;
|
||||||
FlipSharp,
|
FlipSmoothSharp,
|
||||||
MouseDown,
|
MouseDown,
|
||||||
Delete,
|
Delete,
|
||||||
NudgeSelectedPoints,
|
NudgeSelectedPoints,
|
||||||
|
|
@ -177,7 +191,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||||
Dragging => actions!(PathToolMessageDiscriminant;
|
Dragging => actions!(PathToolMessageDiscriminant;
|
||||||
Escape,
|
Escape,
|
||||||
RightClick,
|
RightClick,
|
||||||
FlipSharp,
|
FlipSmoothSharp,
|
||||||
DragStop,
|
DragStop,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
Delete,
|
Delete,
|
||||||
|
|
@ -185,7 +199,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||||
DeleteAndBreakPath,
|
DeleteAndBreakPath,
|
||||||
),
|
),
|
||||||
DrawingBox => actions!(PathToolMessageDiscriminant;
|
DrawingBox => actions!(PathToolMessageDiscriminant;
|
||||||
FlipSharp,
|
FlipSmoothSharp,
|
||||||
DragStop,
|
DragStop,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
Delete,
|
Delete,
|
||||||
|
|
@ -378,7 +392,7 @@ impl PathToolData {
|
||||||
// Check if the alt key has just been pressed
|
// Check if the alt key has just been pressed
|
||||||
if alt && !self.alt_debounce {
|
if alt && !self.alt_debounce {
|
||||||
self.opposing_handle_lengths = None;
|
self.opposing_handle_lengths = None;
|
||||||
shape_editor.toggle_handle_mirroring_on_selected(responses);
|
shape_editor.toggle_colinear_handles_state_on_selected(responses);
|
||||||
}
|
}
|
||||||
self.alt_debounce = alt;
|
self.alt_debounce = alt;
|
||||||
|
|
||||||
|
|
@ -545,21 +559,21 @@ impl Fsm for PathToolFsmState {
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
// Mouse up
|
// Mouse up
|
||||||
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
(PathToolFsmState::DrawingBox, PathToolMessage::DragStop { equidistant }) => {
|
||||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
let equidistant = input.keyboard.get(equidistant as usize);
|
||||||
|
|
||||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||||
} else {
|
} else {
|
||||||
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !equidistant);
|
||||||
}
|
}
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
(_, PathToolMessage::DragStop { equidistant }) => {
|
||||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
let equidistant = input.keyboard.get(equidistant as usize);
|
||||||
|
|
||||||
let nearest_point = shape_editor
|
let nearest_point = shape_editor
|
||||||
.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD)
|
.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD)
|
||||||
|
|
@ -567,7 +581,7 @@ impl Fsm for PathToolFsmState {
|
||||||
|
|
||||||
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.metadata, &tool_data.opposing_handle_lengths, responses);
|
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.metadata, &tool_data.opposing_handle_lengths, responses);
|
||||||
|
|
||||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !equidistant {
|
||||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
|
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
|
||||||
if clicked_selected {
|
if clicked_selected {
|
||||||
shape_editor.deselect_all_points();
|
shape_editor.deselect_all_points();
|
||||||
|
|
@ -598,9 +612,9 @@ impl Fsm for PathToolFsmState {
|
||||||
shape_editor.delete_point_and_break_path(&document.network, responses);
|
shape_editor.delete_point_and_break_path(&document.network, responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::FlipSharp) => {
|
(_, PathToolMessage::FlipSmoothSharp) => {
|
||||||
if !tool_data.double_click_handled {
|
if !tool_data.double_click_handled {
|
||||||
shape_editor.flip_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
|
shape_editor.flip_smooth_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
|
|
@ -641,16 +655,16 @@ impl Fsm for PathToolFsmState {
|
||||||
tool_data.selection_status = get_selection_status(&document.network, &document.metadata, shape_editor);
|
tool_data.selection_status = get_selection_status(&document.network, &document.metadata, shape_editor);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {
|
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_editor.set_handle_mirroring_on_selected(true, responses);
|
shape_editor.set_colinear_handles_state_on_selected(true, responses);
|
||||||
shape_editor.smooth_selected_groups(responses, &document.network);
|
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, &document.network);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::ManipulatorAngleMakeSharp) => {
|
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_editor.set_handle_mirroring_on_selected(false, responses);
|
shape_editor.set_colinear_handles_state_on_selected(false, responses);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
@ -663,6 +677,8 @@ impl Fsm for PathToolFsmState {
|
||||||
PathToolFsmState::Ready => HintData(vec![
|
PathToolFsmState::Ready => HintData(vec![
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]),
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
|
||||||
|
// TODO: Only show if at least one anchor is selected, and dynamically show either "Smooth" or "Sharp" based on the current state
|
||||||
|
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDouble, "Make Anchor Smooth/Sharp")]),
|
||||||
// TODO: Only show the following hints if at least one point is selected
|
// TODO: Only show the following hints if at least one point is selected
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
|
||||||
HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected")]),
|
HintGroup(vec![HintInfo::keys([Key::KeyG, Key::KeyR, Key::KeyS], "Grab/Rotate/Scale Selected")]),
|
||||||
|
|
@ -677,15 +693,13 @@ impl Fsm for PathToolFsmState {
|
||||||
PathToolFsmState::Dragging => HintData(vec![
|
PathToolFsmState::Dragging => HintData(vec![
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
// TODO: Make hint dynamically say "Make Handle Smooth" or "Make Handle Sharp" based on its current state
|
// TODO: Switch this to the "S" key. Also, make the hint dynamically say "Make Colinear" or "Make Not Colinear" based on its current state. And only
|
||||||
// TODO: Switch this to the "S" key
|
// TODO: show this hint if a handle (not an anchor) is being dragged, and disable that shortcut so it can't be pressed even with the hint not shown.
|
||||||
// TODO: Only show this if a handle (not an anchor) is being dragged, and disable that shortcut so it can't be pressed even with the hint not shown
|
HintInfo::keys([Key::Alt], "Toggle Colinear Handles"),
|
||||||
HintInfo::keys([Key::Alt], "Toggle Smooth/Sharp Handles"),
|
// TODO: Switch this to the "Alt" key (since it's equivalent to the "From Center" modifier when drawing a line). And show this only when a handle is being dragged.
|
||||||
// TODO: Switch this to the "Alt" key (since it's equivalent to the "From Center" modifier when drawing a line)
|
HintInfo::keys([Key::Shift], "Equidistant Handles"),
|
||||||
// TODO: Show this only when a handle is being dragged
|
// TODO: Add "Snap 15°" modifier with the "Shift" key (only when a handle is being dragged).
|
||||||
HintInfo::keys([Key::Shift], "Equidistant Handles (Smooth Only)"),
|
// TODO: Add "Lock Angle" modifier with the "Ctrl" key (only when a handle is being dragged).
|
||||||
// TODO: Add "Snap 15°" modifier with the "Shift" key (only when a handle is being dragged)
|
|
||||||
// TODO: Add "Lock Angle" modifier with the "Ctrl" key (only when a handle is being dragged)
|
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
PathToolFsmState::DrawingBox => HintData(vec![
|
PathToolFsmState::DrawingBox => HintData(vec![
|
||||||
|
|
@ -761,28 +775,31 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
|
||||||
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
|
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(subpaths) = get_subpaths(layer, document_network) else {
|
let Some(subpaths) = get_subpaths(layer, document_network) else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
let Some(mirror) = get_mirror_handles(layer, document_network) else {
|
let Some(colinear_manipulators) = get_colinear_manipulators(layer, document_network) else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
let Some(point) = shape_state.selected_points().next() else {
|
let Some(point) = shape_state.selected_points().next() else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
|
let Some(manipulator) = get_manipulator_from_id(subpaths, point.group) else {
|
||||||
let Some(group) = get_manipulator_from_id(subpaths, point.group) else {
|
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
let Some(local_position) = point.manipulator_type.get_position(group) else {
|
let Some(local_position) = point.manipulator_type.get_position(manipulator) else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let manipulator_angle = if mirror.contains(&point.group) { ManipulatorAngle::Smooth } else { ManipulatorAngle::Sharp };
|
let coordinates = document_metadata.transform_to_document(layer).transform_point2(local_position);
|
||||||
|
let manipulator_angle = if colinear_manipulators.contains(&point.group) {
|
||||||
|
ManipulatorAngle::Colinear
|
||||||
|
} else {
|
||||||
|
ManipulatorAngle::Free
|
||||||
|
};
|
||||||
|
|
||||||
return SelectionStatus::One(SingleSelectedPoint {
|
return SelectionStatus::One(SingleSelectedPoint {
|
||||||
coordinates: document_metadata.transform_to_document(layer).transform_point2(local_position),
|
coordinates,
|
||||||
layer,
|
layer,
|
||||||
id: *point,
|
id: *point,
|
||||||
manipulator_angle,
|
manipulator_angle,
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ struct PenToolData {
|
||||||
layer: Option<LayerNodeIdentifier>,
|
layer: Option<LayerNodeIdentifier>,
|
||||||
subpath_index: usize,
|
subpath_index: usize,
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
should_mirror: bool,
|
colinear_handles: bool,
|
||||||
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
|
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
|
||||||
from_start: bool,
|
from_start: bool,
|
||||||
angle: f64,
|
angle: f64,
|
||||||
|
|
@ -212,21 +212,16 @@ impl PenToolData {
|
||||||
self.from_start = from_start;
|
self.from_start = from_start;
|
||||||
self.subpath_index = subpath_index;
|
self.subpath_index = subpath_index;
|
||||||
|
|
||||||
// Stop the handles on the first point from mirroring
|
let Some(subpaths) = get_subpaths(layer, &document.network) else { return };
|
||||||
let Some(subpaths) = get_subpaths(layer, &document.network) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let manipulator_groups = subpaths[subpath_index].manipulator_groups();
|
let manipulator_groups = subpaths[subpath_index].manipulator_groups();
|
||||||
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else {
|
let first_or_last = if from_start { manipulator_groups.first() } else { manipulator_groups.last() };
|
||||||
return;
|
let Some(last_handle) = first_or_last else { return };
|
||||||
};
|
let id = last_handle.id;
|
||||||
|
|
||||||
|
// Stop the handles on the first point from being colinear
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
layer,
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
||||||
id: last_handle.id,
|
|
||||||
mirror_angle: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,53 +265,56 @@ impl PenToolData {
|
||||||
self.subpath_index = 0;
|
self.subpath_index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tooltip / user documentation?
|
/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
|
||||||
/// If you place the anchor on top of the previous anchor then you break the mirror
|
fn bend_from_previous_point(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
||||||
fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<()> {
|
(|| -> Option<()> {
|
||||||
// Get subpath
|
// Get subpath
|
||||||
let layer = self.layer?;
|
let layer = self.layer?;
|
||||||
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
|
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
|
||||||
|
|
||||||
// Get the last manipulator group and the one previous to that
|
// Get the last manipulator group and the one previous to that
|
||||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||||
|
|
||||||
// Get correct handle types
|
// Get correct handle types
|
||||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||||
|
|
||||||
// Get manipulator points
|
// Get manipulator points
|
||||||
let last_anchor = last_manipulator_group.anchor;
|
let last_anchor = last_manipulator_group.anchor;
|
||||||
let previous_anchor = previous_manipulator_group.anchor;
|
let previous_anchor = previous_manipulator_group.anchor;
|
||||||
|
|
||||||
// Break the control
|
// Break the control
|
||||||
let transform = document.metadata.document_to_viewport * transform;
|
let transform = document.metadata.document_to_viewport * transform;
|
||||||
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||||
if !on_top {
|
if !on_top {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Remove the point that has just been placed
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
|
||||||
layer,
|
|
||||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Move the in handle of the previous anchor to on top of the previous position
|
// Remove the point that has just been placed
|
||||||
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
|
responses.add(GraphOperationMessage::Vector {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
layer,
|
||||||
layer,
|
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Stop the handles on the last point from mirroring
|
// Move the in handle of the previous anchor to on top of the previous position
|
||||||
let id = previous_manipulator_group.id;
|
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
layer,
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
|
||||||
});
|
});
|
||||||
|
|
||||||
self.should_mirror = false;
|
// Stop the handles on the last point from being colinear
|
||||||
None
|
let id = previous_manipulator_group.id;
|
||||||
|
responses.add(GraphOperationMessage::Vector {
|
||||||
|
layer,
|
||||||
|
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
self.colinear_handles = false;
|
||||||
|
|
||||||
|
None
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||||
|
|
@ -357,11 +355,11 @@ impl PenToolData {
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
|
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stop the handles on the first point from mirroring
|
// Stop the handles on the first point from being colinear
|
||||||
let id = first_manipulator_group.id;
|
let id = first_manipulator_group.id;
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
layer,
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove the point that has just been placed
|
// Remove the point that has just been placed
|
||||||
|
|
@ -409,11 +407,11 @@ impl PenToolData {
|
||||||
// Get manipulator points
|
// Get manipulator points
|
||||||
let last_anchor = last_manipulator_group.anchor;
|
let last_anchor = last_manipulator_group.anchor;
|
||||||
|
|
||||||
let should_mirror = !modifiers.break_handle && self.should_mirror;
|
let colinear = !modifiers.break_handle && self.colinear_handles;
|
||||||
|
|
||||||
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
|
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
|
||||||
let pos = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, should_mirror, mouse, Some(last_anchor), false);
|
let position = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, colinear, mouse, Some(last_anchor), false);
|
||||||
if !pos.is_finite() {
|
if !position.is_finite() {
|
||||||
return Some(PenToolFsmState::DraggingHandle);
|
return Some(PenToolFsmState::DraggingHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,25 +419,25 @@ impl PenToolData {
|
||||||
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer: self.layer?,
|
layer: self.layer?,
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mirror handle of last segment
|
// Place the previous anchor's in handle at the opposing position
|
||||||
if should_mirror {
|
if colinear {
|
||||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||||
let pos = last_anchor - (pos - last_anchor);
|
let position = last_anchor - (position - last_anchor);
|
||||||
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
|
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer: self.layer?,
|
layer: self.layer?,
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the mirror status of the currently modifying point
|
// Update the colinear handles status of the currently modifying point
|
||||||
let id = last_manipulator_group.id;
|
let id = last_manipulator_group.id;
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer: self.layer?,
|
layer: self.layer?,
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: should_mirror },
|
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear },
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(PenToolFsmState::DraggingHandle)
|
Some(PenToolFsmState::DraggingHandle)
|
||||||
|
|
@ -487,7 +485,7 @@ impl PenToolData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snap the angle of the line from relative to position if the key is pressed.
|
/// Snap the angle of the line from relative to position if the key is pressed.
|
||||||
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, mirror: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
|
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, colinear: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
|
||||||
let document = snap_data.document;
|
let document = snap_data.document;
|
||||||
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
|
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
|
||||||
let snap = &mut self.snap_manager;
|
let snap = &mut self.snap_manager;
|
||||||
|
|
@ -509,7 +507,7 @@ impl PenToolData {
|
||||||
};
|
};
|
||||||
let near_point = SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone());
|
let near_point = SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone());
|
||||||
let far_point = SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors);
|
let far_point = SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors);
|
||||||
if mirror {
|
if colinear {
|
||||||
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
|
let snapped = snap.constrained_snap(&snap_data, &near_point, constraint, None);
|
||||||
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None);
|
let snapped_far = snap.constrained_snap(&snap_data, &far_point, constraint, None);
|
||||||
document_pos = if snapped_far.other_snap_better(&snapped) {
|
document_pos = if snapped_far.other_snap_better(&snapped) {
|
||||||
|
|
@ -523,7 +521,7 @@ impl PenToolData {
|
||||||
document_pos = snapped.snapped_point_document;
|
document_pos = snapped.snapped_point_document;
|
||||||
snap.update_indicator(snapped);
|
snap.update_indicator(snapped);
|
||||||
}
|
}
|
||||||
} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| mirror) {
|
} else if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| colinear) {
|
||||||
let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()), None, false);
|
let snapped = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(document_pos, neighbors.clone()), None, false);
|
||||||
let snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors), None, false);
|
let snapped_far = snap.free_snap(&snap_data, &SnapCandidatePoint::handle_neighbors(2. * relative - document_pos, neighbors), None, false);
|
||||||
document_pos = if snapped_far.other_snap_better(&snapped) {
|
document_pos = if snapped_far.other_snap_better(&snapped) {
|
||||||
|
|
@ -641,8 +639,8 @@ impl Fsm for PenToolFsmState {
|
||||||
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
// Disable this tool's mirroring
|
// Prevent the initial point from having a colinear in handle while dragging the out handle
|
||||||
tool_data.should_mirror = false;
|
tool_data.colinear_handles = false;
|
||||||
|
|
||||||
// Perform extension of an existing path
|
// Perform extension of an existing path
|
||||||
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||||
|
|
@ -663,11 +661,11 @@ impl Fsm for PenToolFsmState {
|
||||||
}
|
}
|
||||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
tool_data.check_break(document, transform, responses);
|
tool_data.bend_from_previous_point(document, transform, responses);
|
||||||
PenToolFsmState::DraggingHandle
|
PenToolFsmState::DraggingHandle
|
||||||
}
|
}
|
||||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||||
tool_data.should_mirror = true;
|
tool_data.colinear_handles = true;
|
||||||
tool_data.finish_placing_handle(document, transform, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
|
tool_data.finish_placing_handle(document, transform, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||||
}
|
}
|
||||||
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||||
|
|
@ -677,6 +675,7 @@ impl Fsm for PenToolFsmState {
|
||||||
break_handle: input.keyboard.key(break_handle),
|
break_handle: input.keyboard.key(break_handle),
|
||||||
};
|
};
|
||||||
let snap_data = SnapData::new(document, input);
|
let snap_data = SnapData::new(document, input);
|
||||||
|
|
||||||
let state = tool_data
|
let state = tool_data
|
||||||
.drag_handle(snap_data, transform, input.mouse.position, modifiers, responses)
|
.drag_handle(snap_data, transform, input.mouse.position, modifiers, responses)
|
||||||
.unwrap_or(PenToolFsmState::Ready);
|
.unwrap_or(PenToolFsmState::Ready);
|
||||||
|
|
@ -772,6 +771,10 @@ impl Fsm for PenToolFsmState {
|
||||||
]),
|
]),
|
||||||
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
|
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
|
||||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Sharp Point"), HintInfo::mouse(MouseMotion::LmbDrag, "Add Smooth Point")]),
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Add Sharp Point"), HintInfo::mouse(MouseMotion::LmbDrag, "Add Smooth Point")]),
|
||||||
|
HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::Lmb, ""),
|
||||||
|
HintInfo::mouse(MouseMotion::LmbDrag, "Bend from Prev. Point").prepend_slash(),
|
||||||
|
]),
|
||||||
]),
|
]),
|
||||||
PenToolFsmState::DraggingHandle => HintData(vec![
|
PenToolFsmState::DraggingHandle => HintData(vec![
|
||||||
HintGroup(vec![
|
HintGroup(vec![
|
||||||
|
|
@ -780,6 +783,7 @@ impl Fsm for PenToolFsmState {
|
||||||
HintInfo::keys([Key::Enter], "End Path").prepend_slash(),
|
HintInfo::keys([Key::Enter], "End Path").prepend_slash(),
|
||||||
]),
|
]),
|
||||||
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
|
HintGroup(vec![HintInfo::keys([Key::Shift], "Snap 15°"), HintInfo::keys([Key::Control], "Lock Angle")]),
|
||||||
|
// TODO: Only show this if the handle being dragged is colinear, so don't show this when bending from the previous point (by clicking and dragging from the previously placed anchor)
|
||||||
HintGroup(vec![HintInfo::keys([Key::Alt], "Bend Handle")]),
|
HintGroup(vec![HintInfo::keys([Key::Alt], "Bend Handle")]),
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ fn update_spline(tool_data: &SplineToolData, show_preview: bool, responses: &mut
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
graph_modification_utils::set_manipulator_mirror_angle(subpath.manipulator_groups(), layer, true, responses);
|
graph_modification_utils::set_manipulator_colinear_handles_state(subpath.manipulator_groups(), layer, true, responses);
|
||||||
let subpaths = vec![subpath];
|
let subpaths = vec![subpath];
|
||||||
let modification = VectorDataModification::UpdateSubpaths { subpaths };
|
let modification = VectorDataModification::UpdateSubpaths { subpaths };
|
||||||
responses.add_front(GraphOperationMessage::Vector { layer, modification });
|
responses.add_front(GraphOperationMessage::Vector { layer, modification });
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
<path class="bright" d="M6,1C4.3,1,3,2.3,3,4v4h5V1H6z M7,6v1H4c0,0,0-0.6,0.2-1c0.3-0.6,0.8-1.1,1.3-1.4c1.1-0.9,0.8-1.9,0.1-1.7C5,3,4.8,3.6,4.9,4.1H4C3.9,2.9,4.7,2,5.8,2c1.1,0,1.5,1.2,1,2.3C6.4,5.1,5.5,5.5,5.4,6H7z" />
|
<path class="bright" d="M4.5,3.8c-0.2-0.7,0-1.4,1-1.6C6.7,2,7.2,3.4,5.4,4.6c-0.8,0.5-1.7,1.2-2.1,2C3,7.2,3,8,3,8h5V6C7.5,6.6,5.4,6.6,5.4,6.6C5.5,5.8,7,5.4,7.6,4.3C8.5,2.7,7.7,1,6,1C4.2,1,2.5,2,3.2,4.6L4.5,3.8z" />
|
||||||
<path class="dim" d="M10,1H9v1h1c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V9H3v1c0,2.76,2.24,5,5,5s5-2.24,5-5V4C13,2.35,11.65,1,10,1z" />
|
<path class="dim" d="M10,1H9v1h1c1.1,0,2,0.9,2,2v6c0,2.21-1.79,4-4,4s-4-1.79-4-4V9H3v1c0,2.76,2.24,5,5,5s5-2.24,5-5V4C13,2.35,11.65,1,10,1z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 428 B |
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
<path class="bright" d="M10,1H8v7h5V4C13,2.3,11.7,1,10,1z M12,7H9.1c0,0,0-0.6,0.2-1c0.2-0.6,0.8-1.1,1.3-1.5c0.9-0.8,0.4-1.8-0.4-1.6C9.6,3.1,9.9,4.1,9.9,4.1H9.1c-0.3-1,0.1-2.2,1.3-2.1c1,0.1,1.6,1.1,1.4,2.1C11.6,5,10.6,5.4,10.5,6H12V7z" />
|
<path class="bright" d="M10.4,6.6c0.1-0.8,2.1-1.2,2.4-2.4C13.3,2.8,12,1,10.3,1c-2.2,0-2.8,2.2-2,3.8l1.1-0.9c0,0-0.6-1.3,0.5-1.7c1-0.4,2.5,1.2,0.7,2.2C9.9,5,8.7,5.7,8.3,6.6C8,7.2,8,8,8,8h5V6C12.5,6.6,10.4,6.6,10.4,6.6z" />
|
||||||
<path class="dim" d="M6,1h1v1H6C4.9,2,4,2.9,4,4v6c0,2.21,1.79,4,4,4s4-1.79,4-4V9h1v1c0,2.76-2.24,5-5,5s-5-2.24-5-5V4C3,2.35,4.35,1,6,1z" />
|
<path class="dim" d="M6,1h1v1H6C4.9,2,4,2.9,4,4v6c0,2.21,1.79,4,4,4s4-1.79,4-4V9h1v1c0,2.76-2.24,5-5,5s-5-2.24-5-5V4C3,2.35,4.35,1,6,1z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 432 B |
|
|
@ -231,6 +231,7 @@ import MouseHintLmb from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hin
|
||||||
import MouseHintMmbDrag from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-mmb-drag.svg";
|
import MouseHintMmbDrag from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-mmb-drag.svg";
|
||||||
import MouseHintMmb from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-mmb.svg";
|
import MouseHintMmb from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-mmb.svg";
|
||||||
import MouseHintNone from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-none.svg";
|
import MouseHintNone from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-none.svg";
|
||||||
|
import MouseHintRmbDouble from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-rmb-double.svg";
|
||||||
import MouseHintRmbDrag from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-rmb-drag.svg";
|
import MouseHintRmbDrag from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-rmb-drag.svg";
|
||||||
import MouseHintRmb from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-rmb.svg";
|
import MouseHintRmb from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-rmb.svg";
|
||||||
import MouseHintScrollDown from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-scroll-down.svg";
|
import MouseHintScrollDown from "@graphite-frontend/assets/icon-16px-two-tone/mouse-hint-scroll-down.svg";
|
||||||
|
|
@ -245,6 +246,7 @@ const TWO_TONE_16PX = {
|
||||||
MouseHintMmbDrag: { svg: MouseHintMmbDrag, size: 16 },
|
MouseHintMmbDrag: { svg: MouseHintMmbDrag, size: 16 },
|
||||||
MouseHintNone: { svg: MouseHintNone, size: 16 },
|
MouseHintNone: { svg: MouseHintNone, size: 16 },
|
||||||
MouseHintRmb: { svg: MouseHintRmb, size: 16 },
|
MouseHintRmb: { svg: MouseHintRmb, size: 16 },
|
||||||
|
MouseHintRmbDouble: { svg: MouseHintRmbDouble, size: 16 },
|
||||||
MouseHintRmbDrag: { svg: MouseHintRmbDrag, size: 16 },
|
MouseHintRmbDrag: { svg: MouseHintRmbDrag, size: 16 },
|
||||||
MouseHintScrollDown: { svg: MouseHintScrollDown, size: 16 },
|
MouseHintScrollDown: { svg: MouseHintScrollDown, size: 16 },
|
||||||
MouseHintScrollUp: { svg: MouseHintScrollUp, size: 16 },
|
MouseHintScrollUp: { svg: MouseHintScrollUp, size: 16 },
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_at_exisiting_manipulator_group_of_open_subpath() {
|
fn insert_at_existing_manipulator_group_of_open_subpath() {
|
||||||
// This will do nothing to the subpath
|
// This will do nothing to the subpath
|
||||||
let mut subpath = set_up_open_subpath();
|
let mut subpath = set_up_open_subpath();
|
||||||
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.75));
|
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.75));
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,14 @@ fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
|
||||||
|
|
||||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PathGenerator<Mirror> {
|
pub struct PathGenerator<ColinearManipulators> {
|
||||||
mirror: Mirror,
|
colinear_manipulators: ColinearManipulators,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(PathGenerator)]
|
#[node_macro::node_fn(PathGenerator)]
|
||||||
fn generate_path(path_data: Vec<Subpath<ManipulatorGroupId>>, mirror: Vec<ManipulatorGroupId>) -> super::VectorData {
|
fn generate_path(path_data: Vec<Subpath<ManipulatorGroupId>>, colinear_manipulators: Vec<ManipulatorGroupId>) -> super::VectorData {
|
||||||
let mut vector_data = super::VectorData::from_subpaths(path_data);
|
let mut vector_data = super::VectorData::from_subpaths(path_data);
|
||||||
vector_data.mirror_angle = mirror;
|
vector_data.colinear_manipulators = colinear_manipulators;
|
||||||
vector_data
|
vector_data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ pub struct VectorData {
|
||||||
pub transform: DAffine2,
|
pub transform: DAffine2,
|
||||||
pub style: PathStyle,
|
pub style: PathStyle,
|
||||||
pub alpha_blending: AlphaBlending,
|
pub alpha_blending: AlphaBlending,
|
||||||
/// A list of all manipulator groups (referenced in `subpaths`) that have smooth handles (where their handles are colinear, or locked to 180° angles from one another)
|
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
|
||||||
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have subpath and mirror angle inputs"` to find it).
|
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
|
||||||
pub mirror_angle: Vec<ManipulatorGroupId>,
|
pub colinear_manipulators: Vec<ManipulatorGroupId>,
|
||||||
|
|
||||||
pub point_domain: PointDomain,
|
pub point_domain: PointDomain,
|
||||||
pub segment_domain: SegmentDomain,
|
pub segment_domain: SegmentDomain,
|
||||||
|
|
@ -35,7 +35,7 @@ impl core::hash::Hash for VectorData {
|
||||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||||
self.style.hash(state);
|
self.style.hash(state);
|
||||||
self.alpha_blending.hash(state);
|
self.alpha_blending.hash(state);
|
||||||
self.mirror_angle.hash(state);
|
self.colinear_manipulators.hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ impl VectorData {
|
||||||
transform: DAffine2::IDENTITY,
|
transform: DAffine2::IDENTITY,
|
||||||
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
||||||
alpha_blending: AlphaBlending::new(),
|
alpha_blending: AlphaBlending::new(),
|
||||||
mirror_angle: Vec::new(),
|
colinear_manipulators: Vec::new(),
|
||||||
point_domain: PointDomain::new(),
|
point_domain: PointDomain::new(),
|
||||||
segment_domain: SegmentDomain::new(),
|
segment_domain: SegmentDomain::new(),
|
||||||
region_domain: RegionDomain::new(),
|
region_domain: RegionDomain::new(),
|
||||||
|
|
|
||||||
|
|
@ -364,7 +364,7 @@ impl crate::vector::ConcatElement for super::VectorData {
|
||||||
self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map);
|
self.region_domain.concat(&other.region_domain, transform * other.transform, &id_map);
|
||||||
// TODO: properly deal with fills such as gradients
|
// TODO: properly deal with fills such as gradients
|
||||||
self.style = other.style.clone();
|
self.style = other.style.clone();
|
||||||
self.mirror_angle.extend(other.mirror_angle.iter().copied());
|
self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied());
|
||||||
self.alpha_blending = other.alpha_blending;
|
self.alpha_blending = other.alpha_blending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue