Migrate vector data and tools to use nodes (#1065)

* Add rendering to vector nodes

* Add line, shape, rectange and freehand tool

* Fix transforms, strokes and fills

* Migrate spline tool

* Remove blank lines

* Fix test

* Fix fill in properties

* Select layers when filling

* Properties panel transform around pivot

* Fix select tool outlines

* Select tool modifies node graph pivot

* Add the pivot assist to the properties

* Improve setting non existant fill UX

* Cleanup hash function

* Path and pen tools

* Bug fixes

* Disable boolean ops

* Fix default handle smoothing on ellipses

* Fix test and warnings

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-03-26 08:03:51 +01:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 639a24d8ad
commit 959e790cdf
64 changed files with 2639 additions and 1552 deletions
@@ -36,6 +36,9 @@ pub enum DocumentMessage {
#[remain::unsorted]
#[child]
NodeGraph(NodeGraphMessage),
#[remain::unsorted]
#[child]
GraphOperation(GraphOperationMessage),
// Messages
AbortTransaction,
@@ -62,9 +65,7 @@ pub enum DocumentMessage {
layer_path: Vec<LayerId>,
},
DeleteSelectedLayers,
DeleteSelectedManipulatorPoints,
DeselectAllLayers,
DeselectAllManipulatorPoints,
DirtyRenderDocument,
DirtyRenderDocumentInOutlineView,
DocumentHistoryBackward,
@@ -93,11 +94,6 @@ pub enum DocumentMessage {
insert_index: isize,
reverse_index: bool,
},
MoveSelectedManipulatorPoints {
layer_path: Vec<LayerId>,
delta: (f64, f64),
mirror_distance: bool,
},
NodeGraphFrameClear {
layer_path: Vec<LayerId>,
node_id: NodeId,
@@ -193,10 +189,6 @@ pub enum DocumentMessage {
ToggleLayerVisibility {
layer_path: Vec<LayerId>,
},
ToggleSelectedHandleMirroring {
layer_path: Vec<LayerId>,
toggle_angle: bool,
},
Undo,
UndoFinished,
UngroupLayers {
@@ -33,7 +33,6 @@ use document_legacy::layers::text_layer::Font;
use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
use graph_craft::document::NodeId;
use graphene_core::raster::{Color, ImageFrame};
use graphene_std::vector::subpath::Subpath;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
@@ -207,6 +206,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let selected_layers = &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice()));
self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, selected_layers));
}
#[remain::unsorted]
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)),
// Messages
AbortTransaction => {
@@ -252,13 +253,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
_ => lerp(&bbox),
};
let translation = (aggregated - center) * axis;
responses.push_back(
DocumentOperation::TransformLayerInViewport {
path: path.to_vec(),
transform: DAffine2::from_translation(translation).to_cols_array(),
}
.into(),
);
responses.add(GraphOperationMessage::TransformChange {
layer: path.to_vec(),
transform: DAffine2::from_translation(translation),
transform_in: TransformIn::Viewport,
});
}
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
@@ -323,25 +322,10 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.push_front(BroadcastEvent::SelectionChanged.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
DeleteSelectedManipulatorPoints => {
responses.push_back(StartTransaction.into());
responses.push_front(
DocumentOperation::DeleteSelectedManipulatorPoints {
layer_paths: self.selected_layers_without_children().iter().map(|path| path.to_vec()).collect(),
}
.into(),
);
}
DeselectAllLayers => {
responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into());
self.layer_range_selection_reference.clear();
}
DeselectAllManipulatorPoints => {
for layer_path in self.selected_layers_without_children() {
responses.push_back(DocumentOperation::DeselectAllManipulatorPoints { layer_path: layer_path.to_vec() }.into());
}
}
DirtyRenderDocument => {
// Mark all non-overlay caches as dirty
DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root);
@@ -409,14 +393,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let center = (max + min) / 2.;
let bbox_trans = DAffine2::from_translation(-center);
for path in self.selected_layers() {
responses.push_back(
DocumentOperation::TransformLayerInScope {
path: path.to_vec(),
transform: DAffine2::from_scale(scale).to_cols_array(),
scope: bbox_trans.to_cols_array(),
}
.into(),
);
responses.add(GraphOperationMessage::TransformChange {
layer: path.to_vec(),
transform: DAffine2::from_scale(scale),
transform_in: TransformIn::Scope { scope: bbox_trans },
});
}
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
@@ -507,11 +488,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
.into(),
);
}
MoveSelectedManipulatorPoints { layer_path, delta, mirror_distance } => {
if let Ok(_layer) = self.document_legacy.layer(&layer_path) {
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta, mirror_distance }.into());
}
}
NodeGraphFrameClear {
layer_path,
node_id,
@@ -577,7 +553,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
for path in self.selected_layers().map(|path| path.to_vec()) {
// Nudge translation
let transform = if !ipp.keyboard.key(resize) {
Some(DAffine2::from_translation((delta_x, delta_y).into()).to_cols_array())
Some(DAffine2::from_translation((delta_x, delta_y).into()))
}
// Nudge resize
else {
@@ -595,12 +571,13 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
let offset = DAffine2::from_translation(if opposite_corner { -existing_bottom_right } else { -existing_top_left });
let scale = DAffine2::from_scale((new_width / width, new_height / height).into());
(offset.inverse() * scale * offset).to_cols_array()
offset.inverse() * scale * offset
})
};
if let Some(transform) = transform {
responses.push_back(DocumentOperation::TransformLayerInViewport { path, transform }.into());
let transform_in = TransformIn::Viewport;
responses.add(GraphOperationMessage::TransformChange { layer: path, transform, transform_in });
}
}
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
@@ -662,13 +639,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
.into(),
);
responses.push_back(
DocumentOperation::SetLayerTransform {
path: path.clone(),
transform: transform.to_cols_array(),
}
.into(),
);
responses.add(GraphOperationMessage::TransformSet {
layer: path.clone(),
transform,
transform_in: TransformIn::Local,
});
responses.push_back(DocumentMessage::NodeGraphFrameGenerate { layer_path: path }.into());
@@ -920,9 +895,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.push_back(DocumentOperation::ToggleLayerVisibility { path: layer_path }.into());
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
}
ToggleSelectedHandleMirroring { layer_path, toggle_angle } => {
responses.push_back(DocumentOperation::SetSelectedHandleMirroring { layer_path, toggle_angle }.into());
}
Undo => {
self.undo_in_progress = true;
responses.push_back(BroadcastEvent::ToolAbort.into());
@@ -1250,22 +1222,6 @@ impl DocumentMessageHandler {
})
}
/// Returns a copy of all the currently selected [Subpath]s.
pub fn selected_subpaths(&self) -> Vec<Subpath> {
self.selected_visible_layers()
.flat_map(|layer| self.document_legacy.layer(layer))
.flat_map(|layer| layer.as_subpath_copy())
.collect::<Vec<Subpath>>()
}
/// Returns references to all the currently selected [Subpath]s.
pub fn selected_subpaths_ref(&self) -> Vec<&Subpath> {
self.selected_visible_layers()
.flat_map(|layer| self.document_legacy.layer(layer))
.flat_map(|layer| layer.as_subpath())
.collect::<Vec<&Subpath>>()
}
/// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths.
pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec<Vec<LayerId>>>, ignore_artboard: Option<LayerId>, render_data: &'a RenderData) -> impl Iterator<Item = [DVec2; 2]> + 'a {
self.visible_layers()
@@ -0,0 +1,44 @@
use crate::messages::prelude::*;
use glam::{DAffine2, DVec2};
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::ManipulatorPointId;
pub type LayerIdentifier = Vec<document_legacy::LayerId>;
#[impl_message(Message, DocumentMessage, GraphOperation)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum GraphOperationMessage {
FillSet { layer: LayerIdentifier, fill: Fill },
StrokeSet { layer: LayerIdentifier, stroke: Stroke },
TransformChange { layer: LayerIdentifier, transform: DAffine2, transform_in: TransformIn },
TransformSet { layer: LayerIdentifier, transform: DAffine2, transform_in: TransformIn },
TransformSetPivot { layer: LayerIdentifier, pivot: DVec2 },
Vector { layer: LayerIdentifier, modification: VectorDataModification },
}
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn {
Local,
Scope { scope: DAffine2 },
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 },
SetManipulatorHandleMirroring { id: ManipulatorGroupId, mirror_angle: bool },
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
ToggleManipulatorHandleMirroring { id: ManipulatorGroupId },
}
@@ -0,0 +1,262 @@
use crate::messages::prelude::*;
use document_legacy::document::Document;
use document_legacy::{LayerId, Operation};
use glam::{DAffine2, DVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
use graphene_core::vector::style::{Fill, FillType, Stroke};
use transform_utils::LayerBounds;
use super::{resolve_document_node_type, VectorDataModification};
mod transform_utils;
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphOperationMessageHandler;
struct ModifyInputsContext<'a> {
network: &'a mut NodeNetwork,
node_graph: &'a mut NodeGraphMessageHandler,
responses: &'a mut VecDeque<Message>,
layer: &'a [LayerId],
}
impl<'a> ModifyInputsContext<'a> {
/// Get the node network from the document
fn new(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Option<Self> {
document.layer_mut(&layer).ok().and_then(|layer| layer.as_node_graph_mut().ok()).map(|network| Self {
network,
node_graph,
responses,
layer,
})
}
/// Updates the input of an existing node
fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
let document_node = self.network.nodes.get_mut(&node_id).unwrap();
update_input(&mut document_node.inputs);
}
/// Insert a new node and modify the inputs
fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
let output_node_id = self.network.outputs[0].node_id;
let Some(output_node) = self.network.nodes.get_mut(&output_node_id) else {
warn!("Output node doesn't exist");
return;
};
let metadata = output_node.metadata.clone();
let new_input = output_node.inputs[0].clone();
let node_id = generate_uuid();
output_node.inputs[0] = NodeInput::node(node_id, 0);
let Some(node_type) = resolve_document_node_type(name) else {
warn!("Node type \"{name}\" doesn't exist");
return;
};
let mut new_document_node = node_type.to_document_node_default_inputs([Some(new_input)], metadata);
update_input(&mut new_document_node.inputs);
self.network.nodes.insert(node_id, new_document_node);
}
/// Changes the inputs of a specific node
fn modify_inputs(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
let node_id = self.network.primary_flow().find(|(node, _)| node.name == name).map(|(_, id)| id);
if let Some(node_id) = node_id {
self.modify_existing_node_inputs(node_id, update_input);
} else {
self.modify_new_node(name, update_input);
}
self.node_graph.layer_path = Some(self.layer.to_vec());
self.node_graph.nested_path.clear();
self.responses.add(PropertiesPanelMessage::ResendActiveProperties);
let layer_path = self.layer.to_vec();
self.responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path });
}
fn fill_set(&mut self, fill: Fill) {
self.modify_inputs("Fill", |inputs| {
let fill_type = match fill {
Fill::None => FillType::None,
Fill::Solid(_) => FillType::Solid,
Fill::Gradient(_) => FillType::Gradient,
};
inputs[1] = NodeInput::value(TaggedValue::FillType(fill_type), false);
if Fill::None == fill {
inputs[2] = NodeInput::value(TaggedValue::OptionalColor(None), false);
} else if let Fill::Solid(color) = fill {
inputs[2] = NodeInput::value(TaggedValue::OptionalColor(Some(color)), false);
} else if let Fill::Gradient(gradient) = fill {
inputs[3] = NodeInput::value(TaggedValue::GradientType(gradient.gradient_type), false);
inputs[4] = NodeInput::value(TaggedValue::DVec2(gradient.start), false);
inputs[5] = NodeInput::value(TaggedValue::DVec2(gradient.end), false);
inputs[6] = NodeInput::value(TaggedValue::DAffine2(gradient.transform), false);
inputs[7] = NodeInput::value(TaggedValue::GradientPositions(gradient.positions), false);
}
});
}
fn stroke_set(&mut self, stroke: Stroke) {
self.modify_inputs("Stroke", |inputs| {
inputs[1] = NodeInput::value(TaggedValue::Color(stroke.color.unwrap_or_default()), false);
inputs[2] = NodeInput::value(TaggedValue::F64(stroke.weight), false);
inputs[3] = NodeInput::value(TaggedValue::VecF32(stroke.dash_lengths), false);
inputs[4] = NodeInput::value(TaggedValue::F64(stroke.dash_offset), false);
inputs[5] = NodeInput::value(TaggedValue::LineCap(stroke.line_cap), false);
inputs[6] = NodeInput::value(TaggedValue::LineJoin(stroke.line_join), false);
inputs[7] = NodeInput::value(TaggedValue::F64(stroke.line_join_miter_limit), false);
});
}
fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds) {
self.modify_inputs("Transform", |inputs| {
let layer_transform = transform_utils::get_current_transform(inputs);
let to = match transform_in {
TransformIn::Local => DAffine2::IDENTITY,
TransformIn::Scope { scope } => scope * parent_transform,
TransformIn::Viewport => parent_transform,
};
let pivot = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs)));
let transform = to.inverse() * pivot.inverse() * transform * pivot * to * layer_transform;
transform_utils::update_transform(inputs, transform);
});
}
fn transform_set(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds) {
self.modify_inputs("Transform", |inputs| {
let to = match transform_in {
TransformIn::Local => DAffine2::IDENTITY,
TransformIn::Scope { scope } => scope * parent_transform,
TransformIn::Viewport => parent_transform,
};
let pivot = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs)));
let transform = to.inverse() * pivot.inverse() * transform * pivot;
transform_utils::update_transform(inputs, transform);
});
}
fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) {
self.modify_inputs("Transform", |inputs| {
let layer_transform = transform_utils::get_current_transform(inputs);
let old_pivot_transform = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs)));
let new_pivot_transform = DAffine2::from_translation(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);
inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false);
});
}
fn vector_modify(&mut self, modification: VectorDataModification) {
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];
self.modify_inputs("Path Generator", |inputs| {
let [subpaths, mirror_angle_groups] = inputs.as_mut_slice() else {
panic!("Path generator does not have subpath and mirror angle inputs");
};
let NodeInput::Value {
tagged_value: TaggedValue::Subpaths(subpaths),
..
} = subpaths else{
return;
};
let NodeInput::Value {
tagged_value: TaggedValue::ManipulatorGroupIds(mirror_angle_groups),
..
} = mirror_angle_groups else{
return;
};
[old_bounds_min, old_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
transform_utils::VectorModificationState { subpaths, mirror_angle_groups }.modify(modification);
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
});
self.modify_inputs("Transform", |inputs| {
let layer_transform = transform_utils::get_current_transform(inputs);
let normalised_pivot = transform_utils::get_current_normalised_pivot(inputs);
let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalised_pivot + old_bounds_min;
let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalised_pivot + new_bounds_min;
let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot);
let old_pivot_transform = DAffine2::from_translation(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);
});
}
}
impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessageHandler)> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, (document, node_graph): (&mut Document, &mut NodeGraphMessageHandler)) {
match message {
GraphOperationMessage::FillSet { layer, fill } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.fill_set(fill);
} else {
responses.add(Operation::SetLayerFill { path: layer, fill });
}
}
GraphOperationMessage::StrokeSet { layer, stroke } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.stroke_set(stroke);
} else {
responses.add(Operation::SetLayerStroke { path: layer, stroke });
}
}
GraphOperationMessage::TransformChange { layer, transform, transform_in } => {
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds);
} else {
let transform = transform.to_cols_array();
responses.add(match transform_in {
TransformIn::Local => Operation::TransformLayer { path: layer, transform },
TransformIn::Scope { scope } => {
let scope = scope.to_cols_array();
Operation::TransformLayerInScope { path: layer, transform, scope }
}
TransformIn::Viewport => Operation::TransformLayerInViewport { path: layer, transform },
});
}
}
GraphOperationMessage::TransformSet { layer, transform, transform_in } => {
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.transform_set(transform, transform_in, parent_transform, bounds);
} else {
let transform = transform.to_cols_array();
responses.add(match transform_in {
TransformIn::Local => Operation::SetLayerTransform { path: layer, transform },
TransformIn::Scope { scope } => {
let scope = scope.to_cols_array();
Operation::SetLayerTransformInScope { path: layer, transform, scope }
}
TransformIn::Viewport => Operation::SetLayerTransformInViewport { path: layer, transform },
});
}
}
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
let bounds = LayerBounds::new(document, &layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.pivot_set(pivot, bounds);
}
let pivot = pivot.into();
responses.add(Operation::SetPivot { layer_path: layer, pivot });
}
GraphOperationMessage::Vector { layer, modification } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
modify_inputs.vector_modify(modification);
}
}
}
}
fn actions(&self) -> ActionList {
actions!(GraphOperationMessage; )
}
}
@@ -0,0 +1,301 @@
use crate::messages::portfolio::document::node_graph::VectorDataModification;
use bezier_rs::{ManipulatorGroup, Subpath};
use document_legacy::document::Document;
use glam::{DAffine2, DVec2};
use graph_craft::document::{value::TaggedValue, NodeInput};
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::{ManipulatorPointId, SelectedType};
/// Convert an affine transform into scale angle translation and 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;
let y_axis = transform.matrix2.y_axis;
// Assuming there is no vertical shear
let angle = x_axis.y.atan2(x_axis.x);
let (sin, cos) = angle.sin_cos();
let scale_x = if cos.abs() > 1e-10 { x_axis.x / cos } else { x_axis.y / sin };
let mut shear_x = (sin * y_axis.y + cos * y_axis.x) / (sin * sin * scale_x + cos * cos * scale_x);
if !shear_x.is_finite() {
shear_x = 0.;
}
let scale_y = if cos.abs() > 1e-10 {
(y_axis.y - scale_x * sin * shear_x) / cos
} else {
(scale_x * cos * shear_x - y_axis.x) / sin
};
let translation = transform.translation;
let scale = DVec2::new(scale_x, scale_y);
let shear = DVec2::new(shear_x, 0.);
(scale, angle, translation, shear)
}
/// Update the inputs of the transform node to match a new transform
pub fn update_transform(inputs: &mut [NodeInput], transform: DAffine2) {
let (scale, angle, translation, skew) = compute_scale_angle_translation_shear(transform);
inputs[1] = NodeInput::value(TaggedValue::DVec2(translation), false);
inputs[2] = NodeInput::value(TaggedValue::F64(angle), false);
inputs[3] = NodeInput::value(TaggedValue::DVec2(scale), false);
inputs[4] = NodeInput::value(TaggedValue::DVec2(skew), false);
}
/// TODO: This should be extracted from the graph at the location of the transform node.
pub struct LayerBounds {
pub bounds: [DVec2; 2],
pub bounds_transform: DAffine2,
pub layer_transform: DAffine2,
}
impl LayerBounds {
/// Extract the layer bounds and their transform for a layer.
pub fn new(document: &Document, layer_path: &[u64]) -> Self {
let layer = document.layer(layer_path).ok();
let bounds = layer
.and_then(|layer| layer.as_graph_frame().ok())
.and_then(|frame| frame.vector_data.as_ref().map(|vector| vector.nonzero_bounding_box()))
.unwrap_or([DVec2::ZERO, DVec2::ONE]);
let bounds_transform = DAffine2::IDENTITY;
let layer_transform = document.multiply_transforms(layer_path).unwrap_or_default();
Self {
bounds,
bounds_transform,
layer_transform,
}
}
pub fn layerspace_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
self.bounds[0] + (self.bounds[1] - self.bounds[0]) * normalised_pivot
}
pub fn local_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
self.bounds_transform.transform_point2(self.layerspace_pivot(normalised_pivot))
}
}
/// Get the current affine transform from the transform node's inputs
pub fn get_current_transform(inputs: &[NodeInput]) -> DAffine2 {
let translation = if let NodeInput::Value {
tagged_value: TaggedValue::DVec2(translation),
..
} = inputs[1]
{
translation
} else {
DVec2::ZERO
};
let angle = if let NodeInput::Value {
tagged_value: TaggedValue::F64(angle),
..
} = inputs[2]
{
angle
} else {
0.
};
let scale = if let NodeInput::Value {
tagged_value: TaggedValue::DVec2(scale),
..
} = inputs[3]
{
scale
} else {
DVec2::ONE
};
let shear = if let NodeInput::Value {
tagged_value: TaggedValue::DVec2(shear),
..
} = inputs[4]
{
shear
} else {
DVec2::ZERO
};
DAffine2::from_scale_angle_translation(scale, angle, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.])
}
/// Extract the current normalised pivot from the layer
pub fn get_current_normalised_pivot(inputs: &[NodeInput]) -> DVec2 {
if let NodeInput::Value {
tagged_value: TaggedValue::DVec2(pivot),
..
} = inputs[5]
{
pivot
} else {
DVec2::splat(0.5)
}
}
///
// \begin{bmatrix}
// S_{x}\cos(\theta)-S_{y}\sin(\theta)H_{y} & S_{x}\cos(\theta)H_{x}-S_{y}\sin(\theta) & T_{x}\\
// S_{x}\sin(\theta)+S_{y}\cos(\theta)H_{y} & S_{x}\sin(\theta)H_{x}+S_{y}\cos(\theta) & T_{y}\\
// 0 & 0 & 1
// \end{bmatrix}
#[test]
fn derive_transform() {
for shear_x in -10..=10 {
let shear_x = (shear_x as f64) / 2.;
for angle in (0..=360).step_by(15) {
let angle = (angle as f64).to_radians();
for scale_x in 1..10 {
let scale_x = (scale_x as f64) / 5.;
for scale_y in 1..10 {
let scale_y = (scale_y as f64) / 5.;
let shear = DVec2::new(shear_x, 0.);
let scale = DVec2::new(scale_x, scale_y);
let translate = DVec2::new(5666., 644.);
let origional_transform = DAffine2::from_cols(
DVec2::new(scale.x * angle.cos() - scale.y * angle.sin() * shear.y, scale.x * angle.sin() + scale.y * angle.cos() * shear.y),
DVec2::new(scale.x * angle.cos() * shear.x - scale.y * angle.sin(), scale.x * angle.sin() * shear.x + scale.y * angle.cos()),
translate,
);
let (new_scale, new_angle, new_translation, new_shear) = compute_scale_angle_translation_shear(origional_transform);
let new_transform = DAffine2::from_scale_angle_translation(new_scale, new_angle, new_translation) * DAffine2::from_cols_array(&[1., new_shear.y, new_shear.x, 1., 0., 0.]);
assert!(
new_transform.abs_diff_eq(origional_transform, 1e-10),
"origional_transform {} new_transform {} / scale {} new_scale {} / angle {} new_angle {} / shear {} / new_shear {}",
origional_transform,
new_transform,
scale,
new_scale,
angle,
new_angle,
shear,
new_shear,
);
}
}
}
}
}
/// Expand a bounds to avoid div zero errors
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
let bounds_size = bounds_max - bounds_min;
if bounds_size.x < 1e-10 {
bounds_max.x = bounds_min.x + 1.;
}
if bounds_size.y < 1e-10 {
bounds_max.y = bounds_min.y + 1.;
}
[bounds_min, bounds_max]
}
/// Returns corners of all subpaths
fn subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
subpaths
.iter()
.filter_map(|subpath| subpath.bounding_box())
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
.unwrap_or_default()
}
/// Returns corners of all subpaths (but expanded to avoid div zero errors)
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [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 mirror_angle_groups: &'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_mirror(&mut self, id: ManipulatorGroupId, mirror_angle: bool) {
if !mirror_angle {
self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id);
} else if !self.mirror_angle_groups.contains(&id) {
self.mirror_angle_groups.push(id);
}
}
fn toggle_mirror(&mut self, id: ManipulatorGroupId) {
if self.mirror_angle_groups.contains(&id) {
self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id);
} else {
self.mirror_angle_groups.push(id);
}
}
fn set_position(&mut self, point: ManipulatorPointId, position: DVec2) {
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.mirror_angle_groups.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::SetManipulatorHandleMirroring { id, mirror_angle } => self.set_mirror(id, mirror_angle),
VectorDataModification::SetManipulatorPosition { point, position } => self.set_position(point, position),
VectorDataModification::ToggleManipulatorHandleMirroring { id } => self.toggle_mirror(id),
}
}
}
@@ -5,3 +5,11 @@ mod node_graph_message_handler;
pub use node_graph_message::{NodeGraphMessage, NodeGraphMessageDiscriminant};
#[doc(inline)]
pub use node_graph_message_handler::*;
mod graph_operation_message;
mod graph_operation_message_handler;
#[doc(inline)]
pub use graph_operation_message::*;
#[doc(inline)]
pub use graph_operation_message_handler::*;
@@ -46,7 +46,7 @@ impl FrontendGraphDataType {
TaggedValue::Image(_) => Self::Raster,
TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Color(_) => Self::Color,
TaggedValue::RcSubpath(_) | TaggedValue::Subpath(_) | TaggedValue::VectorData(_) => Self::Subpath,
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath,
_ => Self::General,
}
}
@@ -238,23 +238,7 @@ impl NodeGraphMessageHandler {
// If empty, show all nodes in the network starting with the output
if self.selected_nodes.is_empty() {
let mut stack = network.outputs.iter().map(|output| output.node_id).collect::<Vec<_>>();
let mut nodes = Vec::new();
while let Some(node_id) = stack.pop() {
let Some(document_node) = network.nodes.get(&node_id) else {
continue;
};
stack.extend(
document_node
.inputs
.iter()
.take(1) // Only show the primary input
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
);
nodes.push((document_node, node_id));
}
for &(document_node, node_id) in nodes.iter().rev() {
for (document_node, node_id) in network.primary_flow().collect::<Vec<_>>().into_iter().rev() {
sections.push(node_properties::generate_node_properties(document_node, node_id, context));
}
}
@@ -668,25 +668,25 @@ fn static_nodes() -> Vec<DocumentNodeType> {
DocumentNodeType {
name: "Path Generator",
category: "Vector",
identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator"),
inputs: vec![DocumentInputType {
name: "Path Data",
data_type: FrontendGraphDataType::Subpath,
default: NodeInput::value(TaggedValue::Subpath(bezier_rs::Subpath::new(Vec::new(), false)), false),
}],
identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator<_>"),
inputs: vec![
DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false),
DocumentInputType::value("Mirror", TaggedValue::ManipulatorGroupIds(vec![]), false),
],
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::no_properties,
},
DocumentNodeType {
name: "Transform",
category: "Vector",
identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _>"),
identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _>"),
inputs: vec![
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
DocumentInputType::value("Translation", TaggedValue::DVec2(DVec2::ZERO), false),
DocumentInputType::value("Rotation", TaggedValue::F64(0.), false),
DocumentInputType::value("Scale", TaggedValue::DVec2(DVec2::ONE), false),
DocumentInputType::value("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
DocumentInputType::value("Pivot", TaggedValue::DVec2(DVec2::splat(0.5)), false),
],
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
properties: node_properties::transform_properties,
@@ -697,8 +697,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
identifier: NodeImplementation::proto("graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>"),
inputs: vec![
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
DocumentInputType::value("Fill Type", TaggedValue::FillType(vector::style::FillType::Solid), false),
DocumentInputType::value("Solid Color", TaggedValue::Color(Color::BLACK), false),
DocumentInputType::value("Fill Type", TaggedValue::FillType(vector::style::FillType::None), false),
DocumentInputType::value("Solid Color", TaggedValue::OptionalColor(None), false),
DocumentInputType::value("Gradient Type", TaggedValue::GradientType(vector::style::GradientType::Linear), false),
DocumentInputType::value("Start", TaggedValue::DVec2(DVec2::new(0., 0.5)), false),
DocumentInputType::value("End", TaggedValue::DVec2(DVec2::new(1., 0.5)), false),
@@ -871,7 +871,7 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw
}
}
pub fn new_vector_network(subpath: bezier_rs::Subpath<uuid::ManipulatorGroupId>) -> NodeNetwork {
pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGroupId>>) -> NodeNetwork {
let input = resolve_document_node_type("Input").expect("Input node does not exist");
let path_generator = resolve_document_node_type("Path Generator").expect("Path Generator node does not exist");
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
@@ -891,7 +891,7 @@ pub fn new_vector_network(subpath: bezier_rs::Subpath<uuid::ManipulatorGroupId>)
outputs: vec![NodeOutput::new(5, 0)],
nodes: [
input.to_document_node_default_inputs([], next_pos()),
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpath(subpath), false))], next_pos()),
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], next_pos()),
transform.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], next_pos()),
fill.to_document_node_default_inputs([Some(NodeInput::node(2, 0))], next_pos()),
stroke.to_document_node_default_inputs([Some(NodeInput::node(3, 0))], next_pos()),
@@ -398,18 +398,24 @@ fn gradient_positions(rows: &mut Vec<LayoutGroup>, document_node: &DocumentNode,
fn color_widget(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, color_props: ColorInput, blank_assist: bool) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
if let NodeInput::Value {
tagged_value: TaggedValue::Color(x),
exposed: false,
} = document_node.inputs[index]
{
widgets.extend_from_slice(&[
WidgetHolder::unrelated_separator(),
color_props
.value(Some(x as Color))
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.unwrap()), node_id, index))
.widget_holder(),
])
if let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] {
if let &TaggedValue::Color(x) = tagged_value {
widgets.extend_from_slice(&[
WidgetHolder::unrelated_separator(),
color_props
.value(Some(x as Color))
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.unwrap()), node_id, index))
.widget_holder(),
])
} else if let &TaggedValue::OptionalColor(x) = tagged_value {
widgets.extend_from_slice(&[
WidgetHolder::unrelated_separator(),
color_props
.value(x)
.on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value), node_id, index))
.widget_holder(),
])
}
}
LayoutGroup::Row { widgets }
}
@@ -574,7 +580,23 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
let translation = {
let index = 1;
let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, true);
let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, false);
let pivot_index = 5;
if let NodeInput::Value {
tagged_value: TaggedValue::DVec2(pivot),
exposed: false,
} = document_node.inputs[pivot_index]
{
widgets.push(WidgetHolder::unrelated_separator());
widgets.push(
PivotAssist::new(pivot.into())
.on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into())
.widget_holder(),
);
} else {
add_blank_assist(&mut widgets);
}
if let NodeInput::Value {
tagged_value: TaggedValue::DVec2(vec2),
@@ -1044,7 +1066,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
widgets.extend_from_slice(&[
WidgetHolder::unrelated_separator(),
DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).widget_holder(),
DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).tooltip("When selecing a layer in a folder, shallow select will select the parent folder whereas deep select will select the layer. Double clicking in shallow select mode will select the layer.").widget_holder(),
]);
}
LayoutGroup::Row { widgets }.with_tooltip("Algorithm used to generate the image during each sampling step")
@@ -1289,7 +1311,8 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
let mut widgets = Vec::new();
let gradient = fill_type == Some(graphene_core::vector::style::FillType::Gradient);
let solid = fill_type == Some(graphene_core::vector::style::FillType::Solid);
if fill_type.is_none() || solid {
let empty = fill_type == Some(graphene_core::vector::style::FillType::None);
if fill_type.is_none() || solid || empty {
let solid_color = color_widget(document_node, node_id, solid_color_index, "Color", ColorInput::default(), true);
widgets.push(solid_color);
}
@@ -1300,7 +1323,7 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
gradient_positions(&mut widgets, document_node, "Gradient Positions", node_id, positions_index);
}
if gradient || solid {
if gradient || solid || empty {
let new_fill_type = if gradient { FillType::Solid } else { FillType::Gradient };
let switch_button = TextButton::new(if gradient { "Use Solid Color" } else { "Use Gradient" })
.tooltip(if gradient {
@@ -129,12 +129,12 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
self.create_document_operation(Operation::SetTextContent { path, new_text }, true, responses);
}
SetPivot { new_position } => {
let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let (layer, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
let position: Option<glam::DVec2> = new_position.into();
let pivot = position.unwrap().into();
let pivot = position.unwrap();
responses.push_back(DocumentMessage::StartTransaction.into());
responses.push_back(Operation::SetPivot { layer_path, pivot }.into());
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
}
CheckSelectedWasUpdated { path } => {
if self.matches_selected(&path) {
@@ -4,7 +4,6 @@ use crate::messages::prelude::*;
use document_legacy::document::Document;
use document_legacy::layers::style::RenderData;
use document_legacy::LayerId;
use document_legacy::Operation as DocumentOperation;
use glam::{DAffine2, DVec2};
use std::collections::{HashMap, VecDeque};
@@ -152,6 +151,7 @@ impl TransformOperation {
};
selected.update_transforms(transformation);
self.hints(snapping, selected.responses);
}
}
@@ -176,6 +176,32 @@ impl TransformOperation {
self.apply_transform_operation(selected, snapping);
}
pub fn hints(&self, snapping: bool, responses: &mut VecDeque<Message>) {
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
let mut hints = Vec::new();
let value_str = match self {
TransformOperation::None => String::new(),
TransformOperation::Grabbing(translation) => format!("Translate X: {} Y: {}", translation.to_dvec().x, translation.to_dvec().y),
TransformOperation::Rotating(rotation) => format!("Rotate {}°", rotation.to_f64(snapping)),
TransformOperation::Scaling(scale) => format!("Scale X: {} Y: {}", scale.to_dvec(snapping).x, scale.to_dvec(snapping).y),
};
hints.push(HintInfo::label(value_str));
hints.push(HintInfo::keys([Key::Shift], "Precision Mode"));
if matches!(self, TransformOperation::Rotating(_) | TransformOperation::Scaling(_)) {
hints.push(HintInfo::keys([Key::Control], "Snap"));
}
if matches!(self, TransformOperation::Grabbing(_) | TransformOperation::Scaling(_)) {
hints.push(HintInfo::keys([Key::KeyX], "X Axis"));
hints.push(HintInfo::keys([Key::KeyY], "Y Axis"));
}
let hint_data = HintData(vec![HintGroup(hints)]);
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}
}
pub struct Selected<'a> {
@@ -239,13 +265,11 @@ impl<'a> Selected<'a> {
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
let new = to.inverse() * transformation * to * original_layer_transforms;
self.responses.push_back(
DocumentOperation::SetLayerTransform {
path: layer_path.to_vec(),
transform: new.to_cols_array(),
}
.into(),
);
self.responses.add(GraphOperationMessage::TransformSet {
layer: layer_path.to_vec(),
transform: new,
transform_in: TransformIn::Local,
});
}
self.responses.push_back(BroadcastEvent::DocumentIsDirty.into());
@@ -253,16 +277,14 @@ impl<'a> Selected<'a> {
}
pub fn revert_operation(&mut self) {
for path in self.selected {
if let Some(transform) = self.original_transforms.get(*path) {
for layer in self.selected {
if let Some(&transform) = self.original_transforms.get(*layer) {
// Push front to stop document switching before sending the transform
self.responses.push_front(
DocumentOperation::SetLayerTransform {
path: path.to_vec(),
transform: transform.to_cols_array(),
}
.into(),
);
self.responses.add(GraphOperationMessage::TransformSet {
layer: layer.to_vec(),
transform,
transform_in: TransformIn::Local,
});
}
}
}