mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-30 17:57:21 +00:00
Migrate the Select tool to the document graph (#1433)
* function for accessing document metadata * Better select tool * Fix render * Fix transforms * Fix loading saved documents * Populate graph UI when loading autosave * Multiple transform nodes * Fix deep select * Graph tooltips * Fix flip axis icon * Show disabled widgets * Stop select tool from selecting artboards * Disable (not hide) the pivot widget; remove Deep/Shallow select for now * Code review changes * Fix pivot position with select tool * Fix incorrectly selected layers when shift clicking --------- Co-authored-by: Dennis Kobert <dennis@kobert.dev> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
e1cdb2242d
commit
5827e989dc
46 changed files with 1041 additions and 1215 deletions
|
@ -3,15 +3,17 @@ use graphene_core::renderer::ClickTarget;
|
|||
use std::collections::HashMap;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
|
||||
|
||||
use graphene_core::renderer::Quad;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentMetadata {
|
||||
transforms: HashMap<LayerNodeIdentifier, DAffine2>,
|
||||
upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
selected_nodes: Vec<NodeId>,
|
||||
/// Transform from document space to viewport space.
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
|
@ -20,13 +22,17 @@ impl Default for DocumentMetadata {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
transforms: HashMap::new(),
|
||||
upstream_transforms: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
|
||||
selected_nodes: Vec::new(),
|
||||
document_to_viewport: DAffine2::IDENTITY,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct SelectionChanged;
|
||||
|
||||
// layer iters
|
||||
impl DocumentMetadata {
|
||||
/// Get the root layer from the document
|
||||
pub const fn root(&self) -> LayerNodeIdentifier {
|
||||
|
@ -38,7 +44,7 @@ impl DocumentMetadata {
|
|||
}
|
||||
|
||||
pub fn selected_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.all_layers()
|
||||
self.all_layers().filter(|layer| self.selected_nodes.contains(&layer.to_node()))
|
||||
}
|
||||
|
||||
pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
|
@ -46,7 +52,19 @@ impl DocumentMetadata {
|
|||
}
|
||||
|
||||
pub fn selected_visible_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
self.all_layers()
|
||||
self.selected_layers()
|
||||
}
|
||||
|
||||
pub fn selected_nodes(&self) -> core::slice::Iter<'_, NodeId> {
|
||||
self.selected_nodes.iter()
|
||||
}
|
||||
|
||||
pub fn selected_nodes_ref(&self) -> &Vec<NodeId> {
|
||||
&self.selected_nodes
|
||||
}
|
||||
|
||||
pub fn has_selected_nodes(&self) -> bool {
|
||||
!self.selected_nodes.is_empty()
|
||||
}
|
||||
|
||||
/// Access the [`NodeRelations`] of a layer
|
||||
|
@ -59,35 +77,125 @@ impl DocumentMetadata {
|
|||
self.structure.entry(node_identifier).or_default()
|
||||
}
|
||||
|
||||
/// Update the cached transforms of the layers
|
||||
pub fn update_transforms(&mut self, new_transforms: HashMap<LayerNodeIdentifier, DAffine2>) {
|
||||
self.transforms = new_transforms;
|
||||
pub fn shallowest_unique_layers<'a>(&self, layers: impl Iterator<Item = &'a LayerNodeIdentifier>) -> Vec<Vec<LayerNodeIdentifier>> {
|
||||
let mut sorted_layers = layers
|
||||
.map(|layer| {
|
||||
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
|
||||
layer_path.reverse();
|
||||
layer_path
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
sorted_layers.sort();
|
||||
// Sorting here creates groups of similar UUID paths
|
||||
sorted_layers.dedup_by(|a, b| a.starts_with(b));
|
||||
sorted_layers
|
||||
}
|
||||
}
|
||||
|
||||
// selected layer modifications
|
||||
impl DocumentMetadata {
|
||||
#[must_use]
|
||||
pub fn retain_selected_nodes(&mut self, f: impl FnMut(&NodeId) -> bool) -> SelectionChanged {
|
||||
self.selected_nodes.retain(f);
|
||||
SelectionChanged
|
||||
}
|
||||
#[must_use]
|
||||
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) -> SelectionChanged {
|
||||
self.selected_nodes = new;
|
||||
SelectionChanged
|
||||
}
|
||||
#[must_use]
|
||||
pub fn add_selected_nodes(&mut self, iter: impl IntoIterator<Item = NodeId>) -> SelectionChanged {
|
||||
self.selected_nodes.extend(iter);
|
||||
SelectionChanged
|
||||
}
|
||||
#[must_use]
|
||||
pub fn clear_selected_nodes(&mut self) -> SelectionChanged {
|
||||
self.set_selected_nodes(Vec::new())
|
||||
}
|
||||
|
||||
/// Loads the structure of layer nodes from a node graph.
|
||||
pub fn load_structure(&mut self, graph: &NodeNetwork) {
|
||||
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]);
|
||||
|
||||
let id = graph.outputs[0].node_id;
|
||||
let Some(output_node) = graph.nodes.get(&id) else {
|
||||
return;
|
||||
};
|
||||
let Some((layer_node, node_id)) = first_child_layer(graph, output_node, id) else {
|
||||
return;
|
||||
};
|
||||
let parent = LayerNodeIdentifier::ROOT;
|
||||
let mut stack = vec![(layer_node, node_id, parent)];
|
||||
while let Some((node, id, parent)) = stack.pop() {
|
||||
let mut current = Some((node, id));
|
||||
while let Some(&(current_node, current_id)) = current.as_ref() {
|
||||
let current_identifier = LayerNodeIdentifier::new_unchecked(current_id);
|
||||
if !self.structure.contains_key(¤t_identifier) {
|
||||
parent.push_child(self, current_identifier);
|
||||
|
||||
if let Some((child_node, child_id)) = first_child_layer(graph, current_node, current_id) {
|
||||
stack.push((child_node, child_id, current_identifier));
|
||||
}
|
||||
}
|
||||
|
||||
current = sibling_below(graph, current_node, current_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn first_child_layer<'a>(graph: &'a NodeNetwork, node: &DocumentNode, id: NodeId) -> Option<(&'a DocumentNode, NodeId)> {
|
||||
graph.primary_flow_from_opt(Some(node.inputs[0].as_node()?)).find(|(node, _)| node.name == "Layer")
|
||||
}
|
||||
fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode, id: NodeId) -> Option<(&'a DocumentNode, NodeId)> {
|
||||
node.inputs[7].as_node().and_then(|id| graph.nodes.get(&id).filter(|node| node.name == "Layer").map(|node| (node, id)))
|
||||
}
|
||||
|
||||
// transforms
|
||||
impl DocumentMetadata {
|
||||
/// Update the cached transforms of the layers
|
||||
pub fn update_transforms(&mut self, new_transforms: HashMap<LayerNodeIdentifier, DAffine2>, new_upstream_transforms: HashMap<NodeId, DAffine2>) {
|
||||
self.transforms = new_transforms;
|
||||
self.upstream_transforms = new_upstream_transforms;
|
||||
}
|
||||
|
||||
/// Access the cached transformation to document space from layer space
|
||||
pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
self.transforms.get(&layer).copied().unwrap_or_else(|| {
|
||||
warn!("Tried to access transform of bad layer {layer:?}");
|
||||
DAffine2::IDENTITY
|
||||
})
|
||||
}
|
||||
|
||||
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
self.document_to_viewport * self.transform_to_document(layer)
|
||||
}
|
||||
|
||||
pub fn upstream_transform(&self, node_id: NodeId) -> DAffine2 {
|
||||
self.upstream_transforms.get(&node_id).copied().unwrap_or(DAffine2::IDENTITY)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||
network.primary_flow_from_opt(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard")
|
||||
}
|
||||
|
||||
// click targets
|
||||
impl DocumentMetadata {
|
||||
/// Update the cached click targets of the layers
|
||||
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
|
||||
self.click_targets = new_click_targets;
|
||||
}
|
||||
|
||||
/// Access the cached transformation from document space to layer space
|
||||
pub fn transform_from_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
self.transforms.get(&layer).copied().unwrap_or_else(|| {
|
||||
warn!("Tried to access transform of bad layer");
|
||||
DAffine2::IDENTITY
|
||||
})
|
||||
}
|
||||
|
||||
pub fn transform_from_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
self.document_to_viewport * self.transform_from_document(layer)
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space quad
|
||||
pub fn intersect_quad(&self, viewport_quad: Quad) -> Option<LayerNodeIdentifier> {
|
||||
pub fn intersect_quad<'a>(&'a self, viewport_quad: Quad, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
let document_quad = self.document_to_viewport.inverse() * viewport_quad;
|
||||
self.root()
|
||||
.decendants(self)
|
||||
.filter(|&layer| !is_artboard(layer, network))
|
||||
.filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets)))
|
||||
.find(|(layer, target)| target.iter().any(|target| target.intersect_rectangle(document_quad, self.transform_from_document(*layer))))
|
||||
.filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.transform_to_document(*layer))))
|
||||
.map(|(layer, _)| layer)
|
||||
}
|
||||
|
||||
|
@ -97,13 +205,13 @@ impl DocumentMetadata {
|
|||
self.root()
|
||||
.decendants(self)
|
||||
.filter_map(|layer| self.click_targets.get(&layer).map(|targets| (layer, targets)))
|
||||
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_from_document(*layer))))
|
||||
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.transform_to_document(*layer))))
|
||||
.map(|(layer, _)| layer)
|
||||
}
|
||||
|
||||
/// Find the layer that has been clicked on from a viewport space location
|
||||
pub fn click(&self, viewport_location: DVec2) -> Option<LayerNodeIdentifier> {
|
||||
self.click_xray(viewport_location).next()
|
||||
pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
|
||||
self.click_xray(viewport_location).filter(|&layer| !is_artboard(layer, network)).next()
|
||||
}
|
||||
|
||||
/// Get the bounding box of the click target of the specified layer in the specified transform space
|
||||
|
@ -115,14 +223,47 @@ impl DocumentMetadata {
|
|||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculate the corners of the bounding box but with a nonzero size.
|
||||
///
|
||||
/// If the layer bounds are `0` in either axis then they are changed to be `1`.
|
||||
pub fn nonzero_bounding_box(&self, layer: LayerNodeIdentifier) -> [DVec2; 2] {
|
||||
let [bounds_min, mut bounds_max] = self.bounding_box_with_transform(layer, DAffine2::IDENTITY).unwrap_or_default();
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
/// Get the bounding box of the click target of the specified layer in document space
|
||||
pub fn bounding_box_document(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(layer, self.transform_from_document(layer))
|
||||
self.bounding_box_with_transform(layer, self.transform_to_document(layer))
|
||||
}
|
||||
|
||||
/// Get the bounding box of the click target of the specified layer in viewport space
|
||||
pub fn bounding_box_viewport(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(layer, self.transform_from_viewport(layer))
|
||||
self.bounding_box_with_transform(layer, self.transform_to_viewport(layer))
|
||||
}
|
||||
|
||||
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
|
||||
self.selected_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable))
|
||||
pub fn document_bounds(&self) -> Option<[DVec2; 2]> {
|
||||
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> graphene_core::vector::Subpath {
|
||||
let Some(click_targets) = self.click_targets.get(&layer) else {
|
||||
return graphene_core::vector::Subpath::new();
|
||||
};
|
||||
graphene_core::vector::Subpath::from_bezier_rs(click_targets.iter().map(|click_target| &click_target.subpath))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +383,7 @@ impl LayerNodeIdentifier {
|
|||
pub fn decendants(self, document_metadata: &DocumentMetadata) -> DecendantsIter {
|
||||
DecendantsIter {
|
||||
front: self.first_child(document_metadata),
|
||||
back: self.last_child(document_metadata),
|
||||
back: self.last_child(document_metadata).and_then(|child| child.last_children(document_metadata).last()),
|
||||
document_metadata,
|
||||
}
|
||||
}
|
||||
|
@ -339,6 +480,17 @@ impl LayerNodeIdentifier {
|
|||
pub fn exists(&self, document_metadata: &DocumentMetadata) -> bool {
|
||||
document_metadata.get_relations(*self).is_some()
|
||||
}
|
||||
|
||||
pub fn starts_with(&self, other: Self, document_metadata: &DocumentMetadata) -> bool {
|
||||
self.ancestors(document_metadata).any(|parent| parent == other)
|
||||
}
|
||||
|
||||
pub fn child_of_root(&self, document_metadata: &DocumentMetadata) -> Self {
|
||||
self.ancestors(document_metadata)
|
||||
.filter(|&layer| layer != LayerNodeIdentifier::ROOT)
|
||||
.last()
|
||||
.expect("There should be a layer before the root")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NodeId> for LayerNodeIdentifier {
|
||||
|
@ -457,7 +609,8 @@ fn test_tree() {
|
|||
assert!(root.children(document_metadata).all(|child| child.parent(document_metadata) == Some(root)));
|
||||
LayerNodeIdentifier::new_unchecked(6).delete(document_metadata);
|
||||
LayerNodeIdentifier::new_unchecked(1).delete(document_metadata);
|
||||
LayerNodeIdentifier::new_unchecked(9).push_child(document_metadata, LayerNodeIdentifier::new_unchecked(10));
|
||||
assert_eq!(root.children(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![2, 3, 4, 5, 9]);
|
||||
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![2, 3, 4, 5, 9]);
|
||||
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::<Vec<_>>(), vec![9, 5, 4, 3, 2]);
|
||||
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).collect::<Vec<_>>(), vec![2, 3, 4, 5, 9, 10]);
|
||||
assert_eq!(root.decendants(document_metadata).map(LayerNodeIdentifier::to_node).rev().collect::<Vec<_>>(), vec![10, 9, 5, 4, 3, 2]);
|
||||
}
|
||||
|
|
|
@ -32,11 +32,12 @@ impl LayerData for ShapeLayer {
|
|||
let layer_bounds = subpath.bounding_box().unwrap_or_default();
|
||||
|
||||
let transform = self.transform(transforms, render_data.view_mode);
|
||||
let inverse = transform.inverse();
|
||||
if !inverse.is_finite() {
|
||||
if !transform.is_finite() || transform.matrix2.determinant() == 0. {
|
||||
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
|
||||
return false;
|
||||
}
|
||||
let inverse = transform.inverse();
|
||||
|
||||
subpath.apply_affine(transform);
|
||||
|
||||
let transformed_bounds = subpath.bounding_box().unwrap_or_default();
|
||||
|
|
|
@ -3,13 +3,10 @@
|
|||
extern crate log;
|
||||
|
||||
pub mod boolean_ops;
|
||||
/// Contains constant values used by this crate.
|
||||
pub mod consts;
|
||||
pub mod document;
|
||||
pub mod document_metadata;
|
||||
/// Defines errors that can occur when using this crate.
|
||||
pub mod error;
|
||||
/// Utilities for computing intersections.
|
||||
pub mod intersection;
|
||||
pub mod layers;
|
||||
pub mod operation;
|
||||
|
|
|
@ -11,9 +11,6 @@ pub enum DocumentResponse {
|
|||
FolderChanged {
|
||||
path: Vec<LayerId>,
|
||||
},
|
||||
AddSelectedLayer {
|
||||
additional_layers: Vec<Vec<LayerId>>,
|
||||
},
|
||||
CreatedLayer {
|
||||
path: Vec<LayerId>,
|
||||
is_selected: bool,
|
||||
|
@ -38,7 +35,6 @@ impl fmt::Display for DocumentResponse {
|
|||
match self {
|
||||
DocumentResponse::DocumentChanged { .. } => write!(f, "DocumentChanged"),
|
||||
DocumentResponse::FolderChanged { .. } => write!(f, "FolderChanged"),
|
||||
DocumentResponse::AddSelectedLayer { .. } => write!(f, "AddSelectedLayer"),
|
||||
DocumentResponse::CreatedLayer { .. } => write!(f, "CreatedLayer"),
|
||||
DocumentResponse::LayerChanged { .. } => write!(f, "LayerChanged"),
|
||||
DocumentResponse::DeletedLayer { .. } => write!(f, "DeleteLayer"),
|
||||
|
|
|
@ -27,7 +27,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
serde_json = { version = "1.0" }
|
||||
graphite-proc-macros = { path = "../proc-macros" }
|
||||
bezier-rs = { path = "../libraries/bezier-rs" }
|
||||
glam = { version = "0.24", features = ["serde"] }
|
||||
glam = { version = "0.24", features = ["serde", "debug-glam-assert"] }
|
||||
remain = "0.2.2"
|
||||
derivative = "2.2.0"
|
||||
once_cell = "1.13.0" # Remove when `core::cell::LazyCell` is stabilized (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use graphite_proc_macros::WidgetBuilder;
|
||||
|
||||
use derivative::*;
|
||||
|
|
|
@ -28,7 +28,6 @@ use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as Doc
|
|||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::text::Font;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
@ -123,9 +122,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
for response in document_responses {
|
||||
match &response {
|
||||
DocumentResponse::FolderChanged { path } => responses.add(FolderChanged { affected_folder_path: path.clone() }),
|
||||
DocumentResponse::AddSelectedLayer { additional_layers } => responses.add(AddSelectedLayers {
|
||||
additional_layers: additional_layers.clone(),
|
||||
}),
|
||||
DocumentResponse::DeletedLayer { path } => {
|
||||
self.layer_metadata.remove(path);
|
||||
}
|
||||
|
@ -139,20 +135,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
insert_index: *insert_index,
|
||||
reverse_index: *reverse_index,
|
||||
}),
|
||||
DocumentResponse::CreatedLayer { path, is_selected } => {
|
||||
if self.layer_metadata.contains_key(path) {
|
||||
warn!("CreatedLayer overrides existing layer metadata.");
|
||||
}
|
||||
self.layer_metadata.insert(path.clone(), LayerMetadata::new(false));
|
||||
|
||||
responses.add(LayerChanged { affected_layer_path: path.clone() });
|
||||
self.layer_range_selection_reference = path.clone();
|
||||
|
||||
if *is_selected {
|
||||
responses.add(AddSelectedLayers {
|
||||
additional_layers: vec![path.clone()],
|
||||
});
|
||||
}
|
||||
DocumentResponse::CreatedLayer { .. } => {
|
||||
unimplemented!("We should no longer be creating layers in the document and should instead be using the node graph.")
|
||||
}
|
||||
DocumentResponse::DocumentChanged => responses.add(RenderDocument),
|
||||
DocumentResponse::DeletedSelectedManipulatorPoints => {
|
||||
|
@ -176,11 +160,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
}
|
||||
#[remain::unsorted]
|
||||
Navigation(message) => {
|
||||
let document_bounds = self.document_bounds();
|
||||
let document_bounds = self.metadata().document_bounds();
|
||||
self.navigation_handler.process_message(
|
||||
message,
|
||||
responses,
|
||||
(&self.document_legacy, document_bounds, ipp, self.selected_visible_layers_bounding_box(&render_data)),
|
||||
(&self.document_legacy, document_bounds, ipp, self.metadata().selected_visible_layers_bounding_box_viewport()),
|
||||
);
|
||||
}
|
||||
#[remain::unsorted]
|
||||
|
@ -235,39 +219,38 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
}
|
||||
AlignSelectedLayers { axis, aggregate } => {
|
||||
self.backup(responses);
|
||||
let (paths, boxes): (Vec<_>, Vec<_>) = self
|
||||
.selected_layers()
|
||||
.filter_map(|path| self.document_legacy.viewport_bounding_box(path, &render_data).ok()?.map(|b| (path, b)))
|
||||
.unzip();
|
||||
|
||||
let axis = match axis {
|
||||
AlignAxis::X => DVec2::X,
|
||||
AlignAxis::Y => DVec2::Y,
|
||||
};
|
||||
let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5);
|
||||
if let Some(combined_box) = self.document_legacy.combined_viewport_bounding_box(self.selected_layers(), &render_data) {
|
||||
let aggregated = match aggregate {
|
||||
AlignAggregate::Min => combined_box[0],
|
||||
AlignAggregate::Max => combined_box[1],
|
||||
AlignAggregate::Center => lerp(&combined_box),
|
||||
AlignAggregate::Average => boxes.iter().map(|b| lerp(b)).reduce(|a, b| a + b).map(|b| b / boxes.len() as f64).unwrap(),
|
||||
let Some(combined_box) = self.metadata().selected_visible_layers_bounding_box_viewport() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let aggregated = match aggregate {
|
||||
AlignAggregate::Min => combined_box[0],
|
||||
AlignAggregate::Max => combined_box[1],
|
||||
AlignAggregate::Center => (combined_box[0] + combined_box[1]) / 2.,
|
||||
};
|
||||
for layer in self.metadata().selected_layers() {
|
||||
let Some(bbox) = self.metadata().bounding_box_viewport(layer) else {
|
||||
continue;
|
||||
};
|
||||
for (path, bbox) in paths.into_iter().zip(boxes) {
|
||||
let center = match aggregate {
|
||||
AlignAggregate::Min => bbox[0],
|
||||
AlignAggregate::Max => bbox[1],
|
||||
_ => lerp(&bbox),
|
||||
};
|
||||
let translation = (aggregated - center) * axis;
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_translation(translation),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
let center = match aggregate {
|
||||
AlignAggregate::Min => bbox[0],
|
||||
AlignAggregate::Max => bbox[1],
|
||||
_ => (bbox[0] + bbox[1]) / 2.,
|
||||
};
|
||||
let translation = (aggregated - center) * axis;
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
layer: layer.to_path(),
|
||||
transform: DAffine2::from_translation(translation),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
BackupDocument { document, layer_metadata } => self.backup_with_document(document, layer_metadata, responses),
|
||||
ClearLayerTree => {
|
||||
|
@ -374,8 +357,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
// Calculate the bounding box of the region to be exported
|
||||
let bounds = match bounds {
|
||||
ExportBounds::AllArtwork => self.all_layer_bounds(&render_data),
|
||||
ExportBounds::Selection => self.selected_visible_layers_bounding_box(&render_data),
|
||||
ExportBounds::Artboard(id) => self.document_legacy.metadata.bounding_box_document(id),
|
||||
ExportBounds::Selection => self.metadata().selected_visible_layers_bounding_box_viewport(),
|
||||
ExportBounds::Artboard(id) => self.metadata().bounding_box_document(id),
|
||||
}
|
||||
.unwrap_or_default();
|
||||
let size = bounds[1] - bounds[0];
|
||||
|
@ -405,12 +388,12 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
FlipAxis::X => DVec2::new(-1., 1.),
|
||||
FlipAxis::Y => DVec2::new(1., -1.),
|
||||
};
|
||||
if let Some([min, max]) = self.document_legacy.combined_viewport_bounding_box(self.selected_layers(), &render_data) {
|
||||
if let Some([min, max]) = self.metadata().selected_visible_layers_bounding_box_viewport() {
|
||||
let center = (max + min) / 2.;
|
||||
let bbox_trans = DAffine2::from_translation(-center);
|
||||
for path in self.selected_layers() {
|
||||
for layer in self.metadata().selected_layers() {
|
||||
responses.add(GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
layer: layer.to_path(),
|
||||
transform: DAffine2::from_scale(scale),
|
||||
transform_in: TransformIn::Scope { scope: bbox_trans },
|
||||
skip_rerender: false,
|
||||
|
@ -636,7 +619,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
RenderRulers => {
|
||||
let document_transform_scale = self.navigation_handler.snapped_scale();
|
||||
|
||||
let ruler_origin = self.document_legacy.metadata.document_to_viewport.transform_point2(DVec2::ZERO);
|
||||
let ruler_origin = self.metadata().document_to_viewport.transform_point2(DVec2::ZERO);
|
||||
let log = document_transform_scale.log2();
|
||||
let ruler_interval = if log < 0. { 100. * 2_f64.powf(-log.ceil()) } else { 100. / 2_f64.powf(log.ceil()) };
|
||||
let ruler_spacing = ruler_interval * document_transform_scale;
|
||||
|
@ -654,7 +637,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
|
||||
let viewport_size = ipp.viewport_bounds.size();
|
||||
let viewport_mid = ipp.viewport_bounds.center();
|
||||
let [bounds1, bounds2] = self.document_bounds().unwrap_or([viewport_mid; 2]);
|
||||
let [bounds1, bounds2] = self.metadata().document_bounds().unwrap_or([viewport_mid; 2]);
|
||||
let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;
|
||||
let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;
|
||||
let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING);
|
||||
|
@ -912,7 +895,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 2. });
|
||||
}
|
||||
ZoomCanvasToFitAll => {
|
||||
if let Some(bounds) = self.document_bounds() {
|
||||
if let Some(bounds) = self.metadata().document_bounds() {
|
||||
responses.add(NavigationMessage::FitViewportToBounds {
|
||||
bounds,
|
||||
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
|
||||
|
@ -964,10 +947,13 @@ impl DocumentMessageHandler {
|
|||
pub fn network(&self) -> &NodeNetwork {
|
||||
&self.document_legacy.document_network
|
||||
}
|
||||
pub fn metadata(&self) -> &document_legacy::document_metadata::DocumentMetadata {
|
||||
&self.document_legacy.metadata
|
||||
}
|
||||
|
||||
/// Remove the artwork and artboard pan/tilt/zoom to render it without the user's viewport navigation, and save it to be restored at the end
|
||||
pub(crate) fn remove_document_transform(&mut self) -> DAffine2 {
|
||||
let old_artwork_transform = self.document_legacy.metadata.document_to_viewport;
|
||||
let old_artwork_transform = self.metadata().document_to_viewport;
|
||||
self.document_legacy.metadata.document_to_viewport = DAffine2::IDENTITY;
|
||||
DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root);
|
||||
|
||||
|
@ -1074,11 +1060,6 @@ impl DocumentMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn selected_visible_layers_bounding_box(&self, render_data: &RenderData) -> Option<[DVec2; 2]> {
|
||||
let paths = self.selected_visible_layers();
|
||||
self.document_legacy.combined_viewport_bounding_box(paths, render_data)
|
||||
}
|
||||
|
||||
pub fn selected_layers(&self) -> impl Iterator<Item = &[LayerId]> {
|
||||
self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice()))
|
||||
}
|
||||
|
@ -1130,17 +1111,18 @@ impl DocumentMessageHandler {
|
|||
|
||||
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure: &mut Vec<u64>, data: &mut Vec<LayerId>, path: &mut Vec<LayerId>) {
|
||||
let mut space = 0;
|
||||
for layer_node in folder.children(&self.document_legacy.metadata) {
|
||||
for layer_node in folder.children(&self.metadata()) {
|
||||
data.push(layer_node.to_node());
|
||||
info!("Pushed child");
|
||||
space += 1;
|
||||
if layer_node.has_children(&self.document_legacy.metadata) {
|
||||
if layer_node.has_children(&self.metadata()) {
|
||||
path.push(layer_node.to_node());
|
||||
if self.layer_metadata(path).expanded {
|
||||
structure.push(space);
|
||||
self.serialize_structure(folder, structure, data, path);
|
||||
space = 0;
|
||||
}
|
||||
|
||||
// TODO: Skip if folder is not expanded.
|
||||
structure.push(space);
|
||||
self.serialize_structure(layer_node, structure, data, path);
|
||||
space = 0;
|
||||
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
|
@ -1181,7 +1163,7 @@ impl DocumentMessageHandler {
|
|||
/// ```
|
||||
pub fn serialize_root(&self) -> Vec<u64> {
|
||||
let (mut structure, mut data) = (vec![0], Vec::new());
|
||||
self.serialize_structure(self.document_legacy.metadata.root(), &mut structure, &mut data, &mut vec![]);
|
||||
self.serialize_structure(self.metadata().root(), &mut structure, &mut data, &mut vec![]);
|
||||
structure[0] = structure.len() as u64 - 1;
|
||||
structure.extend(data);
|
||||
|
||||
|
@ -1279,7 +1261,7 @@ impl DocumentMessageHandler {
|
|||
/// Replace the document with a new document save, returning the document save.
|
||||
pub fn replace_document(&mut self, DocumentSave { document, layer_metadata }: DocumentSave) -> DocumentSave {
|
||||
// Keeping the root is required if the bounds of the viewport have changed during the operation
|
||||
let old_root = self.document_legacy.metadata.document_to_viewport;
|
||||
let old_root = self.metadata().document_to_viewport;
|
||||
let document = std::mem::replace(&mut self.document_legacy, document);
|
||||
self.document_legacy.metadata.document_to_viewport = old_root;
|
||||
self.document_legacy.root.cache_dirty = true;
|
||||
|
@ -1414,10 +1396,7 @@ impl DocumentMessageHandler {
|
|||
|
||||
pub fn layer_panel_entry_from_path(&self, path: &[LayerId], render_data: &RenderData) -> Option<LayerPanelEntry> {
|
||||
let layer_metadata = self.layer_metadata(path);
|
||||
let transform = self
|
||||
.document_legacy
|
||||
.generate_transform_across_scope(path, Some(self.document_legacy.metadata.document_to_viewport.inverse()))
|
||||
.ok()?;
|
||||
let transform = self.document_legacy.generate_transform_across_scope(path, Some(self.metadata().document_to_viewport.inverse())).ok()?;
|
||||
let layer = self.document_legacy.layer(path).ok()?;
|
||||
|
||||
Some(LayerPanelEntry::new(layer_metadata, transform, layer, path.to_vec(), render_data))
|
||||
|
@ -1439,15 +1418,6 @@ impl DocumentMessageHandler {
|
|||
self.document_legacy.viewport_bounding_box(&[], render_data).ok().flatten()
|
||||
}
|
||||
|
||||
/// Calculates the document bounds used for scrolling and centring (the layer bounds or the artboard (if applicable))
|
||||
pub fn document_bounds(&self) -> Option<[DVec2; 2]> {
|
||||
self.document_legacy
|
||||
.metadata
|
||||
.all_layers()
|
||||
.filter_map(|layer| self.document_legacy.metadata.bounding_box_viewport(layer))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculate the path that new layers should be inserted to.
|
||||
/// Depends on the selected layers as well as their types (Folder/Non-Folder)
|
||||
pub fn get_path_for_new_layer(&self) -> Vec<u64> {
|
||||
|
|
|
@ -59,9 +59,9 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
|
||||
/// Updates the input of an existing node
|
||||
fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||
let document_node = self.network.nodes.get_mut(&node_id).unwrap();
|
||||
update_input(&mut document_node.inputs);
|
||||
update_input(&mut document_node.inputs, node_id, &self.document_metadata);
|
||||
}
|
||||
|
||||
pub fn insert_between(&mut self, id: NodeId, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize, shift_upstream: IVec2) -> Option<NodeId> {
|
||||
|
@ -216,7 +216,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
|
||||
/// Inserts a new node and modifies the inputs
|
||||
fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||
let output_node_id = self.layer_node.unwrap_or(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");
|
||||
|
@ -235,19 +235,20 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
return;
|
||||
};
|
||||
let mut new_document_node = node_type.to_document_node_default_inputs([Some(new_input)], metadata);
|
||||
update_input(&mut new_document_node.inputs);
|
||||
update_input(&mut new_document_node.inputs, node_id, &self.document_metadata);
|
||||
self.network.nodes.insert(node_id, new_document_node);
|
||||
}
|
||||
|
||||
/// Changes the inputs of a specific node
|
||||
fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
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.network.primary_flow_from_opt(self.layer_node).find(|(node, _)| node.name == name).map(|(_, id)| id);
|
||||
if let Some(node_id) = existing_node_id {
|
||||
self.modify_existing_node_inputs(node_id, update_input);
|
||||
} else {
|
||||
self.modify_new_node(name, update_input);
|
||||
}
|
||||
self.node_graph.nested_path.clear();
|
||||
|
||||
self.node_graph.network.clear();
|
||||
self.responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
let layer_path = self.layer.to_vec();
|
||||
|
||||
|
@ -261,8 +262,25 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Changes the inputs of a all of the existing instances of a node name
|
||||
fn modify_all_node_inputs(&mut self, name: &'static str, skip_rerender: bool, mut update_input: impl FnMut(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||
let existing_nodes: Vec<_> = self.network.primary_flow_from_opt(self.layer_node).filter(|(node, _)| node.name == name).map(|(_, id)| id).collect();
|
||||
for existing_node_id in existing_nodes {
|
||||
self.modify_existing_node_inputs(existing_node_id, &mut update_input);
|
||||
}
|
||||
|
||||
self.responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
let layer_path = self.layer.to_vec();
|
||||
|
||||
if !skip_rerender {
|
||||
self.responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
} else {
|
||||
self.responses.add(DocumentMessage::FrameClear);
|
||||
}
|
||||
}
|
||||
|
||||
fn fill_set(&mut self, fill: Fill) {
|
||||
self.modify_inputs("Fill", false, |inputs| {
|
||||
self.modify_inputs("Fill", false, |inputs, _node_id, _metadata| {
|
||||
let fill_type = match fill {
|
||||
Fill::None => FillType::None,
|
||||
Fill::Solid(_) => FillType::Solid,
|
||||
|
@ -284,7 +302,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
|
||||
fn stroke_set(&mut self, stroke: Stroke) {
|
||||
self.modify_inputs("Stroke", false, |inputs| {
|
||||
self.modify_inputs("Stroke", false, |inputs, _node_id, _metadata| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::OptionalColor(stroke.color), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F32(stroke.weight as f32), false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::VecF32(stroke.dash_lengths), false);
|
||||
|
@ -296,49 +314,45 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
|
||||
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| {
|
||||
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(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
||||
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;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
}
|
||||
|
||||
fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option<DAffine2>, bounds: LayerBounds, skip_rerender: bool) {
|
||||
self.modify_inputs("Transform", skip_rerender, |inputs| {
|
||||
let current_transform_node = transform_utils::get_current_transform(inputs);
|
||||
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
|
||||
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(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
||||
let pivot = DAffine2::from_translation(upstream_transform.transform_point2(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs))));
|
||||
|
||||
if let Some(current_transform) = current_transform.filter(|transform| transform.inverse().is_finite() && current_transform_node.inverse().is_finite()) {
|
||||
// this_transform * upstream_transforms = current_transform
|
||||
// So this_transform.inverse() * current_transform = upstream_transforms
|
||||
let upstream_transform = (pivot * current_transform_node * pivot.inverse()).inverse() * current_transform;
|
||||
// desired_final_transform = this_transform * upstream_transform
|
||||
// So this_transform = desired_final_transform * upstream_transform.inverse()
|
||||
if let Some(current_transform) = current_transform.filter(|transform| transform.matrix2.determinant() != 0. && upstream_transform.matrix2.determinant() != 0.) {
|
||||
transform = transform * upstream_transform.inverse();
|
||||
}
|
||||
|
||||
let transform = pivot.inverse() * to.inverse() * transform * pivot;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
let final_transform = pivot.inverse() * to.inverse() * transform * pivot;
|
||||
transform_utils::update_transform(inputs, final_transform);
|
||||
});
|
||||
}
|
||||
|
||||
fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) {
|
||||
self.modify_inputs("Transform", false, |inputs| {
|
||||
self.modify_inputs("Transform", false, |inputs, node_id, metadata| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let old_pivot_transform = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalized_pivot(inputs)));
|
||||
let new_pivot_transform = DAffine2::from_translation(bounds.local_pivot(new_pivot));
|
||||
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);
|
||||
inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false);
|
||||
|
@ -346,14 +360,15 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
|
||||
fn update_bounds(&mut self, [old_bounds_min, old_bounds_max]: [DVec2; 2], [new_bounds_min, new_bounds_max]: [DVec2; 2]) {
|
||||
self.modify_inputs("Transform", false, |inputs| {
|
||||
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(new_layerspace_pivot);
|
||||
let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot);
|
||||
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);
|
||||
|
@ -369,7 +384,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
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("Shape", false, |inputs| {
|
||||
self.modify_inputs("Shape", false, |inputs, _node_id, _metadata| {
|
||||
let [subpaths, mirror_angle_groups] = inputs.as_mut_slice() else {
|
||||
panic!("Shape does not have subpath and mirror angle inputs");
|
||||
};
|
||||
|
@ -400,13 +415,13 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
|
||||
fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
|
||||
self.modify_inputs("Brush", false, |inputs| {
|
||||
self.modify_inputs("Brush", false, |inputs, _node_id, _metadata| {
|
||||
inputs[2] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
|
||||
});
|
||||
}
|
||||
|
||||
fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
|
||||
self.modify_inputs("Artboard", false, |inputs| {
|
||||
self.modify_inputs("Artboard", false, |inputs, _node_id, _metadata| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::IVec2(location), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::IVec2(dimensions), false);
|
||||
});
|
||||
|
@ -443,9 +458,10 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
for node_id in delete_nodes {
|
||||
self.network.nodes.remove(&node_id);
|
||||
for node_id in &delete_nodes {
|
||||
self.network.nodes.remove(node_id);
|
||||
}
|
||||
self.responses.add(self.document_metadata.retain_selected_nodes(|id| !delete_nodes.contains(id)));
|
||||
|
||||
self.responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
|
||||
|
@ -503,29 +519,18 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
|||
skip_rerender,
|
||||
} => {
|
||||
let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
|
||||
let current_transform = document.layer(&layer).ok().map(|layer| layer.transform);
|
||||
let current_transform = Some(document.metadata.transform_to_viewport(LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network)));
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
|
||||
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
|
||||
}
|
||||
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(&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(&layer, document, node_graph, responses) {
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
|||
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use graph_craft::document::{value::TaggedValue, NodeInput};
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
@ -52,18 +53,12 @@ pub struct LayerBounds {
|
|||
|
||||
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_layer().ok())
|
||||
.and_then(|frame| frame.as_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();
|
||||
pub fn new(document: &Document, layer: &[u64]) -> Self {
|
||||
let layer = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
|
||||
Self {
|
||||
bounds,
|
||||
bounds_transform,
|
||||
layer_transform,
|
||||
bounds: document.metadata.nonzero_bounding_box(layer),
|
||||
bounds_transform: DAffine2::IDENTITY,
|
||||
layer_transform: document.metadata.transform_to_document(layer),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,7 +121,7 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 {
|
|||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(pivot),
|
||||
..
|
||||
} = inputs[4]
|
||||
} = inputs[5]
|
||||
{
|
||||
pivot
|
||||
} else {
|
||||
|
|
|
@ -4,11 +4,15 @@ use document_legacy::LayerId;
|
|||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, NodeGraph)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NodeGraphMessage {
|
||||
// Messages
|
||||
Init,
|
||||
AddSelectNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
SelectedNodesUpdated,
|
||||
CloseNodeGraph,
|
||||
ConnectNodesByLink {
|
||||
output_node: u64,
|
||||
|
@ -63,9 +67,6 @@ pub enum NodeGraphMessage {
|
|||
serialized_nodes: String,
|
||||
},
|
||||
RunDocumentGraph,
|
||||
SelectNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
SendGraph {
|
||||
should_rerender: bool,
|
||||
},
|
||||
|
@ -85,6 +86,9 @@ pub enum NodeGraphMessage {
|
|||
input_index: usize,
|
||||
value: TaggedValue,
|
||||
},
|
||||
SetSelectNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
ShiftNode {
|
||||
node_id: NodeId,
|
||||
},
|
||||
|
|
|
@ -119,39 +119,19 @@ impl FrontendNodeType {
|
|||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct NodeGraphMessageHandler {
|
||||
pub layer_path: Option<Vec<LayerId>>,
|
||||
pub nested_path: Vec<NodeId>,
|
||||
pub selected_nodes: Vec<NodeId>,
|
||||
pub network: Vec<NodeId>,
|
||||
has_selection: bool,
|
||||
#[serde(skip)]
|
||||
pub widgets: [LayoutGroup; 2],
|
||||
}
|
||||
|
||||
impl Into<Message> for document_legacy::document_metadata::SelectionChanged {
|
||||
fn into(self) -> Message {
|
||||
BroadcastMessage::TriggerEvent(BroadcastEvent::SelectionChanged).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeGraphMessageHandler {
|
||||
fn get_root_network<'a>(&self, document: &'a Document) -> &'a graph_craft::document::NodeNetwork {
|
||||
self.layer_path
|
||||
.as_ref()
|
||||
.and_then(|path| document.root.child(path))
|
||||
.and_then(|layer| layer.as_layer_network().ok())
|
||||
.unwrap_or(&document.document_network)
|
||||
}
|
||||
|
||||
fn get_root_network_mut<'a>(&self, document: &'a mut Document) -> &'a mut graph_craft::document::NodeNetwork {
|
||||
self.layer_path
|
||||
.as_ref()
|
||||
.and_then(|path| document.root.child_mut(path))
|
||||
.and_then(|layer| layer.as_layer_network_mut().ok())
|
||||
.unwrap_or(&mut document.document_network)
|
||||
}
|
||||
|
||||
/// Get the active graph_craft NodeNetwork struct
|
||||
fn get_active_network<'a>(&self, document: &'a Document) -> Option<&'a graph_craft::document::NodeNetwork> {
|
||||
self.get_root_network(document).nested_network(&self.nested_path)
|
||||
}
|
||||
|
||||
/// Get the active graph_craft NodeNetwork struct
|
||||
fn get_active_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> {
|
||||
self.get_root_network_mut(document).nested_network_mut(&self.nested_path)
|
||||
}
|
||||
|
||||
/// Send the cached layout to the frontend for the options bar at the top of the node panel
|
||||
fn send_node_bar_layout(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
|
@ -176,8 +156,8 @@ impl NodeGraphMessageHandler {
|
|||
None => ("File", "Document"),
|
||||
};
|
||||
|
||||
let mut network = Some(self.get_root_network(document));
|
||||
for node_id in &self.nested_path {
|
||||
let mut network = Some(&document.document_network);
|
||||
for node_id in &self.network {
|
||||
let node = network.and_then(|network| network.nodes.get(node_id));
|
||||
|
||||
if let Some(DocumentNode { name, .. }) = node {
|
||||
|
@ -210,16 +190,16 @@ impl NodeGraphMessageHandler {
|
|||
|
||||
/// Updates the buttons for disable and preview
|
||||
fn update_selection_action_buttons(&mut self, document: &Document, responses: &mut VecDeque<Message>) {
|
||||
if let Some(network) = self.get_active_network(document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
let mut widgets = Vec::new();
|
||||
|
||||
// Don't allow disabling input or output nodes
|
||||
let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && !network.original_outputs_contain(id));
|
||||
let mut selected_nodes = document.metadata.selected_nodes().filter(|&&id| !network.inputs.contains(&id) && !network.original_outputs_contain(id));
|
||||
|
||||
// If there is at least one other selected node then show the hide or show button
|
||||
if selected_nodes.next().is_some() {
|
||||
// Check if any of the selected nodes are disabled
|
||||
let is_hidden = self.selected_nodes.iter().any(|id| network.disabled.contains(id));
|
||||
let is_hidden = document.metadata.selected_nodes().any(|id| network.disabled.contains(id));
|
||||
|
||||
// Check if multiple nodes are selected
|
||||
let multiple_nodes = selected_nodes.next().is_some();
|
||||
|
@ -234,8 +214,8 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
|
||||
// If only one node is selected then show the preview or stop previewing button
|
||||
if self.selected_nodes.len() == 1 {
|
||||
let node_id = self.selected_nodes[0];
|
||||
let mut selected_nodes = document.metadata.selected_nodes();
|
||||
if let (Some(&node_id), None) = (selected_nodes.next(), selected_nodes.next()) {
|
||||
// Is this node the current output
|
||||
let is_output = network.outputs_contain(node_id);
|
||||
|
||||
|
@ -257,18 +237,20 @@ impl NodeGraphMessageHandler {
|
|||
/// Collate the properties panel sections for a node graph
|
||||
pub fn collate_properties(&self, context: &mut NodePropertiesContext, sections: &mut Vec<LayoutGroup>) {
|
||||
let mut network = context.network;
|
||||
for segment in &self.nested_path {
|
||||
let document = context.document;
|
||||
|
||||
for segment in &self.network {
|
||||
network = network.nodes.get(segment).and_then(|node| node.implementation.get_network()).unwrap();
|
||||
}
|
||||
|
||||
// If empty, show all nodes in the network starting with the output
|
||||
if self.selected_nodes.is_empty() {
|
||||
if !document.metadata.has_selected_nodes() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
// Show properties for all selected nodes
|
||||
for node_id in &self.selected_nodes {
|
||||
for node_id in document.metadata.selected_nodes() {
|
||||
let Some(document_node) = network.nodes.get(node_id) else {
|
||||
continue;
|
||||
};
|
||||
|
@ -359,7 +341,7 @@ impl NodeGraphMessageHandler {
|
|||
fn update_selected(&mut self, document: &mut Document, responses: &mut VecDeque<Message>) {
|
||||
self.update_selection_action_buttons(document, responses);
|
||||
responses.add(FrontendMessage::UpdateNodeGraphSelection {
|
||||
selected: self.selected_nodes.clone(),
|
||||
selected: document.metadata.selected_nodes_ref().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -428,14 +410,16 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
|
||||
/// Tries to remove a node from the network, returning true on success.
|
||||
fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId, reconnect: bool) -> bool {
|
||||
if Self::remove_references_from_network(network, node_id, reconnect) {
|
||||
network.nodes.remove(&node_id);
|
||||
self.selected_nodes.retain(|&id| id != node_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
fn remove_node(&mut self, document: &mut Document, node_id: NodeId, responses: &mut VecDeque<Message>, reconnect: bool) -> bool {
|
||||
let Some(network) = document.document_network.nested_network_mut(&self.network) else {
|
||||
return false;
|
||||
};
|
||||
if !Self::remove_references_from_network(network, node_id, reconnect) {
|
||||
return false;
|
||||
}
|
||||
network.nodes.remove(&node_id);
|
||||
responses.add(document.metadata.retain_selected_nodes(|&id| id != node_id));
|
||||
true
|
||||
}
|
||||
|
||||
/// Gets the default node input based on the node name and the input index
|
||||
|
@ -464,10 +448,26 @@ pub struct NodeGraphHandlerData<'a> {
|
|||
}
|
||||
|
||||
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
|
||||
#[remain::sorted]
|
||||
let document = data.document;
|
||||
let document_id = data.document_id;
|
||||
match message {
|
||||
// TODO: automatically remove broadcast messages.
|
||||
NodeGraphMessage::Init => {
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::SelectionChanged,
|
||||
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
|
||||
});
|
||||
document.metadata.load_structure(&document.document_network);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
}
|
||||
NodeGraphMessage::AddSelectNodes { nodes } => {
|
||||
responses.add(document.metadata.add_selected_nodes(nodes));
|
||||
}
|
||||
NodeGraphMessage::SelectedNodesUpdated => {
|
||||
self.update_selection_action_buttons(document, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
NodeGraphMessage::CloseNodeGraph => {}
|
||||
NodeGraphMessage::ConnectNodesByLink {
|
||||
output_node,
|
||||
|
@ -477,7 +477,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
} => {
|
||||
let node_id = input_node;
|
||||
|
||||
let Some(network) = self.get_active_network(data.document) else {
|
||||
let Some(network) = document.document_network.nested_network(&self.network) else {
|
||||
error!("No network");
|
||||
return;
|
||||
};
|
||||
|
@ -489,6 +489,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
error!("Failed to find actual index of connector index {input_node_connector_index} on node {input_node:#?}");
|
||||
return;
|
||||
};
|
||||
document.metadata.load_structure(&document.document_network);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
|
@ -499,13 +501,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(NodeGraphMessage::SendGraph { should_rerender });
|
||||
}
|
||||
NodeGraphMessage::Copy => {
|
||||
let Some(network) = self.get_active_network(data.document) else {
|
||||
let Some(network) = document.document_network.nested_network(&self.network) else {
|
||||
error!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
// Collect the selected nodes
|
||||
let new_ids = &self.selected_nodes.iter().copied().enumerate().map(|(new, old)| (old, new as NodeId)).collect();
|
||||
let new_ids = &document.metadata.selected_nodes().copied().enumerate().map(|(new, old)| (old, new as NodeId)).collect();
|
||||
let copied_nodes: Vec<_> = Self::copy_nodes(network, new_ids).collect();
|
||||
|
||||
// Prefix to show that this is nodes
|
||||
|
@ -540,23 +542,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
|
||||
}
|
||||
NodeGraphMessage::DeleteNode { node_id, reconnect } => {
|
||||
if let Some(network) = self.get_active_network_mut(data.document) {
|
||||
self.remove_node(network, node_id, reconnect);
|
||||
}
|
||||
self.update_selected(data.document, responses);
|
||||
self.remove_node(document, node_id, responses, reconnect);
|
||||
}
|
||||
NodeGraphMessage::DeleteSelectedNodes { reconnect } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
for node_id in self.selected_nodes.clone() {
|
||||
for node_id in document.metadata.selected_nodes().copied() {
|
||||
responses.add(NodeGraphMessage::DeleteNode { node_id, reconnect });
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
// Only generate node graph if one of the selected nodes is connected to the output
|
||||
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) {
|
||||
if document.metadata.selected_nodes().any(|&node_id| network.connected_to_output(node_id)) {
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
} else {
|
||||
|
@ -566,7 +565,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
NodeGraphMessage::DisconnectNodes { node_id, input_index } => {
|
||||
let Some(network) = self.get_active_network(data.document) else {
|
||||
let Some(network) = document.document_network.nested_network(&self.network) else {
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
@ -594,55 +593,56 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(NodeGraphMessage::SendGraph { should_rerender });
|
||||
}
|
||||
NodeGraphMessage::DoubleClickNode { node } => {
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() {
|
||||
self.nested_path.push(node);
|
||||
self.network.push(node);
|
||||
}
|
||||
}
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
}
|
||||
self.collect_nested_addresses(data.document, data.document_name, responses);
|
||||
self.update_selected(data.document, responses);
|
||||
self.collect_nested_addresses(document, data.document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
NodeGraphMessage::DuplicateSelectedNodes => {
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
let new_ids = &self.selected_nodes.iter().map(|&id| (id, crate::application::generate_uuid())).collect();
|
||||
self.selected_nodes.clear();
|
||||
let new_ids = &document.metadata.selected_nodes().map(|&id| (id, crate::application::generate_uuid())).collect();
|
||||
responses.add(document.metadata.clear_selected_nodes());
|
||||
|
||||
// Copy the selected nodes
|
||||
let copied_nodes = Self::copy_nodes(network, new_ids).collect::<Vec<_>>();
|
||||
|
||||
// Select the new nodes
|
||||
responses.add(document.metadata.add_selected_nodes(copied_nodes.iter().map(|(node_id, _)| *node_id)));
|
||||
|
||||
for (node_id, mut document_node) in copied_nodes {
|
||||
// Shift duplicated node
|
||||
document_node.metadata.position += IVec2::splat(2);
|
||||
|
||||
// Add new node to the list
|
||||
self.selected_nodes.push(node_id);
|
||||
|
||||
// Insert new node into graph
|
||||
responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
|
||||
}
|
||||
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
self.update_selected(data.document, responses);
|
||||
self.update_selected(document, responses);
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => {
|
||||
self.selected_nodes.clear();
|
||||
responses.add(document.metadata.clear_selected_nodes());
|
||||
for _ in 0..depth_of_nesting {
|
||||
self.nested_path.pop();
|
||||
self.network.pop();
|
||||
}
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
}
|
||||
self.collect_nested_addresses(data.document, data.document_name, responses);
|
||||
self.update_selected(data.document, responses);
|
||||
self.collect_nested_addresses(document, data.document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
||||
let Some(network) = self.get_active_network(data.document) else {
|
||||
let Some(network) = document.document_network.nested_network(&self.network) else {
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
@ -672,17 +672,17 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
NodeGraphMessage::InsertNode { node_id, document_node } => {
|
||||
if let Some(network) = self.get_active_network_mut(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network_mut(&self.network) {
|
||||
network.nodes.insert(node_id, document_node);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => {
|
||||
let Some(network) = self.get_active_network_mut(data.document) else {
|
||||
let Some(network) = document.document_network.nested_network_mut(&self.network) else {
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
for node_id in &self.selected_nodes {
|
||||
for node_id in document.metadata.selected_nodes() {
|
||||
if let Some(node) = network.nodes.get_mut(node_id) {
|
||||
node.metadata.position += IVec2::new(displacement_x, displacement_y)
|
||||
}
|
||||
|
@ -692,19 +692,19 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
NodeGraphMessage::OpenNodeGraph { layer_path } => {
|
||||
self.layer_path = Some(layer_path);
|
||||
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
self.selected_nodes.clear();
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
responses.add(document.metadata.clear_selected_nodes());
|
||||
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
|
||||
let node_types = document_node_types::collect_node_types();
|
||||
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
|
||||
}
|
||||
self.collect_nested_addresses(data.document, data.document_name, responses);
|
||||
self.update_selected(data.document, responses);
|
||||
self.collect_nested_addresses(document, data.document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
NodeGraphMessage::PasteNodes { serialized_nodes } => {
|
||||
let Some(network) = self.get_active_network(data.document) else {
|
||||
let Some(network) = document.document_network.nested_network(&self.network) else {
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
@ -746,22 +746,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
|
||||
let nodes = new_ids.values().copied().collect();
|
||||
responses.add(NodeGraphMessage::SelectNodes { nodes });
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes });
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||
}
|
||||
NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender {
|
||||
document_id: data.document_id,
|
||||
document_id: document_id,
|
||||
layer_path: Vec::new(),
|
||||
}),
|
||||
NodeGraphMessage::SelectNodes { nodes } => {
|
||||
self.selected_nodes = nodes;
|
||||
self.update_selection_action_buttons(data.document, responses);
|
||||
self.update_selected(data.document, responses);
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
NodeGraphMessage::SendGraph { should_rerender } => {
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
if should_rerender {
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
|
@ -772,9 +766,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
if let Some(node) = network.nodes.get(&node_id) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
|
@ -792,7 +785,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
NodeGraphMessage::SetNodeInput { node_id, input_index, input } => {
|
||||
if let Some(network) = self.get_active_network_mut(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network_mut(&self.network) {
|
||||
if let Some(node) = network.nodes.get_mut(&node_id) {
|
||||
node.inputs[input_index] = input
|
||||
}
|
||||
|
@ -809,7 +802,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
return;
|
||||
};
|
||||
|
||||
let network = self.get_root_network_mut(data.document).nested_network_mut(node_path);
|
||||
let network = document.document_network.nested_network_mut(node_path);
|
||||
|
||||
if let Some(network) = network {
|
||||
if let Some(node) = network.nodes.get_mut(node_id) {
|
||||
|
@ -824,8 +817,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::SetSelectNodes { nodes } => {
|
||||
responses.add(document.metadata.set_selected_nodes(nodes));
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
NodeGraphMessage::ShiftNode { node_id } => {
|
||||
let Some(network) = self.get_active_network_mut(data.document) else {
|
||||
let Some(network) = document.document_network.nested_network_mut(&self.network) else {
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
@ -877,22 +874,22 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(NodeGraphMessage::ToggleHiddenImpl);
|
||||
}
|
||||
NodeGraphMessage::ToggleHiddenImpl => {
|
||||
if let Some(network) = self.get_active_network_mut(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network_mut(&self.network) {
|
||||
// Check if any of the selected nodes are hidden
|
||||
if self.selected_nodes.iter().any(|id| network.disabled.contains(id)) {
|
||||
if document.metadata.selected_nodes().any(|id| network.disabled.contains(id)) {
|
||||
// Remove all selected nodes from the disabled list
|
||||
network.disabled.retain(|id| !self.selected_nodes.contains(id));
|
||||
network.disabled.retain(|id| !document.metadata.selected_nodes_ref().contains(id));
|
||||
} else {
|
||||
let original_outputs = network.original_outputs().iter().map(|output| output.node_id).collect::<Vec<_>>();
|
||||
// Add all selected nodes to the disabled list (excluding input or output nodes)
|
||||
network
|
||||
.disabled
|
||||
.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
|
||||
.extend(document.metadata.selected_nodes().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
|
||||
}
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
|
||||
// Only generate node graph if one of the selected nodes is connected to the output
|
||||
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) {
|
||||
if document.metadata.selected_nodes().any(|&node_id| network.connected_to_output(node_id)) {
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
} else {
|
||||
|
@ -900,14 +897,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
}
|
||||
self.update_selection_action_buttons(data.document, responses);
|
||||
self.update_selection_action_buttons(document, responses);
|
||||
}
|
||||
NodeGraphMessage::TogglePreview { node_id } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
|
||||
}
|
||||
NodeGraphMessage::TogglePreviewImpl { node_id } => {
|
||||
if let Some(network) = self.get_active_network_mut(data.document) {
|
||||
if let Some(network) = document.document_network.nested_network_mut(&self.network) {
|
||||
// Check if the node is not already being previewed
|
||||
if !network.outputs_contain(node_id) {
|
||||
network.previous_outputs = Some(network.previous_outputs.to_owned().unwrap_or_else(|| network.outputs.clone()));
|
||||
|
@ -919,7 +916,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
}
|
||||
self.update_selection_action_buttons(data.document, responses);
|
||||
self.update_selection_action_buttons(document, responses);
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
} else {
|
||||
|
@ -927,22 +924,23 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
}
|
||||
NodeGraphMessage::UpdateNewNodeGraph => {
|
||||
if let Some(network) = self.get_active_network(data.document) {
|
||||
self.selected_nodes.clear();
|
||||
if let Some(network) = document.document_network.nested_network(&self.network) {
|
||||
responses.add(document.metadata.clear_selected_nodes());
|
||||
|
||||
Self::send_graph(network, &self.layer_path, responses);
|
||||
|
||||
let node_types = document_node_types::collect_node_types();
|
||||
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
|
||||
}
|
||||
self.collect_nested_addresses(data.document, data.document_name, responses);
|
||||
self.update_selected(data.document, responses);
|
||||
self.collect_nested_addresses(document, data.document_name, responses);
|
||||
self.update_selected(document, responses);
|
||||
}
|
||||
}
|
||||
self.has_selection = document.metadata.has_selected_nodes();
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
if !self.selected_nodes.is_empty() {
|
||||
if self.has_selection {
|
||||
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleHidden)
|
||||
} else {
|
||||
actions!(NodeGraphMessageDiscriminant;)
|
||||
|
|
|
@ -2127,7 +2127,38 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
|||
DocumentNodeType {
|
||||
name: "Transform",
|
||||
category: "Transform",
|
||||
identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _, _>"),
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 1, 1, 1, 1, 1],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Monitor".to_string(),
|
||||
inputs: vec![NodeInput::Network(concrete!(VectorData))],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Transform".to_string(),
|
||||
inputs: vec![
|
||||
NodeInput::node(0, 0),
|
||||
NodeInput::Network(concrete!(DVec2)),
|
||||
NodeInput::Network(concrete!(f32)),
|
||||
NodeInput::Network(concrete!(DVec2)),
|
||||
NodeInput::Network(concrete!(DVec2)),
|
||||
NodeInput::Network(concrete!(DVec2)),
|
||||
],
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::transform::TransformNode<_, _, _, _, _, _>")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true),
|
||||
|
|
|
@ -1171,7 +1171,7 @@ pub fn logic_operator_properties(document_node: &DocumentNode, node_id: NodeId,
|
|||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
|
||||
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
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 {
|
||||
|
@ -1182,7 +1182,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
|||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(
|
||||
PivotAssist::new(pivot.into())
|
||||
.on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into())
|
||||
.on_update(update_value(|pivot: &PivotAssist| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
|
||||
.widget_holder(),
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -138,7 +138,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
persistent_data,
|
||||
document: artwork_document,
|
||||
responses,
|
||||
nested_path: &node_graph_message_handler.nested_path,
|
||||
nested_path: &node_graph_message_handler.network,
|
||||
layer_path: &[],
|
||||
executor,
|
||||
network: &artwork_document.document_network,
|
||||
|
|
|
@ -108,7 +108,7 @@ pub fn register_artwork_layer_properties(
|
|||
persistent_data,
|
||||
document,
|
||||
responses,
|
||||
nested_path: &node_graph_message_handler.nested_path,
|
||||
nested_path: &node_graph_message_handler.network,
|
||||
layer_path: &layer_path,
|
||||
executor,
|
||||
network: &layer.network,
|
||||
|
|
|
@ -13,24 +13,23 @@ pub struct DocumentSave {
|
|||
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum FlipAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash, specta::Type)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash, specta::Type)]
|
||||
pub enum AlignAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash, specta::Type)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, Hash, specta::Type)]
|
||||
pub enum AlignAggregate {
|
||||
Min,
|
||||
Max,
|
||||
Center,
|
||||
Average,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use document_legacy::layers::style::RenderData;
|
||||
use document_legacy::LayerId;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
@ -13,8 +15,8 @@ use std::collections::{HashMap, VecDeque};
|
|||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum OriginalTransforms {
|
||||
Layer(HashMap<Vec<LayerId>, DAffine2>),
|
||||
Path(HashMap<Vec<LayerId>, Vec<(ManipulatorPointId, DVec2)>>),
|
||||
Layer(HashMap<LayerNodeIdentifier, DAffine2>),
|
||||
Path(HashMap<LayerNodeIdentifier, Vec<(ManipulatorPointId, DVec2)>>),
|
||||
}
|
||||
impl Default for OriginalTransforms {
|
||||
fn default() -> Self {
|
||||
|
@ -28,6 +30,51 @@ impl OriginalTransforms {
|
|||
OriginalTransforms::Path(path_map) => path_map.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], responses: &'a mut VecDeque<Message>, document: &'a Document, shape_editor: Option<&'a ShapeState>, tool_type: &'a ToolType) {
|
||||
match self {
|
||||
OriginalTransforms::Layer(layer_map) => {
|
||||
for &layer in selected {
|
||||
if !layer_map.contains_key(&layer) {
|
||||
layer_map.insert(layer, document.metadata.transform_to_document(layer));
|
||||
}
|
||||
}
|
||||
}
|
||||
OriginalTransforms::Path(path_map) => {
|
||||
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) 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))
|
||||
};
|
||||
path_map.insert(layer, points.filter_map(get_manipulator_point_position).collect());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Copy)]
|
||||
|
@ -264,7 +311,7 @@ impl TransformOperation {
|
|||
}
|
||||
|
||||
pub struct Selected<'a> {
|
||||
pub selected: &'a [&'a Vec<LayerId>],
|
||||
pub selected: &'a [LayerNodeIdentifier],
|
||||
pub responses: &'a mut VecDeque<Message>,
|
||||
pub document: &'a Document,
|
||||
pub original_transforms: &'a mut OriginalTransforms,
|
||||
|
@ -277,7 +324,7 @@ impl<'a> Selected<'a> {
|
|||
pub fn new(
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
selected: &'a [&'a Vec<LayerId>],
|
||||
selected: &'a [LayerNodeIdentifier],
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a Document,
|
||||
shape_editor: Option<&'a ShapeState>,
|
||||
|
@ -288,57 +335,8 @@ impl<'a> Selected<'a> {
|
|||
*original_transforms = OriginalTransforms::Layer(HashMap::new());
|
||||
}
|
||||
|
||||
match original_transforms {
|
||||
OriginalTransforms::Layer(layer_map) => {
|
||||
for layer_path in selected {
|
||||
if !layer_map.contains_key(*layer_path) {
|
||||
if let Ok(layer) = document.layer(layer_path) {
|
||||
layer_map.insert(layer_path.to_vec(), layer.transform);
|
||||
} else {
|
||||
warn!("Didn't find a layer for {:?}", layer_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OriginalTransforms::Path(path_map) => {
|
||||
for path 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(*path) {
|
||||
continue;
|
||||
}
|
||||
let Ok(layer) = document.layer(path) else {
|
||||
warn!("Didn't find a layer for {:?}", path);
|
||||
continue;
|
||||
};
|
||||
let Some(vector_data) = layer.as_vector_data() else {
|
||||
continue;
|
||||
};
|
||||
let get_manipulator_point_position = |point_id: ManipulatorPointId| {
|
||||
vector_data
|
||||
.manipulator_from_id(point_id.group)
|
||||
.and_then(|manipulator_group| point_id.manipulator_type.get_position(manipulator_group))
|
||||
.map(|position| (point_id, position))
|
||||
};
|
||||
path_map.insert(path.to_vec(), points.filter_map(get_manipulator_point_position).collect());
|
||||
}
|
||||
}
|
||||
}
|
||||
original_transforms.update(selected, responses, document, shape_editor, tool_type);
|
||||
|
||||
Self {
|
||||
selected,
|
||||
responses,
|
||||
|
@ -351,7 +349,12 @@ impl<'a> Selected<'a> {
|
|||
}
|
||||
|
||||
pub fn mean_average_of_pivots(&mut self, render_data: &RenderData) -> DVec2 {
|
||||
let xy_summation = self.selected.iter().filter_map(|path| self.document.pivot(path, render_data)).reduce(|a, b| a + b).unwrap_or_default();
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.filter_map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
@ -360,12 +363,8 @@ impl<'a> Selected<'a> {
|
|||
let [min, max] = self
|
||||
.selected
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
let multiplied_transform = self.document.multiply_transforms(path).unwrap();
|
||||
|
||||
self.document.layer(path).unwrap().aabb_for_transform(multiplied_transform, render_data)
|
||||
})
|
||||
.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
|
||||
.filter_map(|&layer| self.document.metadata.bounding_box_viewport(layer))
|
||||
.reduce(Quad::combine_bounds)
|
||||
.unwrap_or_default();
|
||||
(min + max) / 2.
|
||||
}
|
||||
|
@ -376,27 +375,30 @@ impl<'a> Selected<'a> {
|
|||
let transformation = pivot * delta * pivot.inverse();
|
||||
|
||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||
for layer_path in Document::shallowest_unique_layers(self.selected.iter()) {
|
||||
let parent_folder_path = &layer_path[..layer_path.len() - 1];
|
||||
for layer_ancestors in self.document.metadata.shallowest_unique_layers(self.selected.iter()) {
|
||||
let layer = *layer_ancestors.last().unwrap();
|
||||
let parent = layer.parent(&self.document.metadata);
|
||||
if *self.tool_type == ToolType::Select {
|
||||
let original_layer_transforms = match self.original_transforms {
|
||||
OriginalTransforms::Layer(layer_map) => *layer_map.get(*layer_path).unwrap(),
|
||||
OriginalTransforms::Layer(layer_map) => *layer_map.get(&layer).unwrap(),
|
||||
OriginalTransforms::Path(_path_map) => {
|
||||
warn!("Found Path variant in original_transforms, returning identity transform for layer {:?}", layer_path);
|
||||
warn!("Found Path variant in original_transforms, returning identity transform for layer {:?}", layer);
|
||||
DAffine2::IDENTITY
|
||||
}
|
||||
};
|
||||
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
|
||||
let to = parent
|
||||
.map(|parent| self.document.metadata.transform_to_viewport(parent))
|
||||
.unwrap_or(self.document.metadata.document_to_viewport);
|
||||
let new = to.inverse() * transformation * to * original_layer_transforms;
|
||||
self.responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: layer_path.to_vec(),
|
||||
layer: layer.to_path(),
|
||||
transform: new,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: true,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
if *self.tool_type == ToolType::Path {
|
||||
let viewspace = self.document.generate_transform_relative_to_viewport(layer_path).ok().unwrap_or_default();
|
||||
let viewspace = self.document.metadata.transform_to_viewport(layer);
|
||||
let layerspace_rotation = viewspace.inverse() * transformation;
|
||||
|
||||
let initial_points = match self.original_transforms {
|
||||
|
@ -404,7 +406,7 @@ impl<'a> Selected<'a> {
|
|||
warn!("Found Layer variant in original_transforms when Path wanted, returning identity transform for layer");
|
||||
None
|
||||
}
|
||||
OriginalTransforms::Path(path_map) => path_map.get(*layer_path),
|
||||
OriginalTransforms::Path(path_map) => path_map.get(&layer),
|
||||
};
|
||||
|
||||
let Some(original) = initial_points else {
|
||||
|
@ -418,7 +420,7 @@ impl<'a> Selected<'a> {
|
|||
let position = new_pos_viewport;
|
||||
|
||||
self.responses.add(GraphOperationMessage::Vector {
|
||||
layer: (*layer_path).to_vec(),
|
||||
layer: layer.to_path(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
|
@ -429,23 +431,23 @@ impl<'a> Selected<'a> {
|
|||
}
|
||||
|
||||
pub fn revert_operation(&mut self) {
|
||||
for path in self.selected.iter().copied() {
|
||||
for layer in self.selected.iter().copied() {
|
||||
let original_transform = &self.original_transforms;
|
||||
match original_transform {
|
||||
OriginalTransforms::Layer(hash) => {
|
||||
let Some(matrix) = hash.get(path) else { continue };
|
||||
let Some(matrix) = hash.get(&layer) else { continue };
|
||||
self.responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: path.to_vec(),
|
||||
layer: layer.to_path(),
|
||||
transform: *matrix,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
OriginalTransforms::Path(path) => {
|
||||
for (layer_path, points) in path {
|
||||
for (layer, points) in path {
|
||||
for &(point, position) in points {
|
||||
self.responses.add(GraphOperationMessage::Vector {
|
||||
layer: (*layer_path).clone(),
|
||||
layer: layer.to_path(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
|
|
|
@ -658,10 +658,12 @@ impl PortfolioMessageHandler {
|
|||
responses.add(PortfolioMessage::GraphViewOverlay { open: self.graph_view_overlay_open });
|
||||
responses.add(ToolMessage::InitTools);
|
||||
responses.add(PropertiesPanelMessage::Init);
|
||||
responses.add(NodeGraphMessage::Init);
|
||||
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(PropertiesPanelMessage::ClearSelection);
|
||||
responses.add(PropertiesPanelMessage::UpdateSelectedDocumentProperties);
|
||||
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
||||
}
|
||||
|
||||
/// Returns an iterator over the open documents in order.
|
||||
|
|
|
@ -9,15 +9,14 @@ use graphene_core::uuid::ManipulatorGroupId;
|
|||
use graphene_core::vector::style::{FillType, Gradient};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
|
||||
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
responses.add(GraphOperationMessage::NewVectorLayer {
|
||||
id: *layer_path.last().unwrap(),
|
||||
subpaths,
|
||||
});
|
||||
let id = *layer_path.last().unwrap();
|
||||
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths });
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![id] })
|
||||
}
|
||||
|
||||
/// Creat a new bitmap layer from an [`graphene_core::raster::ImageFrame<Color>`]
|
||||
|
@ -62,6 +61,25 @@ pub fn get_subpaths(layer: LayerNodeIdentifier, document: &Document) -> Option<&
|
|||
}
|
||||
}
|
||||
|
||||
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
|
||||
pub fn get_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option<DVec2> {
|
||||
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, document)?.find_input("Transform", 5)? {
|
||||
Some(*pivot)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_document_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option<DVec2> {
|
||||
let [min, max] = document.metadata.nonzero_bounding_box(layer);
|
||||
get_pivot(layer, document).map(|pivot| document.metadata.transform_to_document(layer).transform_point2(min + (max - min) * pivot))
|
||||
}
|
||||
|
||||
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document: &Document) -> Option<DVec2> {
|
||||
let [min, max] = document.metadata.nonzero_bounding_box(layer);
|
||||
get_pivot(layer, document).map(|pivot| document.metadata.transform_to_viewport(layer).transform_point2(min + (max - min) * pivot))
|
||||
}
|
||||
|
||||
/// Get the currently mirrored handles for a particular layer from the shape node
|
||||
pub fn get_mirror_handles(layer: LayerNodeIdentifier, document: &Document) -> Option<&Vec<ManipulatorGroupId>> {
|
||||
if let TaggedValue::ManipulatorGroupIds(mirror_handles) = NodeGraphLayer::new(layer, document)?.find_input("Shape", 1)? {
|
||||
|
@ -106,6 +124,16 @@ pub fn is_artboard(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
|||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Artboard"))
|
||||
}
|
||||
|
||||
/// Is a specified layer a shape?
|
||||
pub fn is_shape_layer(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
||||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Shape"))
|
||||
}
|
||||
|
||||
/// Is a specified layer text?
|
||||
pub fn is_text_layer(layer: LayerNodeIdentifier, document: &Document) -> bool {
|
||||
NodeGraphLayer::new(layer, document).is_some_and(|layer| layer.uses_node("Text"))
|
||||
}
|
||||
|
||||
/// Convert subpaths to an iterator of manipulator groups
|
||||
pub fn get_manipulator_groups(subpaths: &[Subpath<ManipulatorGroupId>]) -> impl Iterator<Item = &bezier_rs::ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
|
||||
subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
|
||||
|
|
|
@ -51,7 +51,7 @@ impl OverlayRenderer {
|
|||
}
|
||||
|
||||
pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
let transform = document.metadata.transform_from_viewport(layer);
|
||||
let transform = document.metadata.transform_to_viewport(layer);
|
||||
|
||||
let Some(subpaths) = get_subpaths(layer, document) else {
|
||||
return;
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
use crate::application::generate_uuid;
|
||||
use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT, SELECTION_TOLERANCE};
|
||||
use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::LayerDataType;
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DAffine2;
|
||||
|
||||
/// Manages the overlay used by the select tool for outlining selected shapes and when hovering over a non selected shape.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PathOutline {
|
||||
hovered_layer_path: Option<Vec<LayerId>>,
|
||||
hovered_layer_path: Option<LayerNodeIdentifier>,
|
||||
hovered_overlay_path: Option<Vec<LayerId>>,
|
||||
selected_overlay_paths: Vec<Vec<LayerId>>,
|
||||
}
|
||||
|
@ -21,29 +19,14 @@ pub struct PathOutline {
|
|||
impl PathOutline {
|
||||
/// Creates an outline of a layer either with a pre-existing overlay or by generating a new one
|
||||
fn try_create_outline(
|
||||
document_layer_path: Vec<LayerId>,
|
||||
layer: LayerNodeIdentifier,
|
||||
overlay_path: Option<Vec<LayerId>>,
|
||||
document: &DocumentMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
render_data: &RenderData,
|
||||
) -> Option<Vec<LayerId>> {
|
||||
// Get layer data
|
||||
let document_layer = document.document_legacy.layer(&document_layer_path).ok()?;
|
||||
|
||||
// Get the subpath from the shape
|
||||
let subpath = match &document_layer.data {
|
||||
LayerDataType::Shape(shape) => Some(shape.shape.clone()),
|
||||
LayerDataType::Layer(layer) => {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
// Vector graph output
|
||||
Some(Subpath::from_bezier_rs(&vector_data.subpaths))
|
||||
} else {
|
||||
// Frame graph output
|
||||
Some(Subpath::new_rect(DVec2::new(0., 0.), DVec2::new(1., 1.)))
|
||||
}
|
||||
}
|
||||
_ => document_layer.aabb_for_transform(DAffine2::IDENTITY, render_data).map(|[p1, p2]| Subpath::new_rect(p1, p2)),
|
||||
}?;
|
||||
let subpath = document.metadata().layer_outline(layer);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Generate a new overlay layer if necessary
|
||||
let overlay = overlay_path.unwrap_or_else(|| {
|
||||
|
@ -70,7 +53,7 @@ impl PathOutline {
|
|||
responses.add(DocumentMessage::Overlays(
|
||||
(Operation::SetLayerTransform {
|
||||
path: overlay.clone(),
|
||||
transform: document.document_legacy.multiply_transforms(&document_layer_path).unwrap().to_cols_array(),
|
||||
transform: transform.to_cols_array(),
|
||||
})
|
||||
.into(),
|
||||
));
|
||||
|
@ -82,14 +65,14 @@ impl PathOutline {
|
|||
///
|
||||
/// Creates an outline, discarding the overlay on failure.
|
||||
fn create_outline(
|
||||
document_layer_path: Vec<LayerId>,
|
||||
layer: LayerNodeIdentifier,
|
||||
overlay_path: Option<Vec<LayerId>>,
|
||||
document: &DocumentMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
render_data: &RenderData,
|
||||
) -> Option<Vec<LayerId>> {
|
||||
let copied_overlay_path = overlay_path.clone();
|
||||
let result = Self::try_create_outline(document_layer_path, overlay_path, document, responses, render_data);
|
||||
let result = Self::try_create_outline(layer, overlay_path, document, responses, render_data);
|
||||
if result.is_none() {
|
||||
// Discard the overlay layer if it exists
|
||||
if let Some(overlay_path) = copied_overlay_path {
|
||||
|
@ -112,26 +95,25 @@ impl PathOutline {
|
|||
/// Performs an intersect test and generates a hovered overlay if necessary
|
||||
pub fn intersect_test_hovered(&mut self, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
||||
// Get the layer the user is hovering over
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
|
||||
let mut intersection = document.document_legacy.intersects_quad_root(quad, render_data);
|
||||
let intersection = document.metadata().click(input.mouse.position, &document.document_legacy.document_network);
|
||||
|
||||
// If the user is hovering over a layer they have not already selected, then update outline
|
||||
if let Some(path) = intersection.pop() {
|
||||
if !document.selected_visible_layers().any(|visible| visible == path.as_slice()) {
|
||||
// Updates the overlay, generating a new one if necessary
|
||||
self.hovered_overlay_path = Self::create_outline(path.clone(), self.hovered_overlay_path.take(), document, responses, render_data);
|
||||
if self.hovered_overlay_path.is_none() {
|
||||
self.clear_hovered(responses);
|
||||
}
|
||||
let Some(hovered_layer) = intersection else {
|
||||
self.clear_hovered(responses);
|
||||
return;
|
||||
};
|
||||
|
||||
self.hovered_layer_path = Some(path);
|
||||
} else {
|
||||
self.clear_hovered(responses);
|
||||
}
|
||||
} else {
|
||||
if document.metadata().selected_layers_contains(hovered_layer) {
|
||||
self.clear_hovered(responses);
|
||||
return;
|
||||
}
|
||||
|
||||
// Updates the overlay, generating a new one if necessary
|
||||
self.hovered_overlay_path = Self::create_outline(hovered_layer, self.hovered_overlay_path.take(), document, responses, render_data);
|
||||
if self.hovered_overlay_path.is_none() {
|
||||
self.clear_hovered(responses);
|
||||
}
|
||||
|
||||
self.hovered_layer_path = Some(hovered_layer);
|
||||
}
|
||||
|
||||
/// Clears overlays for the selected paths and removes references
|
||||
|
@ -143,11 +125,11 @@ impl PathOutline {
|
|||
}
|
||||
|
||||
/// Updates the selected overlays, generating or removing overlays if necessary
|
||||
pub fn update_selected<'a>(&mut self, selected: impl Iterator<Item = &'a [LayerId]>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
||||
pub fn update_selected<'a>(&mut self, selected: impl Iterator<Item = LayerNodeIdentifier>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
||||
let mut old_overlay_paths = std::mem::take(&mut self.selected_overlay_paths);
|
||||
|
||||
for document_layer_path in selected {
|
||||
if let Some(overlay_path) = Self::create_outline(document_layer_path.to_vec(), old_overlay_paths.pop(), document, responses, render_data) {
|
||||
for layer_identifier in selected {
|
||||
if let Some(overlay_path) = Self::create_outline(layer_identifier, old_overlay_paths.pop(), document, responses, render_data) {
|
||||
self.selected_overlay_paths.push(overlay_path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,15 @@ use crate::consts::{COLOR_ACCENT, PIVOT_INNER, PIVOT_OUTER, PIVOT_OUTER_OUTLINE_
|
|||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||
use document_legacy::layers::style::{self, RenderData};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::graph_modification_utils;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Pivot {
|
||||
/// Pivot between (0,0) and (1,1)
|
||||
|
@ -39,54 +42,49 @@ impl Default for Pivot {
|
|||
|
||||
impl Pivot {
|
||||
/// Calculates the transform that gets from normalized pivot to viewspace.
|
||||
fn get_layer_pivot_transform(layer_path: &[LayerId], layer: &document_legacy::layers::layer_info::Layer, document: &DocumentMessageHandler, render_data: &RenderData) -> DAffine2 {
|
||||
let [mut min, max] = layer.aabb_for_transform(DAffine2::IDENTITY, render_data).unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
let [min, max] = document.metadata().nonzero_bounding_box(layer);
|
||||
|
||||
// If the layer bounds are 0 in either axis then set them to one (to avoid div 0)
|
||||
if (max.x - min.x) < f64::EPSILON * 1000. {
|
||||
min.x = max.x - 1.;
|
||||
}
|
||||
if (max.y - min.y) < f64::EPSILON * 1000. {
|
||||
min.y = max.y - 1.;
|
||||
}
|
||||
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
let layer_transform = document.document_legacy.multiply_transforms(layer_path).unwrap_or(DAffine2::IDENTITY);
|
||||
let layer_transform = document.metadata().transform_to_viewport(layer);
|
||||
layer_transform * bounds_transform
|
||||
}
|
||||
|
||||
/// Recomputes the pivot position and transform.
|
||||
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler, render_data: &RenderData) {
|
||||
let mut layers = document.selected_visible_layers();
|
||||
if let Some(first) = layers.next() {
|
||||
// Add one because the first item is consumed above.
|
||||
let selected_layers_count = layers.count() + 1;
|
||||
|
||||
// If just one layer is selected we can use its inner transform
|
||||
if selected_layers_count == 1 {
|
||||
if let Ok(layer) = document.document_legacy.layer(first) {
|
||||
self.normalized_pivot = layer.pivot;
|
||||
self.transform_from_normalized = Self::get_layer_pivot_transform(first, layer, document, render_data);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(layer.pivot));
|
||||
}
|
||||
} else {
|
||||
// If more than one layer is selected we use the AABB with the mean of the pivots
|
||||
let xy_summation = document
|
||||
.selected_visible_layers()
|
||||
.filter_map(|path| document.document_legacy.pivot(path, render_data))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
let pivot = xy_summation / selected_layers_count as f64;
|
||||
self.pivot = Some(pivot);
|
||||
let [min, max] = document.selected_visible_layers_bounding_box(render_data).unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.normalized_pivot = (pivot - min) / (max - min);
|
||||
|
||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
}
|
||||
} else {
|
||||
let mut layers = document.metadata().selected_visible_layers();
|
||||
let Some(first) = layers.next() else {
|
||||
// If no layers are selected then we revert things back to default
|
||||
self.normalized_pivot = DVec2::splat(0.5);
|
||||
self.pivot = None;
|
||||
return;
|
||||
};
|
||||
|
||||
// Add one because the first item is consumed above.
|
||||
let selected_layers_count = layers.count() + 1;
|
||||
|
||||
// If just one layer is selected we can use its inner transform (as it accounts for rotation)
|
||||
if selected_layers_count == 1 {
|
||||
if let Some(normalized_pivot) = graph_modification_utils::get_pivot(first, &document.document_legacy) {
|
||||
self.normalized_pivot = normalized_pivot;
|
||||
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
|
||||
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
|
||||
}
|
||||
} else {
|
||||
// If more than one layer is selected we use the AABB with the mean of the pivots
|
||||
let xy_summation = document
|
||||
.metadata()
|
||||
.selected_visible_layers()
|
||||
.filter_map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.document_legacy))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
let pivot = xy_summation / selected_layers_count as f64;
|
||||
self.pivot = Some(pivot);
|
||||
let [min, max] = document.metadata().selected_visible_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.normalized_pivot = (pivot - min) / (max - min);
|
||||
|
||||
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,23 +156,21 @@ impl Pivot {
|
|||
}
|
||||
|
||||
/// Sets the viewport position of the pivot for all selected layers.
|
||||
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
if let Ok(layer) = document.document_legacy.layer(layer_path) {
|
||||
let transform = Self::get_layer_pivot_transform(layer_path, layer, document, render_data);
|
||||
let pivot = transform.inverse().transform_point2(position);
|
||||
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.
|
||||
if pivot.is_finite() {
|
||||
let layer = layer_path.to_owned();
|
||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
||||
}
|
||||
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
for layer in document.metadata().selected_visible_layers() {
|
||||
let transform = Self::get_layer_pivot_transform(layer, document);
|
||||
let pivot = transform.inverse().transform_point2(position);
|
||||
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.
|
||||
if pivot.is_finite() {
|
||||
let layer = layer.to_path();
|
||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the pivot using the normalized transform that is set above.
|
||||
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) {
|
||||
self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, render_data, responses);
|
||||
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
|
||||
}
|
||||
|
||||
/// Answers if the pointer is currently positioned over the pivot.
|
||||
|
|
|
@ -20,7 +20,7 @@ impl Resize {
|
|||
pub fn start(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, render_data: &RenderData) {
|
||||
self.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
let root_transform = document.document_legacy.metadata.document_to_viewport;
|
||||
let root_transform = document.metadata().document_to_viewport;
|
||||
self.drag_start = root_transform.inverse().transform_point2(self.snap_manager.snap_position(responses, document, input.mouse.position));
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ impl Resize {
|
|||
|
||||
/// Calculate the drag start position in viewport space.
|
||||
pub fn viewport_drag_start(&self, document: &DocumentMessageHandler) -> DVec2 {
|
||||
let root_transform = document.document_legacy.metadata.document_to_viewport;
|
||||
let root_transform = document.metadata().document_to_viewport;
|
||||
root_transform.transform_point2(self.drag_start)
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ impl ShapeState {
|
|||
selected_shape_state.select_point(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - document.metadata.transform_from_viewport(layer).transform_point2(point_position);
|
||||
let offset = mouse_position - document.metadata.transform_to_viewport(layer).transform_point2(point_position);
|
||||
|
||||
let points = self
|
||||
.selected_shape_state
|
||||
|
@ -116,7 +116,7 @@ impl ShapeState {
|
|||
|
||||
pub fn select_all_points(&mut self, document: &Document) {
|
||||
for (layer, state) in self.selected_shape_state.iter_mut() {
|
||||
let Some(subpaths) = get_subpaths(*layer, document) else { return };
|
||||
let Some(subpaths) = get_subpaths(*layer, document) else { return };
|
||||
for manipulator in get_manipulator_groups(subpaths) {
|
||||
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor));
|
||||
for selected_type in &[SelectedType::InHandle, SelectedType::OutHandle] {
|
||||
|
@ -367,7 +367,7 @@ impl ShapeState {
|
|||
let Some(subpaths) = get_subpaths(layer, document) else { continue };
|
||||
let Some(mirror_angle) = get_mirror_handles(layer, document) else { continue };
|
||||
|
||||
let transform = document.metadata.transform_from_viewport(layer);
|
||||
let transform = document.metadata.transform_to_viewport(layer);
|
||||
let delta = transform.inverse().transform_vector2(delta);
|
||||
|
||||
for &point in state.selected_points.iter() {
|
||||
|
@ -435,7 +435,7 @@ impl ShapeState {
|
|||
|
||||
let opposing_handle_lengths = opposing_handle_lengths.as_ref().and_then(|lengths| lengths.get(&layer));
|
||||
|
||||
let transform = document.metadata.transform_from_viewport(layer);
|
||||
let transform = document.metadata.transform_to_viewport(layer);
|
||||
|
||||
for &point in state.selected_points.iter() {
|
||||
let anchor = ManipulatorPointId::new(point.group, SelectedType::Anchor);
|
||||
|
@ -649,7 +649,7 @@ impl ShapeState {
|
|||
let mut result = None;
|
||||
|
||||
let subpaths = get_subpaths(layer, document)?;
|
||||
let viewspace = document.metadata.transform_from_viewport(layer);
|
||||
let viewspace = document.metadata.transform_to_viewport(layer);
|
||||
for manipulator in get_manipulator_groups(subpaths) {
|
||||
let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
|
||||
|
||||
|
@ -664,7 +664,7 @@ impl ShapeState {
|
|||
|
||||
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
||||
fn closest_segment(&self, document: &Document, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> {
|
||||
let transform = document.metadata.transform_from_viewport(layer);
|
||||
let transform = document.metadata.transform_to_viewport(layer);
|
||||
let layer_pos = transform.inverse().transform_point2(position);
|
||||
let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() };
|
||||
|
||||
|
@ -735,7 +735,7 @@ impl ShapeState {
|
|||
let mut process_layer = |layer| {
|
||||
let subpaths = get_subpaths(layer, document)?;
|
||||
|
||||
let transform_to_screenspace = document.metadata.transform_from_viewport(layer);
|
||||
let transform_to_screenspace = document.metadata.transform_to_viewport(layer);
|
||||
let mut result = None;
|
||||
let mut closest_distance_squared = tolerance * tolerance;
|
||||
|
||||
|
@ -803,7 +803,7 @@ impl ShapeState {
|
|||
|
||||
let Some(subpaths) = get_subpaths(layer, document) else { continue };
|
||||
|
||||
let transform = document.metadata.transform_from_viewport(layer);
|
||||
let transform = document.metadata.transform_to_viewport(layer);
|
||||
|
||||
for manipulator_group in get_manipulator_groups(subpaths) {
|
||||
for selected_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] {
|
||||
|
|
|
@ -103,14 +103,14 @@ struct ArtboardToolData {
|
|||
|
||||
impl ArtboardToolData {
|
||||
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let current_artboard = self.selected_artboard.and_then(|layer| document.document_legacy.metadata.bounding_box_document(layer));
|
||||
let current_artboard = self.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer));
|
||||
match (current_artboard, self.bounding_box_overlays.take()) {
|
||||
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
|
||||
(Some(bounds), paths) => {
|
||||
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
|
||||
|
||||
bounding_box_overlays.bounds = bounds;
|
||||
bounding_box_overlays.transform = document.document_legacy.metadata.document_to_viewport;
|
||||
bounding_box_overlays.transform = document.metadata().document_to_viewport;
|
||||
|
||||
bounding_box_overlays.transform(responses);
|
||||
|
||||
|
@ -265,7 +265,7 @@ impl Fsm for ArtboardToolFsmState {
|
|||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let root_transform = document.document_legacy.metadata.document_to_viewport.inverse();
|
||||
let root_transform = document.metadata().document_to_viewport.inverse();
|
||||
|
||||
let mut start = tool_data.drag_start;
|
||||
let mut size = snapped_mouse_position - start;
|
||||
|
|
|
@ -341,7 +341,7 @@ impl Fsm for BrushToolFsmState {
|
|||
document, global_tool_data, input, ..
|
||||
} = tool_action_data;
|
||||
|
||||
let document_position = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
let document_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
let layer_position = tool_data.transform.inverse().transform_point2(document_position);
|
||||
|
||||
let ToolMessage::Brush(event) = event else {
|
||||
|
|
|
@ -68,7 +68,7 @@ impl Fsm for FillToolFsmState {
|
|||
let ToolMessage::Fill(event) = event else {
|
||||
return self;
|
||||
};
|
||||
let Some(layer_identifier) = document.document_legacy.metadata.click(input.mouse.position) else {
|
||||
let Some(layer_identifier) = document.metadata().click(input.mouse.position, &document.document_legacy.document_network) else {
|
||||
return self;
|
||||
};
|
||||
let layer = layer_identifier.to_path();
|
||||
|
|
|
@ -110,7 +110,7 @@ impl Fsm for NodeGraphToolFsmState {
|
|||
match (self, event) {
|
||||
(_, FrameToolMessage::DocumentIsDirty | FrameToolMessage::SelectionChanged) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
//tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
|
||||
self
|
||||
}
|
||||
|
|
|
@ -191,7 +191,7 @@ impl Fsm for FreehandToolFsmState {
|
|||
document, global_tool_data, input, ..
|
||||
} = tool_action_data;
|
||||
|
||||
let transform = document.document_legacy.metadata.document_to_viewport;
|
||||
let transform = document.metadata().document_to_viewport;
|
||||
|
||||
let ToolMessage::Freehand(event) = event else {
|
||||
return self;
|
||||
|
|
|
@ -116,10 +116,10 @@ enum GradientToolFsmState {
|
|||
|
||||
/// Computes the transform from gradient space to viewport space (where gradient space is 0..1)
|
||||
fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
let bounds = document.document_legacy.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY).unwrap();
|
||||
let bounds = document.metadata().nonzero_bounding_box(layer);
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
|
||||
let multiplied = document.document_legacy.metadata.transform_from_viewport(layer);
|
||||
let multiplied = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
multiplied * bound_transform
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ impl SelectedGradient {
|
|||
};
|
||||
|
||||
// Clear the gradient if layer deleted
|
||||
if !inner_gradient.layer.exists(&document.document_legacy.metadata) {
|
||||
if !inner_gradient.layer.exists(&document.metadata()) {
|
||||
responses.add(ToolMessage::RefreshToolOptions);
|
||||
*gradient = None;
|
||||
return;
|
||||
|
@ -391,7 +391,7 @@ impl Fsm for GradientToolFsmState {
|
|||
SelectedGradient::update(&mut tool_data.selected_gradient, document, responses);
|
||||
}
|
||||
|
||||
for layer in document.document_legacy.metadata.selected_visible_layers() {
|
||||
for layer in document.metadata().selected_visible_layers() {
|
||||
if let Some(gradient) = get_gradient(layer, &document.document_legacy) {
|
||||
let dragging = tool_data
|
||||
.selected_gradient
|
||||
|
@ -526,7 +526,7 @@ impl Fsm for GradientToolFsmState {
|
|||
document.backup_nonmut(responses);
|
||||
GradientToolFsmState::Drawing
|
||||
} else {
|
||||
let selected_layer = document.document_legacy.metadata.click(input.mouse.position);
|
||||
let selected_layer = document.metadata().click(input.mouse.position, &document.document_legacy.document_network);
|
||||
|
||||
// Apply the gradient to the selected layer
|
||||
if let Some(layer) = selected_layer {
|
||||
|
@ -540,7 +540,7 @@ impl Fsm for GradientToolFsmState {
|
|||
// return self;
|
||||
// }
|
||||
|
||||
if !document.document_legacy.metadata.selected_layers_contains(layer) {
|
||||
if !document.metadata().selected_layers_contains(layer) {
|
||||
let replacement_selected_layers = vec![layer.to_path()];
|
||||
|
||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
||||
|
|
|
@ -118,7 +118,7 @@ impl Fsm for ImaginateToolFsmState {
|
|||
match (self, event) {
|
||||
(_, ImaginateToolMessage::DocumentIsDirty | ImaginateToolMessage::SelectionChanged) => {
|
||||
tool_data.path_outlines.clear_selected(responses);
|
||||
tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
//tool_data.path_outlines.update_selected(document.selected_visible_layers(), document, responses, render_data);
|
||||
|
||||
self
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ impl Fsm for LineToolFsmState {
|
|||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
|
||||
let viewport_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
tool_data.drag_start = document.document_legacy.metadata.document_to_viewport.inverse().transform_point2(viewport_start);
|
||||
tool_data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(viewport_start);
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
|
||||
|
||||
|
@ -202,7 +202,7 @@ impl Fsm for LineToolFsmState {
|
|||
tool_data.drag_current = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let keyboard = &input.keyboard;
|
||||
let transform = document.document_legacy.metadata.document_to_viewport;
|
||||
let transform = document.metadata().document_to_viewport;
|
||||
responses.add(generate_transform(tool_data, transform, keyboard.key(lock_angle), keyboard.key(snap_angle), keyboard.key(center)));
|
||||
|
||||
LineToolFsmState::Drawing
|
||||
|
|
|
@ -210,7 +210,7 @@ struct PathToolData {
|
|||
impl PathToolData {
|
||||
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer in document.document_legacy.metadata.all_layers() {
|
||||
for layer in document.metadata().all_layers() {
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses);
|
||||
}
|
||||
|
||||
|
@ -242,15 +242,11 @@ impl PathToolData {
|
|||
PathToolFsmState::Dragging
|
||||
}
|
||||
// We didn't find a point nearby, so consider selecting the nearest shape instead
|
||||
else if let Some(layer) = document.document_legacy.metadata.click(input.mouse.position) {
|
||||
// TODO: Actual selection
|
||||
let layer_list = vec![layer.to_path()];
|
||||
else if let Some(layer) = document.metadata().click(input.mouse.position, &document.document_legacy.document_network) {
|
||||
if shift {
|
||||
responses.add(DocumentMessage::AddSelectedLayers { additional_layers: layer_list });
|
||||
responses.add(NodeGraphMessage::AddSelectNodes { nodes: vec![layer.to_node()] });
|
||||
} else {
|
||||
responses.add(DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: layer_list,
|
||||
});
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![layer.to_node()] });
|
||||
}
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.previous_mouse_position = input.mouse.position;
|
||||
|
@ -342,7 +338,7 @@ impl Fsm for PathToolFsmState {
|
|||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the newly targeted layers to visible
|
||||
let target_layers = document.document_legacy.metadata.selected_layers().collect();
|
||||
let target_layers = document.metadata().selected_layers().collect();
|
||||
shape_editor.set_selected_layers(target_layers);
|
||||
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
|
@ -354,7 +350,7 @@ impl Fsm for PathToolFsmState {
|
|||
(_, PathToolMessage::DocumentIsDirty) => {
|
||||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer in document.document_legacy.metadata.selected_layers() {
|
||||
for layer in document.metadata().selected_layers() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
||||
}
|
||||
|
||||
|
@ -386,7 +382,7 @@ impl Fsm for PathToolFsmState {
|
|||
let shift_pressed = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![] });
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
|
@ -401,7 +397,7 @@ impl Fsm for PathToolFsmState {
|
|||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
responses.add(NodeGraphMessage::SetSelectNodes { nodes: vec![] });
|
||||
} else {
|
||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
|
|
|
@ -247,7 +247,7 @@ impl PenToolData {
|
|||
let layer_path = document.get_path_for_new_layer();
|
||||
|
||||
// Get the position and set properties
|
||||
let transform = document.document_legacy.metadata.document_to_viewport * document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default();
|
||||
let transform = document.metadata().document_to_viewport * document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default();
|
||||
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let start_position = transform.inverse().transform_point2(snapped_position);
|
||||
self.weight = line_weight;
|
||||
|
@ -566,7 +566,7 @@ impl Fsm for PenToolFsmState {
|
|||
transform = DAffine2::IDENTITY;
|
||||
}
|
||||
|
||||
transform = document.document_legacy.metadata.document_to_viewport * transform;
|
||||
transform = document.metadata().document_to_viewport * transform;
|
||||
|
||||
let ToolMessage::Pen(event) = event else {
|
||||
return self;
|
||||
|
@ -579,19 +579,19 @@ impl Fsm for PenToolFsmState {
|
|||
(_, PenToolMessage::DocumentIsDirty) => {
|
||||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer in document.document_legacy.metadata.selected_layers() {
|
||||
for layer in document.metadata().selected_layers() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::SelectionChanged) => {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer in document.document_legacy.metadata.all_layers() {
|
||||
for layer in document.metadata().all_layers() {
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses);
|
||||
}
|
||||
|
||||
// Redraw the overlays of the newly selected layers
|
||||
for layer in document.document_legacy.metadata.selected_layers() {
|
||||
for layer in document.metadata().selected_layers() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
||||
}
|
||||
self
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -202,7 +202,7 @@ impl Fsm for SplineToolFsmState {
|
|||
..
|
||||
} = tool_action_data;
|
||||
|
||||
let transform = document.document_legacy.metadata.document_to_viewport;
|
||||
let transform = document.metadata().document_to_viewport;
|
||||
|
||||
let ToolMessage::Spline(event) = event else {
|
||||
return self;
|
||||
|
|
|
@ -47,11 +47,12 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
|
||||
|
||||
let selected_layers = document.layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then_some(layer_path)).collect::<Vec<_>>();
|
||||
let selected_layers_n = document.metadata().selected_layers().collect::<Vec<_>>();
|
||||
|
||||
let mut selected = Selected::new(
|
||||
&mut self.original_transforms,
|
||||
&mut self.pivot,
|
||||
&selected_layers,
|
||||
&selected_layers_n,
|
||||
responses,
|
||||
&document.document_legacy,
|
||||
Some(shape_editor),
|
||||
|
@ -228,7 +229,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
SelectionChanged => {
|
||||
let target_layers = document.document_legacy.metadata.selected_layers().collect();
|
||||
let target_layers = document.metadata().selected_layers().collect();
|
||||
shape_editor.set_selected_layers(target_layers);
|
||||
}
|
||||
TypeBackspace => self.transform_operation.grs_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::messages::frontend::utility_types::FrontendImageData;
|
||||
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
|
||||
use crate::messages::portfolio::document::node_graph::{transform_utils, wrap_network_in_scope};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
|
@ -56,6 +56,7 @@ pub struct NodeRuntime {
|
|||
pub(crate) thumbnails: HashMap<NodeId, SvgSegmentList>,
|
||||
pub(crate) click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
pub(crate) transforms: HashMap<NodeId, DAffine2>,
|
||||
pub(crate) upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
|
||||
}
|
||||
|
||||
|
@ -80,6 +81,7 @@ pub(crate) struct GenerationResponse {
|
|||
new_thumbnails: HashMap<NodeId, SvgSegmentList>,
|
||||
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
new_transforms: HashMap<LayerNodeIdentifier, DAffine2>,
|
||||
new_upstream_transforms: HashMap<NodeId, DAffine2>,
|
||||
}
|
||||
|
||||
enum NodeGraphUpdate {
|
||||
|
@ -119,6 +121,7 @@ impl NodeRuntime {
|
|||
canvas_cache: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
transforms: HashMap::new(),
|
||||
upstream_transforms: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub async fn run(&mut self) {
|
||||
|
@ -147,13 +150,14 @@ impl NodeRuntime {
|
|||
|
||||
let monitor_nodes = network
|
||||
.recursive_nodes()
|
||||
.filter(|node| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"))
|
||||
.map(|node| node.path.clone().unwrap_or_default())
|
||||
.collect();
|
||||
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"))
|
||||
.map(|(_, node)| node.path.clone().unwrap_or_default())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let result = self.execute_network(&path, network, transform, viewport_resolution).await;
|
||||
let mut responses = VecDeque::new();
|
||||
self.update_thumbnails(&path, monitor_nodes, &mut responses);
|
||||
self.update_thumbnails(&path, &monitor_nodes, &mut responses);
|
||||
self.update_upstream_transforms(&path, &monitor_nodes, &mut responses);
|
||||
let response = GenerationResponse {
|
||||
generation_id,
|
||||
result,
|
||||
|
@ -161,6 +165,7 @@ impl NodeRuntime {
|
|||
new_thumbnails: self.thumbnails.clone(),
|
||||
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
|
||||
new_transforms: self.transforms.clone().into_iter().map(|(id, transform)| (LayerNodeIdentifier::new_unchecked(id), transform)).collect(),
|
||||
new_upstream_transforms: self.upstream_transforms.clone(),
|
||||
};
|
||||
self.sender.send_generation_response(response);
|
||||
}
|
||||
|
@ -184,7 +189,10 @@ impl NodeRuntime {
|
|||
resolution: viewport_resolution,
|
||||
..Default::default()
|
||||
},
|
||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
||||
export_format: graphene_core::application_io::ExportFormat::Canvas,
|
||||
#[cfg(not(any(feature = "resvg", feature = "vello")))]
|
||||
export_format: graphene_core::application_io::ExportFormat::Svg,
|
||||
},
|
||||
image_frame: None,
|
||||
};
|
||||
|
@ -223,14 +231,14 @@ impl NodeRuntime {
|
|||
}
|
||||
|
||||
/// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI.
|
||||
pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: Vec<Vec<u64>>, responses: &mut VecDeque<Message>) {
|
||||
pub fn update_thumbnails(&mut self, layer_path: &[LayerId], monitor_nodes: &[Vec<u64>], responses: &mut VecDeque<Message>) {
|
||||
let mut image_data: Vec<_> = Vec::new();
|
||||
for node_path in monitor_nodes {
|
||||
let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
|
||||
warn!("Monitor node has invalid node id");
|
||||
continue;
|
||||
};
|
||||
let Some(value) = self.executor.introspect(&node_path).flatten() else {
|
||||
let Some(value) = self.executor.introspect(node_path).flatten() else {
|
||||
warn!("Failed to introspect monitor node for thumbnail");
|
||||
continue;
|
||||
};
|
||||
|
@ -280,6 +288,24 @@ impl NodeRuntime {
|
|||
responses.add(FrontendMessage::UpdateImageData { document_id: 0, image_data });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_upstream_transforms(&mut self, layer_path: &[LayerId], monitor_nodes: &[Vec<u64>], responses: &mut VecDeque<Message>) {
|
||||
for node_path in monitor_nodes {
|
||||
let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
|
||||
warn!("Monitor node has invalid node id");
|
||||
continue;
|
||||
};
|
||||
let Some(value) = self.executor.introspect(node_path).flatten() else {
|
||||
warn!("Failed to introspect monitor node for upstream transforms");
|
||||
continue;
|
||||
};
|
||||
let Some(graphic_element_data) = value.downcast_ref::<graphene_core::vector::VectorData>() else {
|
||||
warn!("Failed to downcast transform input to vector data");
|
||||
continue;
|
||||
};
|
||||
self.upstream_transforms.insert(node_id, graphic_element_data.transform());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn introspect_node(path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
|
||||
NODE_RUNTIME
|
||||
|
@ -470,9 +496,10 @@ impl NodeGraphExecutor {
|
|||
new_thumbnails,
|
||||
new_click_targets,
|
||||
new_transforms,
|
||||
new_upstream_transforms,
|
||||
}) => {
|
||||
self.thumbnails = new_thumbnails;
|
||||
document.metadata.update_transforms(new_transforms);
|
||||
document.metadata.update_transforms(new_transforms, new_upstream_transforms);
|
||||
document.metadata.update_click_targets(new_click_targets);
|
||||
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
|
||||
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||
|
|
12
frontend/package-lock.json
generated
12
frontend/package-lock.json
generated
|
@ -2876,9 +2876,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001481",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz",
|
||||
"integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==",
|
||||
"version": "1.0.30001546",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz",
|
||||
"integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -8017,9 +8017,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001481",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz",
|
||||
"integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==",
|
||||
"version": "1.0.30001546",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz",
|
||||
"integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
|
|
|
@ -282,8 +282,10 @@
|
|||
const layerId = BigInt(item.layerId.toString());
|
||||
path.push(layerId);
|
||||
|
||||
const mapping = layerCache.get(path.toString());
|
||||
const mapping = layerCache.get([path[path.length-1]].toString());
|
||||
if (mapping) {
|
||||
mapping.layerType = item.children.length >= 1 ? "Folder" : "Layer";
|
||||
mapping.path = new BigUint64Array(path);
|
||||
layers.push({
|
||||
folderIndex: index,
|
||||
bottomLayer: index === folder.children.length - 1,
|
||||
|
|
|
@ -648,6 +648,7 @@
|
|||
style:--data-color={`var(--color-data-${node.primaryInput})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryInput}-dim)`}
|
||||
>
|
||||
<title>{node.primaryInput} data</title>
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
@ -665,6 +666,7 @@
|
|||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
|
||||
>
|
||||
<title>{node.primaryOutput.dataType} data</title>
|
||||
<path d="M0,2.953,2.521,1.259a2.649,2.649,0,0,1,2.959,0L8,2.953V8H0Z" />
|
||||
</svg>
|
||||
{/if}
|
||||
|
@ -677,11 +679,12 @@
|
|||
style:--data-color={`var(--color-data-${stackDatainput.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${stackDatainput.dataType}-dim)`}
|
||||
>
|
||||
<title>{stackDatainput.dataType} data</title>
|
||||
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="details">
|
||||
<TextLabel tooltip={node.displayName}>{node.displayName}</TextLabel>
|
||||
<TextLabel tooltip={`${node.displayName} node with id: ${node.id}`}>{node.displayName}</TextLabel>
|
||||
</div>
|
||||
|
||||
<svg class="border-mask" width="0" height="0">
|
||||
|
@ -738,6 +741,7 @@
|
|||
style:--data-color={`var(--color-data-${node.primaryInput})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryInput}-dim)`}
|
||||
>
|
||||
<title>{node.primaryInput} data</title>
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
|
||||
</svg>
|
||||
{/if}
|
||||
|
@ -752,6 +756,7 @@
|
|||
style:--data-color={`var(--color-data-${parameter.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
|
||||
>
|
||||
<title>{parameter.dataType} data</title>
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
|
||||
</svg>
|
||||
{/if}
|
||||
|
@ -769,6 +774,7 @@
|
|||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
|
||||
>
|
||||
<title>{node.primaryOutput.dataType} data</title>
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
|
||||
</svg>
|
||||
{/if}
|
||||
|
@ -782,6 +788,7 @@
|
|||
style:--data-color={`var(--color-data-${parameter.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
|
||||
>
|
||||
<title>{parameter.dataType} data</title>
|
||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" />
|
||||
</svg>
|
||||
{/each}
|
||||
|
|
|
@ -652,7 +652,7 @@ impl JsEditorHandle {
|
|||
#[wasm_bindgen(js_name = selectNodes)]
|
||||
pub fn select_nodes(&self, nodes: Option<Vec<u64>>) {
|
||||
let nodes = nodes.unwrap_or_default();
|
||||
let message = NodeGraphMessage::SelectNodes { nodes };
|
||||
let message = NodeGraphMessage::SetSelectNodes { nodes };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
|
|
@ -47,13 +47,13 @@ impl Subpath {
|
|||
}
|
||||
|
||||
/// Convert to the legacy Subpath from the `bezier_rs::Subpath`.
|
||||
pub fn from_bezier_rs(value: &[bezier_rs::Subpath<ManipulatorGroupId>]) -> Self {
|
||||
pub fn from_bezier_rs<'a, Subpath: core::borrow::Borrow<&'a bezier_rs::Subpath<ManipulatorGroupId>>>(value: impl IntoIterator<Item = Subpath>) -> Self {
|
||||
let mut groups = IdBackedVec::new();
|
||||
for subpath in value {
|
||||
for group in subpath.manipulator_groups() {
|
||||
for subpath in value.into_iter() {
|
||||
for group in subpath.borrow().manipulator_groups() {
|
||||
groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle));
|
||||
}
|
||||
if subpath.closed() {
|
||||
if subpath.borrow().closed() {
|
||||
groups.push(ManipulatorGroup::closed());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,8 +59,7 @@ impl DocumentNode {
|
|||
|
||||
fn resolve_proto_node(mut self) -> ProtoNode {
|
||||
assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {:#?} with no inputs", self);
|
||||
let DocumentNodeImplementation::Unresolved(fqn) = self.implementation
|
||||
else {
|
||||
let DocumentNodeImplementation::Unresolved(fqn) = self.implementation else {
|
||||
unreachable!("tried to resolve not flattened node on resolved node {:?}", self);
|
||||
};
|
||||
let (input, mut args) = if let Some(ty) = self.manual_composition {
|
||||
|
@ -245,6 +244,13 @@ impl NodeInput {
|
|||
None
|
||||
}
|
||||
}
|
||||
pub fn as_node(&self) -> Option<NodeId> {
|
||||
if let NodeInput::Node { node_id, .. } = self {
|
||||
Some(*node_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, DynAny)]
|
||||
|
@ -905,22 +911,22 @@ impl NodeNetwork {
|
|||
|
||||
/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||
pub fn recursive_nodes(&self) -> RecursiveNodeIter {
|
||||
let nodes = self.nodes.values().collect();
|
||||
let nodes = self.nodes.iter().collect();
|
||||
RecursiveNodeIter { nodes }
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||
pub struct RecursiveNodeIter<'a> {
|
||||
nodes: Vec<&'a DocumentNode>,
|
||||
nodes: Vec<(&'a NodeId, &'a DocumentNode)>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RecursiveNodeIter<'a> {
|
||||
type Item = &'a DocumentNode;
|
||||
type Item = (&'a NodeId, &'a DocumentNode);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let node = self.nodes.pop()?;
|
||||
if let DocumentNodeImplementation::Network(network) = &node.implementation {
|
||||
self.nodes.extend(network.nodes.values());
|
||||
if let DocumentNodeImplementation::Network(network) = &node.1.implementation {
|
||||
self.nodes.extend(network.nodes.iter());
|
||||
}
|
||||
Some(node)
|
||||
}
|
||||
|
|
|
@ -359,6 +359,6 @@ async fn render_node<'a: 'input, F: Future<Output = GraphicGroup>>(
|
|||
};
|
||||
RenderOutput::CanvasFrame(frame.into())
|
||||
}
|
||||
_ => todo!("Non svg render output"),
|
||||
_ => todo!("Non svg render output for {output_format:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -328,6 +328,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
|||
)],
|
||||
register_node!(graphene_std::raster::EmptyImageNode<_, _>, input: DAffine2, params: [Color]),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: ImageFrame<Color>, params: []),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: VectorData, params: []),
|
||||
register_node!(graphene_core::memo::MonitorNode<_>, input: graphene_core::GraphicElementData, params: []),
|
||||
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]),
|
||||
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue