Fix the Path tool's smooth/sharp buttons (#1439)
* Fix select tool smooth button * Nit * Fix behavior when zero points are selected but the shape is active --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
c4bea2b400
commit
54745e210a
|
|
@ -343,7 +343,7 @@ impl OverlayRenderer {
|
||||||
let deselected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE));
|
let deselected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE));
|
||||||
let selected_shape_state = state.get(&layer);
|
let selected_shape_state = state.get(&layer);
|
||||||
// Update if the manipulator points are shown as selected
|
// Update if the manipulator points are shown as selected
|
||||||
// Here the index is important, even though overlays[..] has five elements we only care about the first three
|
// Here the index is important, even though overlays has five elements we only care about the first three
|
||||||
for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() {
|
for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() {
|
||||||
let selected_type = [SelectedType::InHandle, SelectedType::OutHandle, SelectedType::Anchor][index];
|
let selected_type = [SelectedType::InHandle, SelectedType::OutHandle, SelectedType::Anchor][index];
|
||||||
if let Some(overlay_path) = overlay {
|
if let Some(overlay_path) = overlay {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::graph_modification_utils;
|
||||||
use crate::consts::DRAG_THRESHOLD;
|
use crate::consts::DRAG_THRESHOLD;
|
||||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
@ -167,17 +168,16 @@ impl ShapeState {
|
||||||
|
|
||||||
/// Moves a control point to a `new_position` in document space.
|
/// Moves a control point to a `new_position` in document space.
|
||||||
/// Returns `Some(())` if successful and `None` otherwise.
|
/// Returns `Some(())` if successful and `None` otherwise.
|
||||||
pub fn reposition_control_point(&self, point: &ManipulatorPointId, responses: &mut VecDeque<Message>, document: &Document, new_position: DVec2, layer_path: &[u64]) -> Option<()> {
|
pub fn reposition_control_point(&self, point: &ManipulatorPointId, responses: &mut VecDeque<Message>, document: &Document, new_position: DVec2, layer: LayerNodeIdentifier) -> Option<()> {
|
||||||
let layer = document.layer(layer_path).ok()?;
|
let subpaths = get_subpaths(layer, document)?;
|
||||||
let vector_data = layer.as_vector_data()?;
|
let transform = document.metadata.transform_to_viewport(layer).inverse();
|
||||||
let transform = layer.transform.inverse();
|
let position = transform.transform_point2(new_position);
|
||||||
let position = transform.transform_point2(new_position - layer.pivot);
|
let group = graph_modification_utils::get_manipulator_from_id(subpaths, point.group)?;
|
||||||
let group = vector_data.manipulator_from_id(point.group)?;
|
|
||||||
let delta = position - point.manipulator_type.get_position(group)?;
|
let delta = position - point.manipulator_type.get_position(group)?;
|
||||||
|
|
||||||
if point.manipulator_type.is_handle() {
|
if point.manipulator_type.is_handle() {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer: layer_path.to_vec(),
|
layer: layer.to_path(),
|
||||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: false },
|
modification: VectorDataModification::SetManipulatorHandleMirroring { id: group.id, mirror_angle: false },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +187,7 @@ impl ShapeState {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer: layer_path.to_vec(),
|
layer: layer.to_path(),
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: (position + delta) },
|
modification: VectorDataModification::SetManipulatorPosition { point, position: (position + delta) },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -208,12 +208,8 @@ impl ShapeState {
|
||||||
let mut point_smoothness_status = self
|
let mut point_smoothness_status = self
|
||||||
.selected_shape_state
|
.selected_shape_state
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(layer_id, selection_state)| {
|
.filter_map(|(&layer, selection_state)| Some((graph_modification_utils::get_mirror_handles(layer, document)?, selection_state)))
|
||||||
let layer = document.layer(&layer_id.to_path()).ok()?;
|
.flat_map(|(mirror, selection_state)| selection_state.selected_points.iter().map(|selected_point| mirror.contains(&selected_point.group)));
|
||||||
let vector_data = layer.as_vector_data()?;
|
|
||||||
Some((vector_data, selection_state))
|
|
||||||
})
|
|
||||||
.flat_map(|(vector_data, selection_state)| selection_state.selected_points.iter().map(|selected_point| vector_data.mirror_angle.contains(&selected_point.group)));
|
|
||||||
|
|
||||||
let Some(first_is_smooth) = point_smoothness_status.next() else { return ManipulatorAngle::Mixed };
|
let Some(first_is_smooth) = point_smoothness_status.next() else { return ManipulatorAngle::Mixed };
|
||||||
|
|
||||||
|
|
@ -226,7 +222,7 @@ impl ShapeState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn smooth_manipulator_group(&self, subpath: &bezier_rs::Subpath<ManipulatorGroupId>, index: usize, responses: &mut VecDeque<Message>, layer: &LayerNodeIdentifier) {
|
pub fn smooth_manipulator_group(&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];
|
||||||
|
|
||||||
|
|
@ -298,9 +294,8 @@ impl ShapeState {
|
||||||
pub fn smooth_selected_groups(&self, responses: &mut VecDeque<Message>, document: &Document) -> Option<()> {
|
pub fn smooth_selected_groups(&self, responses: &mut VecDeque<Message>, document: &Document) -> Option<()> {
|
||||||
let mut skip_set = HashSet::new();
|
let mut skip_set = HashSet::new();
|
||||||
|
|
||||||
for (layer_id, layer_state) in self.selected_shape_state.iter() {
|
for (&layer, layer_state) in self.selected_shape_state.iter() {
|
||||||
let layer = document.layer(&layer_id.to_path()).ok()?;
|
let subpaths = get_subpaths(layer, document)?;
|
||||||
let vector_data = layer.as_vector_data()?;
|
|
||||||
|
|
||||||
for point in layer_state.selected_points.iter() {
|
for point in layer_state.selected_points.iter() {
|
||||||
if skip_set.contains(&point.group) {
|
if skip_set.contains(&point.group) {
|
||||||
|
|
@ -321,14 +316,14 @@ impl ShapeState {
|
||||||
group: point.group,
|
group: point.group,
|
||||||
manipulator_type: SelectedType::InHandle,
|
manipulator_type: SelectedType::InHandle,
|
||||||
});
|
});
|
||||||
let group = vector_data.manipulator_from_id(point.group)?;
|
let group = graph_modification_utils::get_manipulator_from_id(subpaths, point.group)?;
|
||||||
|
|
||||||
match (anchor_selected, out_selected, in_selected) {
|
match (anchor_selected, out_selected, in_selected) {
|
||||||
(_, true, false) => {
|
(_, 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 {
|
||||||
layer: layer_id.to_path(),
|
layer: layer.to_path(),
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point: out_handle, position },
|
modification: VectorDataModification::SetManipulatorPosition { point: out_handle, position },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -337,13 +332,13 @@ impl ShapeState {
|
||||||
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 {
|
||||||
layer: layer_id.to_path(),
|
layer: layer.to_path(),
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point: in_handle, position },
|
modification: VectorDataModification::SetManipulatorPosition { point: in_handle, position },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_, _, _) => {
|
(_, _, _) => {
|
||||||
let found = vector_data.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)?;
|
||||||
// TODO: try subpath closed? wrapping
|
// TODO: try subpath closed? wrapping
|
||||||
|
|
@ -351,7 +346,7 @@ impl ShapeState {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((subpath, index)) = found {
|
if let Some((subpath, index)) = found {
|
||||||
self.smooth_manipulator_group(subpath, index, responses, layer_id);
|
self.smooth_manipulator_group(subpath, index, responses, layer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -764,7 +759,7 @@ impl ShapeState {
|
||||||
};
|
};
|
||||||
|
|
||||||
if already_sharp {
|
if already_sharp {
|
||||||
self.smooth_manipulator_group(subpath, index, responses, &layer);
|
self.smooth_manipulator_group(subpath, index, responses, layer);
|
||||||
} else {
|
} else {
|
||||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
|
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ use std::vec;
|
||||||
|
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
|
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_manipulator_from_id, get_mirror_handles, get_subpaths};
|
||||||
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::{add_bounding_box, remove_bounding_box, update_bounding_box};
|
use crate::messages::tool::common_functionality::transformation_cage::{add_bounding_box, remove_bounding_box, update_bounding_box};
|
||||||
|
|
||||||
use document_legacy::document::Document;
|
use document_legacy::document::Document;
|
||||||
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::LayerId;
|
use document_legacy::LayerId;
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||||
|
|
||||||
|
|
@ -204,6 +206,8 @@ struct PathToolData {
|
||||||
alt_debounce: bool,
|
alt_debounce: bool,
|
||||||
opposing_handle_lengths: Option<OpposingHandleLengths>,
|
opposing_handle_lengths: Option<OpposingHandleLengths>,
|
||||||
drag_box_overlay_layer: Option<Vec<LayerId>>,
|
drag_box_overlay_layer: Option<Vec<LayerId>>,
|
||||||
|
/// Describes information about the selected point(s), if any, across one or multiple shapes and manipulator point types (anchor or handle).
|
||||||
|
/// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected.
|
||||||
selection_status: SelectionStatus,
|
selection_status: SelectionStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -469,14 +473,14 @@ impl Fsm for PathToolFsmState {
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
||||||
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
|
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
||||||
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer_path);
|
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(new_x, coordinates.y), layer);
|
||||||
}
|
}
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
|
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
|
||||||
if let Some(&SingleSelectedPoint { coordinates, id, ref layer_path, .. }) = tool_data.selection_status.as_one() {
|
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
||||||
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer_path);
|
shape_editor.reposition_control_point(&id, responses, &document.document_legacy, DVec2::new(coordinates.x, new_y), layer);
|
||||||
}
|
}
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
@ -538,6 +542,10 @@ enum SelectionStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionStatus {
|
impl SelectionStatus {
|
||||||
|
fn is_none(&self) -> bool {
|
||||||
|
self == &SelectionStatus::None
|
||||||
|
}
|
||||||
|
|
||||||
fn as_one(&self) -> Option<&SingleSelectedPoint> {
|
fn as_one(&self) -> Option<&SingleSelectedPoint> {
|
||||||
match self {
|
match self {
|
||||||
SelectionStatus::One(one) => Some(one),
|
SelectionStatus::One(one) => Some(one),
|
||||||
|
|
@ -551,10 +559,6 @@ impl SelectionStatus {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_none(&self) -> bool {
|
|
||||||
self == &SelectionStatus::None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
|
@ -566,44 +570,51 @@ struct MultipleSelectedPoints {
|
||||||
struct SingleSelectedPoint {
|
struct SingleSelectedPoint {
|
||||||
coordinates: DVec2,
|
coordinates: DVec2,
|
||||||
id: ManipulatorPointId,
|
id: ManipulatorPointId,
|
||||||
layer_path: Vec<u64>,
|
layer: LayerNodeIdentifier,
|
||||||
manipulator_angle: ManipulatorAngle,
|
manipulator_angle: ManipulatorAngle,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is one selected and only one manipulator group this yields the selected control point,
|
/// Sets the cumulative description of the selected points: if `None` are selected, if `One` is selected, or if `Multiple` are selected.
|
||||||
// if only one handle is selected it will yield that handle, otherwise it will yield the group's anchor.
|
/// Applies to any selected points, whether they are anchors or handles; and whether they are from a single shape or across multiple shapes.
|
||||||
fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> SelectionStatus {
|
fn get_selection_status(document: &Document, shape_state: &mut ShapeState) -> SelectionStatus {
|
||||||
// Check to see if only one manipulator group is selected
|
let mut selection_layers = shape_state.selected_shape_state.iter().map(|(k, v)| (*k, v.selected_points_count()));
|
||||||
let selection_layers: Vec<_> = shape_state.selected_shape_state.iter().take(2).map(|(k, v)| (k, v.selected_points_count())).collect();
|
let total_selected_points = selection_layers.clone().map(|(_, v)| v).sum::<usize>();
|
||||||
if let [(layer, 1)] = selection_layers[..] {
|
|
||||||
let Some(layer_data) = document.layer(&layer.to_path()).ok() else { return SelectionStatus::None };
|
// Check to see if only one manipulator group in a single shape is selected
|
||||||
let Some(vector_data) = layer_data.as_vector_data() else { return SelectionStatus::None };
|
if total_selected_points == 1 {
|
||||||
|
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
|
||||||
|
return SelectionStatus::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(subpaths) = get_subpaths(layer, document) else {
|
||||||
|
return SelectionStatus::None;
|
||||||
|
};
|
||||||
|
let Some(mirror) = get_mirror_handles(layer, document) else {
|
||||||
|
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(group) = vector_data.manipulator_from_id(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(group) else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let manipulator_angle = if vector_data.mirror_angle.contains(&point.group) {
|
let manipulator_angle = if mirror.contains(&point.group) { ManipulatorAngle::Smooth } else { ManipulatorAngle::Sharp };
|
||||||
ManipulatorAngle::Smooth
|
|
||||||
} else {
|
|
||||||
ManipulatorAngle::Sharp
|
|
||||||
};
|
|
||||||
|
|
||||||
return SelectionStatus::One(SingleSelectedPoint {
|
return SelectionStatus::One(SingleSelectedPoint {
|
||||||
coordinates: layer_data.transform.transform_point2(local_position) + layer_data.pivot,
|
coordinates: document.metadata.transform_to_document(layer).transform_point2(local_position),
|
||||||
layer_path: layer.to_path(),
|
layer,
|
||||||
id: *point,
|
id: *point,
|
||||||
manipulator_angle,
|
manipulator_angle,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if !selection_layers.is_empty() {
|
// Check to see if multiple manipulator groups are selected
|
||||||
|
if total_selected_points > 1 {
|
||||||
return SelectionStatus::Multiple(MultipleSelectedPoints {
|
return SelectionStatus::Multiple(MultipleSelectedPoints {
|
||||||
manipulator_angle: shape_state.selected_manipulator_angles(document),
|
manipulator_angle: shape_state.selected_manipulator_angles(document),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ impl DocumentNode {
|
||||||
NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)),
|
NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
|
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "received non resolved parameter");
|
||||||
assert!(
|
assert!(
|
||||||
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
|
||||||
"received value as parameter. inputs: {:#?}, construction_args: {:#?}",
|
"received value as parameter. inputs: {:#?}, construction_args: {:#?}",
|
||||||
|
|
@ -115,7 +115,7 @@ impl DocumentNode {
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we have one parameter of the type inline, set it as the construction args
|
// If we have one parameter of the type inline, set it as the construction args
|
||||||
if let &[NodeInput::Inline(ref inline)] = &self.inputs[..] {
|
if let &[NodeInput::Inline(ref inline)] = self.inputs.as_slice() {
|
||||||
args = ConstructionArgs::Inline(inline.clone());
|
args = ConstructionArgs::Inline(inline.clone());
|
||||||
}
|
}
|
||||||
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue