mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 06:58:11 +08:00
Add nondestructive vector editing (#1676)
* Initial vector modify node * Initial extraction of data from monitor nodes * Migrate to point id * Start converting to modify node * Non destructive spline tool (tout le reste est cassé) * Fix unconnected modify node * Fix freehand tool * Pen tool * Migrate demo art * Select points * Fix the demo artwork * Fix the X and Y inputs for path tool * G1 continous toggle * Delete points * Fix test * Insert point * Improve robustness of handles * Fix GRS shortcuts on path * Dragging points * Fix build * Preserve opposing handle lengths * Update demo art and snapping * Fix polygon tool * Double click end anchor * Improve dragging * Fix text shifting * Select only connected verts * Colinear alt * Cleanup * Fix imports * Improve pen tool avoiding handle placement * Improve disolve * Remove pivot widget from Transform node properties * Fix demo art * Fix bugs * Re-save demo artwork * Code review * Serialize hashmap as tuple vec to enable deserialize_inputs * Fix migrate * Add document upgrade function to editor_api.rs * Finalize document upgrading * Rename to the Path node * Remove smoothing from Freehand tool * Upgrade demo artwork * Propertly disable raw-rs tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Adam <adamgerhant@gmail.com> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
co-authored by
Keavon Chambers
Adam
Dennis Kobert
parent
fd3613018a
commit
1652c713a6
@@ -1236,32 +1236,27 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
/// Find any layers sorted by index that are under the given location in viewport space.
|
||||
pub fn click_list_any(&self, viewport_location: DVec2, network: &NodeNetwork) -> Vec<LayerNodeIdentifier> {
|
||||
self.click_xray(viewport_location).filter(|&layer| !is_artboard(layer, network)).collect::<Vec<_>>()
|
||||
pub fn click_xray_no_artboards<'a>(&'a self, viewport_location: DVec2, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
self.click_xray(viewport_location).filter(move |&layer| !is_artboard(layer, network))
|
||||
}
|
||||
|
||||
/// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.
|
||||
pub fn click_list(&self, viewport_location: DVec2, network: &NodeNetwork) -> Vec<LayerNodeIdentifier> {
|
||||
let mut node_list = self.click_list_any(viewport_location, network);
|
||||
node_list.truncate(
|
||||
node_list
|
||||
.iter()
|
||||
.position(|&layer| {
|
||||
if layer != LayerNodeIdentifier::ROOT_PARENT {
|
||||
!network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default()
|
||||
} else {
|
||||
log::error!("ROOT_PARENT should not exist in click_list_any");
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(0) + 1,
|
||||
);
|
||||
node_list
|
||||
pub fn click_list<'a>(&'a self, viewport_location: DVec2, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
self.click_xray_no_artboards(viewport_location, network)
|
||||
.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)
|
||||
.scan(true, |last_had_children, layer| {
|
||||
if *last_had_children {
|
||||
*last_had_children = network.nodes.get(&layer.to_node()).map_or(false, |node| node.layer_has_child_layers(network));
|
||||
Some(layer)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Find the deepest layer that has been clicked on from a location in viewport space.
|
||||
pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
|
||||
self.click_list(viewport_location, network).last().copied()
|
||||
self.click_list(viewport_location, network).last()
|
||||
}
|
||||
|
||||
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::utility_types::TransformIn;
|
||||
use super::utility_types::VectorDataModification;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
@@ -7,9 +6,10 @@ use bezier_rs::Subpath;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::vector::PointId;
|
||||
use graphene_core::vector::VectorModificationType;
|
||||
use graphene_core::{Artboard, Color};
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
|
||||
@@ -77,11 +77,6 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerNodeIdentifier,
|
||||
blend_mode: BlendMode,
|
||||
},
|
||||
UpdateBounds {
|
||||
layer: LayerNodeIdentifier,
|
||||
old_bounds: [DVec2; 2],
|
||||
new_bounds: [DVec2; 2],
|
||||
},
|
||||
StrokeSet {
|
||||
layer: LayerNodeIdentifier,
|
||||
stroke: Stroke,
|
||||
@@ -104,7 +99,7 @@ pub enum GraphOperationMessage {
|
||||
},
|
||||
Vector {
|
||||
layer: LayerNodeIdentifier,
|
||||
modification: VectorDataModification,
|
||||
modification_type: VectorModificationType,
|
||||
},
|
||||
Brush {
|
||||
layer: LayerNodeIdentifier,
|
||||
@@ -129,7 +124,7 @@ pub enum GraphOperationMessage {
|
||||
},
|
||||
NewVectorLayer {
|
||||
id: NodeId,
|
||||
subpaths: Vec<Subpath<ManipulatorGroupId>>,
|
||||
subpaths: Vec<Subpath<PointId>>,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: isize,
|
||||
},
|
||||
|
||||
+9
-26
@@ -1,4 +1,4 @@
|
||||
use super::transform_utils::{self, LayerBounds};
|
||||
use super::transform_utils;
|
||||
use super::utility_types::ModifyInputsContext;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
@@ -9,10 +9,9 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, Previewing};
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::vector::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
|
||||
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, LineCap, LineJoin, Stroke};
|
||||
use graphene_core::Color;
|
||||
use graphene_std::vector::convert_usvg_path;
|
||||
use graphene_std::vector::style::GradientStops;
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
|
||||
@@ -404,15 +403,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
modify_inputs.blend_mode_set(blend_mode);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run UpdateBounds on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
modify_inputs.update_bounds(old_bounds, new_bounds);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::StrokeSet { layer, stroke } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run StrokeSet on ROOT_PARENT");
|
||||
@@ -433,9 +423,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
return;
|
||||
}
|
||||
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
||||
let bounds = LayerBounds::new(document_metadata, layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
|
||||
modify_inputs.transform_change(transform, transform_in, parent_transform, skip_rerender);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::TransformSet {
|
||||
@@ -451,9 +440,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
||||
|
||||
let current_transform = Some(document_metadata.transform_to_viewport(layer));
|
||||
let bounds = LayerBounds::new(document_metadata, layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
|
||||
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, skip_rerender);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
|
||||
@@ -461,21 +449,17 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
let bounds = LayerBounds::new(document_metadata, layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
modify_inputs.pivot_set(pivot, bounds);
|
||||
modify_inputs.pivot_set(pivot);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::Vector { layer, modification } => {
|
||||
GraphOperationMessage::Vector { layer, modification_type } => {
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Cannot run Vector on ROOT_PARENT");
|
||||
return;
|
||||
}
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||
let previous_layer = modify_inputs.vector_modify(modification);
|
||||
if let Some(layer) = previous_layer {
|
||||
responses.add(GraphOperationMessage::DeleteLayer { layer, reconnect: true })
|
||||
}
|
||||
modify_inputs.vector_modify(modification_type);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::Brush { layer, strokes } => {
|
||||
@@ -698,6 +682,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
GraphOperationMessage::SetName { layer, name } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::SetNameImpl { layer, name });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
GraphOperationMessage::SetNameImpl { layer, name } => {
|
||||
if let Some(node) = document_network.nodes.get_mut(&layer.to_node()) {
|
||||
@@ -829,10 +814,8 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
|
||||
.unwrap_or_default();
|
||||
modify_inputs.insert_vector_data(subpaths, layer);
|
||||
|
||||
let center = DAffine2::from_translation((bounds[0] + bounds[1]) / 2.);
|
||||
|
||||
modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
|
||||
transform_utils::update_transform(inputs, center.inverse() * transform * usvg_transform(node.abs_transform()) * center);
|
||||
transform_utils::update_transform(inputs, transform * usvg_transform(node.abs_transform()));
|
||||
});
|
||||
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::{value::TaggedValue, NodeInput};
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
use graphene_core::vector::PointId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
use super::utility_types::VectorDataModification;
|
||||
|
||||
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
||||
pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64, DVec2, DVec2) {
|
||||
let x_axis = transform.matrix2.x_axis;
|
||||
@@ -53,7 +48,10 @@ pub struct LayerBounds {
|
||||
|
||||
impl LayerBounds {
|
||||
/// Extract the layer bounds and their transform for a layer.
|
||||
pub fn new(metadata: &DocumentMetadata, layer: LayerNodeIdentifier) -> Self {
|
||||
pub fn new(
|
||||
metadata: &crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata,
|
||||
layer: crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier,
|
||||
) -> Self {
|
||||
Self {
|
||||
bounds: metadata.nonzero_bounding_box(layer),
|
||||
bounds_transform: DAffine2::IDENTITY,
|
||||
@@ -184,7 +182,7 @@ fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
||||
[bounds_min, bounds_max]
|
||||
}
|
||||
/// Returns corners of all subpaths
|
||||
fn subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
||||
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box())
|
||||
@@ -193,116 +191,7 @@ fn subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
||||
}
|
||||
|
||||
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
||||
clamp_bounds(bounds_min, bounds_max)
|
||||
}
|
||||
|
||||
pub struct VectorModificationState<'a> {
|
||||
pub subpaths: &'a mut Vec<Subpath<ManipulatorGroupId>>,
|
||||
pub colinear_manipulators: &'a mut Vec<ManipulatorGroupId>,
|
||||
}
|
||||
impl<'a> VectorModificationState<'a> {
|
||||
fn insert_start(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||
self.subpaths[subpath_index].insert_manipulator_group(0, manipulator_group)
|
||||
}
|
||||
|
||||
fn insert_end(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||
let subpath = &mut self.subpaths[subpath_index];
|
||||
subpath.insert_manipulator_group(subpath.len(), manipulator_group)
|
||||
}
|
||||
|
||||
fn insert(&mut self, manipulator_group: ManipulatorGroup<ManipulatorGroupId>, after_id: ManipulatorGroupId) {
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if let Some(index) = subpath.manipulator_index_from_id(after_id) {
|
||||
subpath.insert_manipulator_group(index + 1, manipulator_group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_group(&mut self, id: ManipulatorGroupId) {
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if let Some(index) = subpath.manipulator_index_from_id(id) {
|
||||
subpath.remove_manipulator_group(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_point(&mut self, point: ManipulatorPointId) {
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if point.manipulator_type == SelectedType::Anchor {
|
||||
if let Some(index) = subpath.manipulator_index_from_id(point.group) {
|
||||
subpath.remove_manipulator_group(index);
|
||||
break;
|
||||
}
|
||||
} else if let Some(group) = subpath.manipulator_mut_from_id(point.group) {
|
||||
if point.manipulator_type == SelectedType::InHandle {
|
||||
group.in_handle = None;
|
||||
} else if point.manipulator_type == SelectedType::OutHandle {
|
||||
group.out_handle = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_manipulator_colinear_handles_state(&mut self, id: ManipulatorGroupId, colinear: bool) {
|
||||
if !colinear {
|
||||
self.colinear_manipulators.retain(|&manipulator_group_id| manipulator_group_id != id);
|
||||
} else if !self.colinear_manipulators.contains(&id) {
|
||||
self.colinear_manipulators.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_manipulator_colinear_handles_state(&mut self, id: ManipulatorGroupId) {
|
||||
if self.colinear_manipulators.contains(&id) {
|
||||
self.colinear_manipulators.retain(|&manipulator_group_id| manipulator_group_id != id);
|
||||
} else {
|
||||
self.colinear_manipulators.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_position(&mut self, point: ManipulatorPointId, position: DVec2) {
|
||||
assert!(position.is_finite(), "Point position should be finite");
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if let Some(manipulator) = subpath.manipulator_mut_from_id(point.group) {
|
||||
match point.manipulator_type {
|
||||
SelectedType::Anchor => manipulator.anchor = position,
|
||||
SelectedType::InHandle => manipulator.in_handle = Some(position),
|
||||
SelectedType::OutHandle => manipulator.out_handle = Some(position),
|
||||
}
|
||||
if point.manipulator_type != SelectedType::Anchor && self.colinear_manipulators.contains(&point.group) {
|
||||
let reflect = |opposite: DVec2| {
|
||||
(manipulator.anchor - position)
|
||||
.try_normalize()
|
||||
.map(|direction| direction * (opposite - manipulator.anchor).length() + manipulator.anchor)
|
||||
.unwrap_or(opposite)
|
||||
};
|
||||
match point.manipulator_type {
|
||||
SelectedType::InHandle => manipulator.out_handle = manipulator.out_handle.map(reflect),
|
||||
SelectedType::OutHandle => manipulator.in_handle = manipulator.in_handle.map(reflect),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modify(&mut self, modification: VectorDataModification) {
|
||||
match modification {
|
||||
VectorDataModification::AddEndManipulatorGroup { subpath_index, manipulator_group } => self.insert_end(subpath_index, manipulator_group),
|
||||
VectorDataModification::AddStartManipulatorGroup { subpath_index, manipulator_group } => self.insert_start(subpath_index, manipulator_group),
|
||||
VectorDataModification::AddManipulatorGroup { manipulator_group, after_id } => self.insert(manipulator_group, after_id),
|
||||
VectorDataModification::RemoveManipulatorGroup { id } => self.remove_group(id),
|
||||
VectorDataModification::RemoveManipulatorPoint { point } => self.remove_point(point),
|
||||
VectorDataModification::SetClosed { index, closed } => self.subpaths[index].set_closed(closed),
|
||||
VectorDataModification::SetManipulatorColinearHandlesState { id, colinear } => self.set_manipulator_colinear_handles_state(id, colinear),
|
||||
VectorDataModification::SetManipulatorPosition { point, position } => self.set_position(point, position),
|
||||
VectorDataModification::ToggleManipulatorColinearHandlesState { id } => self.toggle_manipulator_colinear_handles_state(id),
|
||||
VectorDataModification::UpdateSubpaths { subpaths } => *self.subpaths = subpaths,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::transform_utils::{self, LayerBounds};
|
||||
use super::transform_utils;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
@@ -10,12 +10,10 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, Previewing};
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::Type;
|
||||
use graphene_core::{Artboard, Color};
|
||||
use graphene_std::vector::ManipulatorPointId;
|
||||
use graphene_core::vector::{PointId, VectorModificationType};
|
||||
use graphene_core::{Artboard, Color, Type};
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
use interpreted_executor::node_registry::NODE_REGISTRY;
|
||||
|
||||
@@ -29,22 +27,6 @@ pub enum TransformIn {
|
||||
Viewport,
|
||||
}
|
||||
|
||||
type ManipulatorGroup = bezier_rs::ManipulatorGroup<ManipulatorGroupId>;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum VectorDataModification {
|
||||
AddEndManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
|
||||
AddManipulatorGroup { manipulator_group: ManipulatorGroup, after_id: ManipulatorGroupId },
|
||||
AddStartManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
|
||||
RemoveManipulatorGroup { id: ManipulatorGroupId },
|
||||
RemoveManipulatorPoint { point: ManipulatorPointId },
|
||||
SetClosed { index: usize, closed: bool },
|
||||
SetManipulatorColinearHandlesState { id: ManipulatorGroupId, colinear: bool },
|
||||
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
|
||||
ToggleManipulatorColinearHandlesState { id: ManipulatorGroupId },
|
||||
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> },
|
||||
}
|
||||
|
||||
// TODO: This is helpful to prevent passing the same arguments to multiple functions, but is currently inefficient due to the collect_outwards_wires. Move it into a function and use only when needed.
|
||||
/// NodeGraphMessage or GraphOperationMessage cannot be added in ModifyInputsContext, since the functions are called by both messages handlers
|
||||
pub struct ModifyInputsContext<'a> {
|
||||
@@ -315,7 +297,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
ModifyInputsContext::insert_node_as_primary_export(node_graph, document_network, new_id, artboard_node)
|
||||
}
|
||||
|
||||
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
|
||||
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: NodeId) {
|
||||
let shape = {
|
||||
let node_type: &crate::messages::portfolio::document::node_graph::document_node_types::DocumentNodeDefinition = resolve_document_node_type("Shape").expect("Shape node does not exist");
|
||||
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default())
|
||||
@@ -453,10 +435,9 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the inputs of a specific node
|
||||
pub fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||
let existing_node_id = self
|
||||
.document_network
|
||||
/// Find a node id as part of the layer
|
||||
fn existing_node_id(&mut self, name: &'static str) -> Option<NodeId> {
|
||||
self.document_network
|
||||
.upstream_flow_back_from_nodes(
|
||||
self.layer_node.map_or_else(
|
||||
|| {
|
||||
@@ -471,7 +452,20 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
graph_craft::document::FlowType::HorizontalFlow,
|
||||
)
|
||||
.find(|(node, _)| node.name == name)
|
||||
.map(|(_, id)| id);
|
||||
.map(|(_, id)| id)
|
||||
}
|
||||
|
||||
/// Changes the input of a specific node; skipping if it doesn't exist
|
||||
pub fn modify_existing_inputs(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||
let existing_node_id = self.existing_node_id(name);
|
||||
if let Some(node_id) = existing_node_id {
|
||||
self.modify_existing_node_inputs(node_id, update_input);
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes the inputs of a specific node; creating it if it doesn't exist
|
||||
pub fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||
let existing_node_id = self.existing_node_id(name);
|
||||
if let Some(node_id) = existing_node_id {
|
||||
self.modify_existing_node_inputs(node_id, update_input);
|
||||
} else {
|
||||
@@ -625,22 +619,20 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds, skip_rerender: bool) {
|
||||
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
|
||||
pub fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) {
|
||||
self.modify_inputs("Transform", skip_rerender, |inputs, _node_id, _metadata| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let upstream_transform = metadata.upstream_transform(node_id);
|
||||
let to = match transform_in {
|
||||
TransformIn::Local => DAffine2::IDENTITY,
|
||||
TransformIn::Scope { scope } => scope * parent_transform,
|
||||
TransformIn::Viewport => parent_transform,
|
||||
};
|
||||
let pivot = DAffine2::from_translation(upstream_transform.transform_point2(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs))));
|
||||
let transform = pivot.inverse() * to.inverse() * transform * to * pivot * layer_transform;
|
||||
let transform = to.inverse() * transform * to * layer_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option<DAffine2>, bounds: LayerBounds, skip_rerender: bool) {
|
||||
pub fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option<DAffine2>, skip_rerender: bool) {
|
||||
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
|
||||
let upstream_transform = metadata.upstream_transform(node_id);
|
||||
|
||||
@@ -649,7 +641,6 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
TransformIn::Scope { scope } => scope * parent_transform,
|
||||
TransformIn::Viewport => parent_transform,
|
||||
};
|
||||
let pivot = DAffine2::from_translation(upstream_transform.transform_point2(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs))));
|
||||
|
||||
if current_transform
|
||||
.filter(|transform| transform.matrix2.determinant() != 0. && upstream_transform.matrix2.determinant() != 0.)
|
||||
@@ -657,79 +648,29 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
{
|
||||
transform *= upstream_transform.inverse();
|
||||
}
|
||||
let final_transform = pivot.inverse() * to.inverse() * transform * pivot;
|
||||
let final_transform = to.inverse() * transform;
|
||||
transform_utils::update_transform(inputs, final_transform);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) {
|
||||
self.modify_inputs("Transform", false, |inputs, node_id, metadata| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let upstream_transform = metadata.upstream_transform(node_id);
|
||||
let old_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(transform_utils::get_current_normalized_pivot(inputs))));
|
||||
let new_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(new_pivot)));
|
||||
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
pub fn pivot_set(&mut self, new_pivot: DVec2) {
|
||||
self.modify_inputs("Transform", false, |inputs, _node_id, _metadata| {
|
||||
inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update_bounds(&mut self, [old_bounds_min, old_bounds_max]: [DVec2; 2], [new_bounds_min, new_bounds_max]: [DVec2; 2]) {
|
||||
self.modify_all_node_inputs("Transform", false, |inputs, node_id, metadata| {
|
||||
let upstream_transform = metadata.upstream_transform(node_id);
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let normalized_pivot = transform_utils::get_current_normalized_pivot(inputs);
|
||||
|
||||
let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalized_pivot + old_bounds_min;
|
||||
let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalized_pivot + new_bounds_min;
|
||||
let new_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(new_layerspace_pivot));
|
||||
let old_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(old_layerspace_pivot));
|
||||
|
||||
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn vector_modify(&mut self, modification: VectorDataModification) -> Option<LayerNodeIdentifier> {
|
||||
let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE];
|
||||
let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE];
|
||||
let mut empty = false;
|
||||
|
||||
self.modify_inputs("Shape", false, |inputs, _node_id, _metadata| {
|
||||
let [subpaths, colinear_manipulators] = inputs.as_mut_slice() else {
|
||||
panic!("Shape does not have both `subpath` and `colinear_manipulators` inputs");
|
||||
};
|
||||
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Subpaths(subpaths),
|
||||
pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
|
||||
self.modify_inputs("Path", false, |inputs, _node_id, _metadata| {
|
||||
let [_, NodeInput::Value {
|
||||
tagged_value: TaggedValue::VectorModification(modification),
|
||||
..
|
||||
} = subpaths
|
||||
}] = inputs.as_mut_slice()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::ManipulatorGroupIds(colinear_manipulators),
|
||||
..
|
||||
} = colinear_manipulators
|
||||
else {
|
||||
return;
|
||||
panic!("Path node does not have modification input");
|
||||
};
|
||||
|
||||
[old_bounds_min, old_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||
|
||||
transform_utils::VectorModificationState { subpaths, colinear_manipulators }.modify(modification);
|
||||
empty = !subpaths.iter().any(|subpath| !subpath.is_empty());
|
||||
|
||||
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||
modification.modify(&modification_type);
|
||||
});
|
||||
|
||||
self.update_bounds([old_bounds_min, old_bounds_max], [new_bounds_min, new_bounds_max]);
|
||||
|
||||
if empty {
|
||||
self.layer_node.map(|layer_id| LayerNodeIdentifier::new(layer_id, &self.document_network))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
|
||||
|
||||
@@ -761,20 +761,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentInputType::value("Dimensions", TaggedValue::UVec2((512, 512).into()), false),
|
||||
DocumentInputType::value("Seed", TaggedValue::U32(0), false),
|
||||
DocumentInputType::value("Scale", TaggedValue::F64(10.), false),
|
||||
DocumentInputType::value("Noise Type", TaggedValue::NoiseType(NoiseType::Perlin), false),
|
||||
DocumentInputType::value("Noise Type", TaggedValue::NoiseType(NoiseType::default()), false),
|
||||
// Domain Warp
|
||||
DocumentInputType::value("Domain Warp Type", TaggedValue::DomainWarpType(DomainWarpType::None), false),
|
||||
DocumentInputType::value("Domain Warp Type", TaggedValue::DomainWarpType(DomainWarpType::default()), false),
|
||||
DocumentInputType::value("Domain Warp Amplitude", TaggedValue::F64(100.), false),
|
||||
// Fractal
|
||||
DocumentInputType::value("Fractal Type", TaggedValue::FractalType(FractalType::None), false),
|
||||
DocumentInputType::value("Fractal Type", TaggedValue::FractalType(FractalType::default()), false),
|
||||
DocumentInputType::value("Fractal Octaves", TaggedValue::U32(3), false),
|
||||
DocumentInputType::value("Fractal Lacunarity", TaggedValue::F64(2.), false),
|
||||
DocumentInputType::value("Fractal Gain", TaggedValue::F64(0.5), false),
|
||||
DocumentInputType::value("Fractal Weighted Strength", TaggedValue::F64(0.), false), // 0-1 range
|
||||
DocumentInputType::value("Fractal Ping Pong Strength", TaggedValue::F64(2.), false),
|
||||
// Cellular
|
||||
DocumentInputType::value("Cellular Distance Function", TaggedValue::CellularDistanceFunction(CellularDistanceFunction::Euclidean), false),
|
||||
DocumentInputType::value("Cellular Return Type", TaggedValue::CellularReturnType(CellularReturnType::Nearest), false),
|
||||
DocumentInputType::value("Cellular Distance Function", TaggedValue::CellularDistanceFunction(CellularDistanceFunction::default()), false),
|
||||
DocumentInputType::value("Cellular Return Type", TaggedValue::CellularReturnType(CellularReturnType::default()), false),
|
||||
DocumentInputType::value("Cellular Jitter", TaggedValue::F64(1.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
@@ -802,7 +802,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Insertion", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Replace", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false),
|
||||
DocumentInputType::value("Replace", TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::insert_channel_properties,
|
||||
@@ -935,7 +935,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
name: "Color Channel",
|
||||
category: "Raster",
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
|
||||
inputs: vec![DocumentInputType::value("Channel", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false)],
|
||||
inputs: vec![DocumentInputType::value("Channel", TaggedValue::RedGreenBlue(RedGreenBlue::default()), false)],
|
||||
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
|
||||
properties: node_properties::color_channel_properties,
|
||||
..Default::default()
|
||||
@@ -944,7 +944,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
name: "Blend Mode Value",
|
||||
category: "Inputs",
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
|
||||
inputs: vec![DocumentInputType::value("Blend Mode", TaggedValue::BlendMode(BlendMode::Normal), false)],
|
||||
inputs: vec![DocumentInputType::value("Blend Mode", TaggedValue::BlendMode(BlendMode::default()), false)],
|
||||
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
|
||||
properties: node_properties::blend_mode_value_properties,
|
||||
..Default::default()
|
||||
@@ -955,7 +955,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::raster::LuminanceNode<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("Luminance Calc", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
|
||||
DocumentInputType::value("Luminance Calc", TaggedValue::LuminanceCalculation(LuminanceCalculation::default()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::luminance_properties,
|
||||
@@ -967,7 +967,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ExtractChannelNode<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType::value("From", TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
||||
DocumentInputType::value("From", TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::default()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::extract_channel_properties,
|
||||
@@ -1864,7 +1864,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentInputType::value("(Blue) Blue", TaggedValue::F64(100.), false),
|
||||
DocumentInputType::value("(Blue) Constant", TaggedValue::F64(0.), false),
|
||||
// Display-only properties (not used within the node)
|
||||
DocumentInputType::value("Output Channel", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false),
|
||||
DocumentInputType::value("Output Channel", TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_channel_mixer_properties,
|
||||
@@ -1879,7 +1879,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
// Mode
|
||||
DocumentInputType::value("Mode", TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), false),
|
||||
DocumentInputType::value("Mode", TaggedValue::RelativeAbsolute(RelativeAbsolute::default()), false),
|
||||
// Reds
|
||||
DocumentInputType::value("(Reds) Cyan", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Reds) Magenta", TaggedValue::F64(0.), false),
|
||||
@@ -1926,7 +1926,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentInputType::value("(Blacks) Yellow", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("(Blacks) Black", TaggedValue::F64(0.), false),
|
||||
// Display-only properties (not used within the node)
|
||||
DocumentInputType::value("Colors", TaggedValue::SelectiveColorChoice(SelectiveColorChoice::Reds), false),
|
||||
DocumentInputType::value("Colors", TaggedValue::SelectiveColorChoice(SelectiveColorChoice::default()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_selective_color_properties,
|
||||
@@ -2347,11 +2347,41 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false),
|
||||
DocumentInputType::value("Colinear Manipulators", TaggedValue::ManipulatorGroupIds(vec![]), false),
|
||||
DocumentInputType::value("Colinear Manipulators", TaggedValue::PointIds(vec![]), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::VectorData)],
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
name: "Path",
|
||||
category: "Vector",
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorData), 0)],
|
||||
..monitor_node()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Path Modify".to_string(),
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_core::vector::VectorModification), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::PathModify<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (NodeId(id as u64), node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true),
|
||||
DocumentInputType::value("Modification", TaggedValue::VectorModification(Default::default()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector Data", FrontendGraphDataType::VectorData)],
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
name: "Sample",
|
||||
category: "Structural",
|
||||
@@ -2489,8 +2519,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentInputType::value("Weight", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Dash Lengths", TaggedValue::VecF64(Vec::new()), false),
|
||||
DocumentInputType::value("Dash Offset", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Line Cap", TaggedValue::LineCap(graphene_core::vector::style::LineCap::Butt), false),
|
||||
DocumentInputType::value("Line Join", TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter), false),
|
||||
DocumentInputType::value("Line Cap", TaggedValue::LineCap(graphene_core::vector::style::LineCap::default()), false),
|
||||
DocumentInputType::value("Line Join", TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::default()), false),
|
||||
DocumentInputType::value("Miter Limit", TaggedValue::F64(4.), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::VectorData)],
|
||||
|
||||
@@ -16,13 +16,12 @@ use bezier_rs::Subpath;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, FlowType, NodeId, NodeInput, NodeNetwork, Previewing, Source};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::*;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
use renderer::{ClickTarget, Quad};
|
||||
use vector::PointId;
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use renderer::{ClickTarget, Quad};
|
||||
use web_sys::window;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeGraphHandlerData<'a> {
|
||||
@@ -62,7 +61,7 @@ pub struct NodeGraphMessageHandler {
|
||||
/// Click targets for every node in the network by using the path to that node.
|
||||
pub node_metadata: HashMap<NodeId, NodeMetadata>,
|
||||
/// Cache for the bounding box around all nodes in node graph space.
|
||||
pub bounding_box_subpath: Option<Subpath<ManipulatorGroupId>>,
|
||||
pub bounding_box_subpath: Option<Subpath<PointId>>,
|
||||
auto_panning: AutoPanning,
|
||||
}
|
||||
|
||||
@@ -1548,8 +1547,15 @@ impl NodeGraphMessageHandler {
|
||||
common
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_text_width(node: &DocumentNode) -> Option<f64> {
|
||||
let document = window().unwrap().document().unwrap();
|
||||
warn!("Failed to find width of {node:#?} due to non-wasm arch");
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn get_text_width(node: &DocumentNode) -> Option<f64> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let div = match document.create_element("div") {
|
||||
Ok(div) => div,
|
||||
Err(err) => {
|
||||
@@ -1594,6 +1600,7 @@ impl NodeGraphMessageHandler {
|
||||
|
||||
Some(text_width)
|
||||
}
|
||||
|
||||
pub fn layer_width_cells(node: &DocumentNode) -> u32 {
|
||||
let half_grid_cell_offset = 24. / 2.;
|
||||
let thumbnail_width = 3. * 24.;
|
||||
|
||||
@@ -1733,25 +1733,7 @@ pub fn logic_operator_properties(document_node: &DocumentNode, node_id: NodeId,
|
||||
}
|
||||
|
||||
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let translation_assist = |widgets: &mut Vec<WidgetHolder>| {
|
||||
let pivot_index = 5;
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(pivot),
|
||||
exposed: false,
|
||||
} = document_node.inputs[pivot_index]
|
||||
{
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(
|
||||
PivotInput::new(pivot.into())
|
||||
.on_update(update_value(|pivot: &PivotInput| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
);
|
||||
} else {
|
||||
add_blank_assist(widgets);
|
||||
}
|
||||
};
|
||||
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", None, translation_assist);
|
||||
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", None, add_blank_assist);
|
||||
|
||||
let rotation = {
|
||||
let index = 2;
|
||||
|
||||
@@ -18,7 +18,7 @@ impl FrontendGraphDataType {
|
||||
pub fn with_type(input: &Type) -> Self {
|
||||
match TaggedValue::from_type(input) {
|
||||
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
|
||||
TaggedValue::Subpaths(_) | TaggedValue::RcSubpath(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||
TaggedValue::U32(_)
|
||||
| TaggedValue::U64(_)
|
||||
| TaggedValue::F64(_)
|
||||
|
||||
@@ -37,11 +37,10 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
|
||||
} else {
|
||||
DVec2::new(secondary_pos, primary_end)
|
||||
};
|
||||
overlay_context.line(
|
||||
overlay_context.colored_line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
None,
|
||||
&("#".to_string() + &grid_color.rgba_hex()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -112,11 +111,10 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
||||
let x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x;
|
||||
let start = DVec2::new(x_pos, min_y);
|
||||
let end = DVec2::new(x_pos, max_y);
|
||||
overlay_context.line(
|
||||
overlay_context.colored_line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
None,
|
||||
&("#".to_string() + &grid_color.rgba_hex()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -131,11 +129,10 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
||||
let y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y;
|
||||
let start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos)));
|
||||
let end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos)));
|
||||
overlay_context.line(
|
||||
overlay_context.colored_line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
None,
|
||||
&("#".to_string() + &grid_color.rgba_hex()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -178,7 +175,7 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
|
||||
let start = DVec2::new(min_x + x_offset, project(&DVec2::new(min_x + x_offset, y_pos)));
|
||||
let end = DVec2::new(max_x + x_offset, project(&DVec2::new(max_x + x_offset, y_pos)));
|
||||
|
||||
overlay_context.line(
|
||||
overlay_context.dashed_line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use super::utility_types::OverlayContext;
|
||||
use crate::consts::HIDE_HANDLE_DISTANCE;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_groups, get_subpaths};
|
||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
|
||||
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
|
||||
use glam::DVec2;
|
||||
use wasm_bindgen::JsCast;
|
||||
@@ -26,54 +25,55 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
|
||||
|
||||
pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||
let Some(subpaths) = get_subpaths(layer, &document.network) else { continue };
|
||||
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
|
||||
continue;
|
||||
};
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
overlay_context.outline(subpaths.iter(), transform);
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
|
||||
for manipulator_group in get_manipulator_groups(subpaths) {
|
||||
let anchor = manipulator_group.anchor;
|
||||
let anchor_position = transform.transform_point2(anchor);
|
||||
|
||||
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
if let Some(in_handle) = manipulator_group.in_handle.filter(not_under_anchor) {
|
||||
let handle_position = transform.transform_point2(in_handle);
|
||||
overlay_context.line(handle_position, anchor_position, None, None);
|
||||
overlay_context.manipulator_handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle)));
|
||||
for (segment_id, bezier, _start, _end) in vector_data.segment_bezier_iter() {
|
||||
let bezier = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||
let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
match bezier.handles {
|
||||
bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {
|
||||
overlay_context.line(handle, bezier.start);
|
||||
overlay_context.line(handle, bezier.end);
|
||||
overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)));
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
if not_under_anchor(handle_start, bezier.start) {
|
||||
overlay_context.line(handle_start, bezier.start);
|
||||
overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)));
|
||||
}
|
||||
if not_under_anchor(handle_end, bezier.end) {
|
||||
overlay_context.line(handle_end, bezier.end);
|
||||
overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if let Some(out_handle) = manipulator_group.out_handle.filter(not_under_anchor) {
|
||||
let handle_position = transform.transform_point2(out_handle);
|
||||
overlay_context.line(handle_position, anchor_position, None, None);
|
||||
overlay_context.manipulator_handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle)));
|
||||
}
|
||||
|
||||
overlay_context.manipulator_anchor(anchor_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor)), None);
|
||||
}
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(selected, ManipulatorPointId::Anchor(id)), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||
let Some(subpaths) = get_subpaths(layer, &document.network) else { continue };
|
||||
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
|
||||
continue;
|
||||
};
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
|
||||
let mut manipulator_groups = get_manipulator_groups(subpaths);
|
||||
|
||||
if let Some(first_manipulator) = manipulator_groups.next() {
|
||||
let anchor = first_manipulator.anchor;
|
||||
let anchor_position = transform.transform_point2(anchor);
|
||||
|
||||
overlay_context.manipulator_anchor(anchor_position, is_selected(selected, ManipulatorPointId::new(first_manipulator.id, SelectedType::Anchor)), None);
|
||||
};
|
||||
|
||||
if let Some(last_manipulator) = manipulator_groups.last() {
|
||||
let anchor = last_manipulator.anchor;
|
||||
let anchor_position = transform.transform_point2(anchor);
|
||||
|
||||
overlay_context.manipulator_anchor(anchor_position, is_selected(selected, ManipulatorPointId::new(last_manipulator.id, SelectedType::Anchor)), None);
|
||||
};
|
||||
for point in vector_data.single_connected_points() {
|
||||
let Some(position) = vector_data.point_domain.position_from_id(point) else { continue };
|
||||
let position = transform.transform_point2(position);
|
||||
overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ use super::utility_functions::overlay_canvas_context;
|
||||
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER};
|
||||
use crate::messages::prelude::Message;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
use graphene_core::renderer::Quad;
|
||||
use wasm_bindgen::JsValue;
|
||||
use graphene_std::vector::{PointId, VectorData};
|
||||
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::TAU;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||
|
||||
@@ -39,7 +41,15 @@ impl OverlayContext {
|
||||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option<f64>) {
|
||||
pub fn line(&mut self, start: DVec2, end: DVec2) {
|
||||
self.dashed_line(start, end, None, None)
|
||||
}
|
||||
|
||||
pub fn colored_line(&mut self, start: DVec2, end: DVec2, color: &str) {
|
||||
self.dashed_line(start, end, Some(color), None)
|
||||
}
|
||||
|
||||
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option<f64>) {
|
||||
let start = start.round() - DVec2::splat(0.5);
|
||||
let end = end.round() - DVec2::splat(0.5);
|
||||
if let Some(dash_width) = dash_width {
|
||||
@@ -152,9 +162,44 @@ impl OverlayContext {
|
||||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
pub fn outline<'a, Id: bezier_rs::Identifier>(&mut self, subpaths: impl Iterator<Item = &'a Subpath<Id>>, transform: DAffine2) {
|
||||
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||
self.render_context.begin_path();
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
self.bezier_command(bezier, transform, move_to);
|
||||
}
|
||||
|
||||
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_BLUE));
|
||||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_BLUE));
|
||||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
fn bezier_command(&self, bezier: Bezier, transform: DAffine2, move_to: bool) {
|
||||
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||
if move_to {
|
||||
self.render_context.move_to(start.x, start.y);
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => self.render_context.line_to(end.x, end.y),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => self.render_context.quadratic_curve_to(handle.x, handle.y, end.x, end.y),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => self.render_context.bezier_curve_to(handle_start.x, handle_start.y, handle_end.x, handle_end.y, end.x, end.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outline<'a>(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) {
|
||||
self.render_context.begin_path();
|
||||
for subpath in subpaths {
|
||||
let subpath = subpath.borrow();
|
||||
let mut curves = subpath.iter().peekable();
|
||||
|
||||
let Some(first) = curves.peek() else {
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use super::nodes::SelectedNodes;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::FlowType;
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use graphene_core::renderer::ClickTarget;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorData;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::vector::PointId;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
@@ -26,6 +29,7 @@ pub struct DocumentMetadata {
|
||||
hidden: HashSet<NodeId>,
|
||||
locked: HashSet<NodeId>,
|
||||
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
vector_modify: HashMap<NodeId, VectorData>,
|
||||
/// Transform from document space to viewport space.
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
@@ -39,6 +43,7 @@ impl Default for DocumentMetadata {
|
||||
folders: HashSet::new(),
|
||||
hidden: HashSet::new(),
|
||||
locked: HashSet::new(),
|
||||
vector_modify: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
document_to_viewport: DAffine2::IDENTITY,
|
||||
}
|
||||
@@ -62,6 +67,23 @@ impl DocumentMetadata {
|
||||
self.click_targets.get(&layer)
|
||||
}
|
||||
|
||||
/// Get vector data after the modification is appled
|
||||
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<VectorData> {
|
||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network);
|
||||
|
||||
if let Some(vector_data) = graph_layer.upstream_node_id_from_name("Path").and_then(|node| self.vector_modify.get(&node)) {
|
||||
let mut modified = vector_data.clone();
|
||||
if let Some(TaggedValue::VectorModification(modification)) = graph_layer.find_input("Path", 1) {
|
||||
modification.apply(&mut modified);
|
||||
}
|
||||
return Some(modified);
|
||||
}
|
||||
self.click_targets
|
||||
.get(&layer)
|
||||
.map(|click| click.iter().map(|click| &click.subpath))
|
||||
.map(|subpaths| VectorData::from_subpaths(subpaths, true))
|
||||
}
|
||||
|
||||
/// Access the [`NodeRelations`] of a layer.
|
||||
fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {
|
||||
self.structure.get(&node_identifier)
|
||||
@@ -225,6 +247,7 @@ impl DocumentMetadata {
|
||||
|
||||
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
|
||||
self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
|
||||
self.vector_modify.retain(|node, _| graph.nodes.contains_key(node));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,9 +304,10 @@ impl DocumentMetadata {
|
||||
// ===============================
|
||||
|
||||
impl DocumentMetadata {
|
||||
/// Update the cached click targets of the layers
|
||||
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
|
||||
/// Update the cached click targets and vector modify values of the layers
|
||||
pub fn update_from_monitor(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, new_vector_modify: HashMap<NodeId, VectorData>) {
|
||||
self.click_targets = new_click_targets;
|
||||
self.vector_modify = new_vector_modify;
|
||||
}
|
||||
|
||||
/// Get the bounding box of the click target of the specified layer in the specified transform space
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::{TransformIn, VectorDataModification};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
@@ -8,15 +8,37 @@ use crate::messages::tool::utility_types::ToolType;
|
||||
|
||||
use graph_craft::document::NodeNetwork;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
use graphene_core::vector::VectorModificationType;
|
||||
use graphene_std::vector::{HandleId, PointId};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
struct AnchorPoint {
|
||||
initial: DVec2,
|
||||
current: DVec2,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
struct HandlePoint {
|
||||
initial: DVec2,
|
||||
relative: DVec2,
|
||||
anchor: PointId,
|
||||
mirror: Option<(HandleId, DVec2)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct InitialPoints {
|
||||
anchors: HashMap<PointId, AnchorPoint>,
|
||||
handles: HashMap<HandleId, HandlePoint>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum OriginalTransforms {
|
||||
Layer(HashMap<LayerNodeIdentifier, DAffine2>),
|
||||
Path(HashMap<LayerNodeIdentifier, Vec<(ManipulatorPointId, DVec2)>>),
|
||||
Path(HashMap<LayerNodeIdentifier, InitialPoints>),
|
||||
}
|
||||
impl Default for OriginalTransforms {
|
||||
fn default() -> Self {
|
||||
@@ -43,36 +65,46 @@ impl OriginalTransforms {
|
||||
}
|
||||
}
|
||||
OriginalTransforms::Path(path_map) => {
|
||||
let Some(shape_editor) = shape_editor else {
|
||||
warn!("No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType");
|
||||
return;
|
||||
};
|
||||
for &layer in selected {
|
||||
let Some(shape_editor) = shape_editor else {
|
||||
warn!("No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType");
|
||||
continue;
|
||||
};
|
||||
// Anchors also move their handles
|
||||
let expand_anchors = |&point: &ManipulatorPointId| {
|
||||
if point.manipulator_type.is_handle() {
|
||||
[Some(point), None, None]
|
||||
} else {
|
||||
[
|
||||
Some(point),
|
||||
Some(ManipulatorPointId::new(point.group, SelectedType::InHandle)),
|
||||
Some(ManipulatorPointId::new(point.group, SelectedType::OutHandle)),
|
||||
]
|
||||
}
|
||||
};
|
||||
let points = shape_editor.selected_points().flat_map(expand_anchors).flatten();
|
||||
if path_map.contains_key(&layer) {
|
||||
continue;
|
||||
}
|
||||
let Some(vector_data) = graph_modification_utils::get_subpaths(layer, document_network) else {
|
||||
let Some(vector_data) = document_metadata.compute_modified_vector(layer, document_network) else {
|
||||
continue;
|
||||
};
|
||||
let get_manipulator_point_position = |point_id: ManipulatorPointId| {
|
||||
graph_modification_utils::get_manipulator_from_id(vector_data, point_id.group)
|
||||
.and_then(|manipulator_group| point_id.manipulator_type.get_position(manipulator_group))
|
||||
.map(|position| (point_id, position))
|
||||
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
|
||||
continue;
|
||||
};
|
||||
path_map.insert(layer, points.filter_map(get_manipulator_point_position).collect());
|
||||
|
||||
// Anchors also move their handles
|
||||
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||
let anchors = anchor_ids.filter_map(|id| vector_data.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
|
||||
let anchors = anchors.collect();
|
||||
|
||||
let selected_handles = selected_points.iter().filter_map(|point| point.as_handle());
|
||||
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||
let connected_handles = anchor_ids.flat_map(|point| vector_data.segment_domain.all_connected(point));
|
||||
let all_handles = selected_handles.chain(connected_handles);
|
||||
|
||||
let handles = all_handles
|
||||
.filter_map(|id| {
|
||||
let anchor = id.to_manipulator_point().get_anchor(&vector_data)?;
|
||||
let initial = id.to_manipulator_point().get_position(&vector_data)?;
|
||||
let relative = vector_data.point_domain.position_from_id(anchor)?;
|
||||
let other_handle = vector_data
|
||||
.other_colinear_handle(id)
|
||||
.filter(|other| !selected_points.contains(&other.to_manipulator_point()) && !selected_points.contains(&ManipulatorPointId::Anchor(anchor)));
|
||||
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector_data)?)));
|
||||
|
||||
Some((id, HandlePoint { initial, relative, anchor, mirror }))
|
||||
})
|
||||
.collect();
|
||||
|
||||
path_map.insert(layer, InitialPoints { anchors, handles });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,28 +414,31 @@ impl<'a> Selected<'a> {
|
||||
});
|
||||
}
|
||||
|
||||
fn transform_path(
|
||||
document_metadata: &DocumentMetadata,
|
||||
layer: LayerNodeIdentifier,
|
||||
initial_points: Option<&Vec<(ManipulatorPointId, DVec2)>>,
|
||||
transformation: DAffine2,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
fn transform_path(document_metadata: &DocumentMetadata, layer: LayerNodeIdentifier, initial_points: &mut InitialPoints, transformation: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
let viewspace = document_metadata.transform_to_viewport(layer);
|
||||
let layerspace_rotation = viewspace.inverse() * transformation;
|
||||
|
||||
let Some(initial_points) = initial_points else {
|
||||
return;
|
||||
};
|
||||
for (&point, anchor) in initial_points.anchors.iter_mut() {
|
||||
let new_pos_viewport = layerspace_rotation.transform_point2(viewspace.transform_point2(anchor.initial));
|
||||
let delta = new_pos_viewport - anchor.current;
|
||||
anchor.current += delta;
|
||||
let modification_type = VectorModificationType::ApplyPointDelta { point, delta };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
for (point_id, position) in initial_points {
|
||||
let viewport_point = viewspace.transform_point2(*position);
|
||||
let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point);
|
||||
let point = *point_id;
|
||||
let position = new_pos_viewport;
|
||||
let modification = VectorDataModification::SetManipulatorPosition { point, position };
|
||||
for (&id, handle) in initial_points.handles.iter() {
|
||||
let new_pos_viewport = layerspace_rotation.transform_point2(viewspace.transform_point2(handle.initial));
|
||||
let relative = initial_points.anchors.get(&handle.anchor).map_or(handle.relative, |anchor| anchor.current);
|
||||
let modification_type = id.set_relative_position(new_pos_viewport - relative);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification });
|
||||
if let Some((id, initial)) = handle.mirror {
|
||||
let direction = viewspace.transform_vector2(new_pos_viewport - relative).try_normalize();
|
||||
let length = viewspace.transform_vector2(initial - relative).length();
|
||||
let new_relative = direction.map_or(initial - relative, |direction| viewspace.inverse().transform_vector2(-direction * length));
|
||||
let modification_type = id.set_relative_position(new_relative);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,9 +448,13 @@ impl<'a> Selected<'a> {
|
||||
for layer_ancestors in self.document_metadata.shallowest_unique_layers(self.selected.iter().copied()) {
|
||||
let layer = *layer_ancestors.last().unwrap();
|
||||
|
||||
match &self.original_transforms {
|
||||
match &mut self.original_transforms {
|
||||
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.document_metadata, layer, layer_transforms.get(&layer), transformation, self.responses),
|
||||
OriginalTransforms::Path(path_transforms) => Self::transform_path(self.document_metadata, layer, path_transforms.get(&layer), transformation, self.responses),
|
||||
OriginalTransforms::Path(path_transforms) => {
|
||||
if let Some(initial_points) = path_transforms.get_mut(&layer) {
|
||||
Self::transform_path(self.document_metadata, layer, initial_points, transformation, self.responses)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,11 +481,20 @@ impl<'a> Selected<'a> {
|
||||
}
|
||||
OriginalTransforms::Path(path) => {
|
||||
for (&layer, points) in path {
|
||||
for &(point, position) in points {
|
||||
self.responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
for (&point, &anchor) in &points.anchors {
|
||||
let delta = anchor.initial - anchor.current;
|
||||
let modification_type = VectorModificationType::ApplyPointDelta { point, delta };
|
||||
self.responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
for (&point, &handle) in &points.handles {
|
||||
let modification_type = point.set_relative_position(handle.initial - handle.relative);
|
||||
self.responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
if let Some((id, initial)) = handle.mirror {
|
||||
let modification_type = id.set_relative_position(initial - handle.relative);
|
||||
self.responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user