Add layer node chains, import/export edge connectors, and refactor graph editing to go thru a NodeNetworkInterface (#1794)

* WIP: NodeNetworkInterface

* Organize ModifyInputsContext to use network interface

* Improve ClickTarget and Position state

* Rework ClickTarget state

* Continue fixing NodeGraphMessageHandler

* Restructure network_metadata

* Final(?) NodeNetworkInterface struct

* Final(??) NodeNetworkInterface

* Final(???) NodeNetworkInterface. Separated persistent and transient data

* Final NodeNetworkInterface data structure. Implemented all basic getters

* Continue migrating functionality to network interface

* Migrate all NodeGraphMessage's to use network interface

* Fix all helper functions in NodeGraphMessageHandler

* Move document metadata to network interface, remove various cached fields

* Move all editor only NodeNetwork implementations to NodeNetworkInterface

* Fix all DocumentNodeDefinitions

* Rework and migrate GraphOperationMessages to network interface

* Continue migration to NodeNetworkInterface

* Save point before merging master

* Fix all errors in network_interface

* 850 -> 160 errors

* Fix all errors :D

* Render default document

* Visualize click targets

* merge conflicts

* Cache transient metadata separately, store entire interface in document history

* Start migration to storing selected nodes for each network

* Remove selected nodes from document message handler

* Move outward wires and all nodes bounding box to transient metadata

* Fix connecting/disconnecting nodes

* Layer stack organization for disconnecting/connecting nodes

* Basic chain locking

* Improve chain positioning

* Add copy/pasting

* Move upstream nodes on shift+drag

* merge conflict fixes

* Improve Graph.svelte code quality

* Final improvements to Graph.svelte

* Fix layer panel

* Performance optimizations

* Bug fixes and derived PTZ

* Chain organization improvement and bug fixes

* Bug fixes, remove all warnings

* Automatic file upgrade

* Final code review

* Fix editor tests

* Fix compile errors

* Remove select tool intersection check when panning

* WIP: Import/Exports

* Fix JS issues

* Finish simplified import/export UI

* Import/Export viewport edge UI

* Remove minimum y bound on import/export ports

* Improve performance while panning graph

* cargo fmt

* Fix CI code build

* Format the demo artwork graph with chains

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: dennis@kobert.dev <dennis@kobert.dev>
This commit is contained in:
adamgerhant
2024-08-04 06:47:13 -07:00
committed by GitHub
co-authored by Keavon Chambers dennis@kobert.dev
parent ea44d1440a
commit 0dbbabe73e
77 changed files with 11361 additions and 8011 deletions
@@ -1,10 +1,13 @@
use super::utility_types::misc::{OptionBoundsSnapping, OptionPointSnapping};
use super::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GridSnapping};
use crate::messages::portfolio::utility_types::PanelType;
use crate::messages::prelude::*;
use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::document::NodeId;
use graphene_core::raster::BlendMode;
use graphene_core::raster::Image;
use graphene_core::vector::style::ViewMode;
@@ -12,8 +15,6 @@ use graphene_core::Color;
use glam::DAffine2;
use super::utility_types::misc::{OptionBoundsSnapping, OptionPointSnapping};
#[impl_message(Message, PortfolioMessage, Document)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum DocumentMessage {
@@ -37,7 +38,7 @@ pub enum DocumentMessage {
aggregate: AlignAggregate,
},
BackupDocument {
network: NodeNetwork,
network_interface: NodeNetworkInterface,
},
ClearArtboards,
ClearLayersPanel,
@@ -47,15 +48,18 @@ pub enum DocumentMessage {
},
CreateEmptyFolder,
DebugPrintDocument,
DeleteLayer {
layer: LayerNodeIdentifier,
},
DeleteSelectedLayers,
DeselectAllLayers,
DocumentHistoryBackward,
DocumentHistoryForward,
DocumentStructureChanged,
DuplicateSelectedLayers,
EnterNestedNetwork {
node_id: NodeId,
},
ExitNestedNetwork {
steps_back: usize,
},
FlipSelectedLayers {
flip_axis: FlipAxis,
},
@@ -77,11 +81,14 @@ pub enum DocumentMessage {
svg: String,
transform: DAffine2,
parent: LayerNodeIdentifier,
insert_index: isize,
insert_index: usize,
},
MoveSelectedLayersTo {
parent: LayerNodeIdentifier,
insert_index: isize,
insert_index: usize,
},
MoveSelectedLayersToGroup {
parent: LayerNodeIdentifier,
},
NudgeSelectedLayers {
delta_x: f64,
@@ -103,7 +110,6 @@ pub enum DocumentMessage {
},
RenderRulers,
RenderScrollbars,
ResetTransform,
SaveDocument,
SelectAllLayers,
SelectedLayersLower,
@@ -118,6 +124,9 @@ pub enum DocumentMessage {
ctrl: bool,
shift: bool,
},
SetActivePanel {
active_panel: PanelType,
},
SetBlendModeForSelectedLayers {
blend_mode: BlendMode,
},
@@ -148,9 +157,10 @@ pub enum DocumentMessage {
Undo,
UndoFinished,
UngroupSelectedLayers,
UpdateDocumentTransform {
transform: glam::DAffine2,
UngroupLayer {
layer: LayerNodeIdentifier,
},
PTZUpdate,
ZoomCanvasTo100Percent,
ZoomCanvasTo200Percent,
ZoomCanvasToFitAll,
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,10 @@
use super::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::document::NodeId;
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::vector::brush_stroke::BrushStroke;
@@ -11,59 +12,16 @@ use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::PointId;
use graphene_core::vector::VectorModificationType;
use graphene_core::{Artboard, Color};
use graphene_std::vector::misc::BooleanOperation;
use glam::{DAffine2, DVec2, IVec2};
#[impl_message(Message, DocumentMessage, GraphOperation)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum GraphOperationMessage {
AddNodesAsChild {
nodes: HashMap<NodeId, DocumentNode>,
new_ids: HashMap<NodeId, NodeId>,
parent: LayerNodeIdentifier,
insert_index: isize,
},
CreateBooleanOperationNode {
node_id: NodeId,
operation: BooleanOperation,
},
DeleteLayer {
layer: LayerNodeIdentifier,
reconnect: bool,
},
DisconnectInput {
node_id: NodeId,
input_index: usize,
},
DisconnectNodeFromStack {
node_id: NodeId,
reconnect_to_sibling: bool,
},
FillSet {
layer: LayerNodeIdentifier,
fill: Fill,
},
InsertNodeAtStackIndex {
node_id: NodeId,
parent: LayerNodeIdentifier,
insert_index: usize,
},
InsertNodeBetween {
// Post node
post_node_id: NodeId,
post_node_input_index: usize,
// Inserted node
insert_node_id: NodeId,
insert_node_output_index: usize,
insert_node_input_index: usize,
// Pre node
pre_node_id: NodeId,
pre_node_output_index: usize,
},
MoveSelectedSiblingsToChild {
new_parent: LayerNodeIdentifier,
},
OpacitySet {
layer: LayerNodeIdentifier,
opacity: f64,
@@ -100,6 +58,9 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier,
strokes: Vec<BrushStroke>,
},
SetUpstreamToChain {
layer: LayerNodeIdentifier,
},
NewArtboard {
id: NodeId,
artboard: Artboard,
@@ -108,20 +69,19 @@ pub enum GraphOperationMessage {
id: NodeId,
image_frame: ImageFrame<Color>,
parent: LayerNodeIdentifier,
insert_index: isize,
insert_index: usize,
},
NewCustomLayer {
id: NodeId,
nodes: HashMap<NodeId, DocumentNode>,
nodes: Vec<(NodeId, NodeTemplate)>,
parent: LayerNodeIdentifier,
insert_index: isize,
alias: String,
insert_index: usize,
},
NewVectorLayer {
id: NodeId,
subpaths: Vec<Subpath<PointId>>,
parent: LayerNodeIdentifier,
insert_index: isize,
insert_index: usize,
},
NewTextLayer {
id: NodeId,
@@ -129,10 +89,10 @@ pub enum GraphOperationMessage {
font: Font,
size: f64,
parent: LayerNodeIdentifier,
insert_index: isize,
insert_index: usize,
},
ResizeArtboard {
id: NodeId,
layer: LayerNodeIdentifier,
location: IVec2,
dimensions: IVec2,
},
@@ -142,45 +102,6 @@ pub enum GraphOperationMessage {
svg: String,
transform: DAffine2,
parent: LayerNodeIdentifier,
insert_index: isize,
},
ShiftUpstream {
node_id: NodeId,
shift: IVec2,
shift_self: bool,
},
SetNodePosition {
node_id: NodeId,
position: IVec2,
},
SetName {
layer: LayerNodeIdentifier,
name: String,
},
SetNameImpl {
layer: LayerNodeIdentifier,
name: String,
},
SetNodeInput {
node_id: NodeId,
input_index: usize,
input: NodeInput,
},
ToggleSelectedVisibility,
ToggleVisibility {
node_id: NodeId,
},
SetVisibility {
node_id: NodeId,
visible: bool,
},
StartPreviewingWithoutRestore,
ToggleSelectedLocked,
ToggleLocked {
node_id: NodeId,
},
SetLocked {
node_id: NodeId,
locked: bool,
insert_index: usize,
},
}
@@ -1,24 +1,21 @@
use super::transform_utils;
use super::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
use crate::messages::portfolio::document::utility_types::nodes::CollapsedLayers;
use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, Previewing};
use graph_craft::document::{generate_uuid, NodeId, NodeInput};
use graphene_core::renderer::Quad;
use graphene_core::text::Font;
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, LineCap, LineJoin, Stroke};
use graphene_core::Color;
use graphene_std::vector::convert_usvg_path;
use glam::{DAffine2, DVec2, IVec2};
use glam::{DAffine2, DVec2};
pub struct GraphOperationMessageData<'a> {
pub document_network: &'a mut NodeNetwork,
pub document_metadata: &'a mut DocumentMetadata,
pub selected_nodes: &'a mut SelectedNodes,
pub network_interface: &'a mut NodeNetworkInterface,
pub collapsed: &'a mut CollapsedLayers,
pub node_graph: &'a mut NodeGraphMessageHandler,
}
@@ -30,301 +27,26 @@ pub struct GraphOperationMessageHandler {}
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
let GraphOperationMessageData {
document_network,
document_metadata,
selected_nodes,
collapsed,
node_graph,
} = data;
let network_interface = data.network_interface;
match message {
GraphOperationMessage::AddNodesAsChild { nodes, new_ids, parent, insert_index } => {
let shift = document_network
.get_root_node()
.and_then(|root_node| {
nodes.get(&root_node.id).and_then(|node| {
if parent == LayerNodeIdentifier::ROOT_PARENT {
return None;
};
let parent_node_id = parent.to_node();
document_network
.nodes
.get(&parent_node_id)
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
})
.unwrap_or_default();
for (old_id, mut document_node) in nodes {
// Shift copied node
document_node.metadata.position += shift;
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
let default_inputs = NodeGraphMessageHandler::get_default_inputs(document_network, &Vec::new(), node_id, &node_graph.resolved_types, &document_node);
document_node = document_node.map_ids(default_inputs, &new_ids);
// Insert node into network
node_graph.insert_node(node_id, document_node, document_network, &Vec::new());
}
let Some(new_layer_id) = new_ids.get(&NodeId(0)) else {
error!("Could not get layer node when adding as child");
return;
};
let insert_index = if insert_index < 0 { 0 } else { insert_index as usize };
let (downstream_node, upstream_node, input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent, insert_index);
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![*new_layer_id] });
match (downstream_node, upstream_node) {
(Some(downstream_node), Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: downstream_node,
post_node_input_index: input_index,
insert_node_output_index: 0,
insert_node_id: *new_layer_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: upstream_node,
}),
(Some(downstream_node), None) => responses.add(GraphOperationMessage::SetNodeInput {
node_id: downstream_node,
input_index,
input: NodeInput::node(*new_layer_id, 0),
}),
(None, Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: document_network.exports_metadata.0,
post_node_input_index: 0,
insert_node_output_index: 0,
insert_node_id: *new_layer_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: upstream_node,
}),
(None, None) => {
if let Some(primary_export) = document_network.exports.get_mut(0) {
*primary_export = NodeInput::node(*new_layer_id, 0)
}
}
};
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: *new_layer_id,
shift: IVec2::new(0, 3),
shift_self: true,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::CreateBooleanOperationNode { node_id, operation } => {
let new_boolean_operation_node = resolve_document_node_type("Boolean Operation")
.expect("Failed to create a Boolean Operation node")
.to_document_node_default_inputs(
[
Some(NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorData::empty()), true)),
Some(NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorData::empty()), true)),
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
],
Default::default(),
);
node_graph.insert_node(node_id, new_boolean_operation_node, document_network, &Vec::new());
}
GraphOperationMessage::DeleteLayer { layer, reconnect } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot delete ROOT_PARENT");
return;
}
ModifyInputsContext::delete_nodes(node_graph, document_network, selected_nodes, vec![layer.to_node()], reconnect, responses, Vec::new());
load_network_structure(document_network, document_metadata, collapsed);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
// Make sure to also update NodeGraphMessage::DisconnectInput when changing this
GraphOperationMessage::DisconnectInput { node_id, input_index } => {
let Some(existing_input) = document_network
.nodes
.get(&node_id)
.map_or_else(|| document_network.exports.get(input_index), |node| node.inputs.get(input_index))
else {
warn!("Could not find input for {node_id} at index {input_index} when disconnecting");
return;
};
let tagged_value = TaggedValue::from_type(&ModifyInputsContext::get_input_type(document_network, &Vec::new(), node_id, &node_graph.resolved_types, input_index));
let mut input = NodeInput::value(tagged_value, true);
if let NodeInput::Value { exposed, .. } = &mut input {
*exposed = existing_input.is_exposed();
}
if node_id == document_network.exports_metadata.0 {
// Since it is only possible to drag the solid line, there must be a root_node_to_restore
if let Previewing::Yes { .. } = document_network.previewing {
responses.add(GraphOperationMessage::StartPreviewingWithoutRestore);
}
// If there is no preview, then disconnect
else {
responses.add(GraphOperationMessage::SetNodeInput { node_id, input_index, input });
}
} else {
responses.add(GraphOperationMessage::SetNodeInput { node_id, input_index, input });
}
if document_network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
responses.add(NodeGraphMessage::SendGraph);
}
GraphOperationMessage::DisconnectNodeFromStack { node_id, reconnect_to_sibling } => {
ModifyInputsContext::remove_references_from_network(node_graph, document_network, node_id, reconnect_to_sibling, &Vec::new());
responses.add(GraphOperationMessage::DisconnectInput { node_id, input_index: 0 });
}
GraphOperationMessage::FillSet { layer, fill } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run FillSet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.fill_set(fill);
}
}
GraphOperationMessage::InsertNodeAtStackIndex { node_id, parent, insert_index } => {
let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent, insert_index);
// `layer_to_move` should always correspond to a node.
let Some(layer_to_move_node) = document_network.nodes.get(&node_id) else {
log::error!("Layer node not found when inserting node {} at index {}", node_id, insert_index);
return;
};
// Move current layer to post node.
let current_position = layer_to_move_node.metadata.position;
let new_position = if let Some(post_node_id) = post_node_id {
document_network.nodes.get(&post_node_id).expect("Post node id should always refer to a node").metadata.position
} else if let Some(root_node) = document_network.get_root_node() {
document_network.nodes.get(&root_node.id).expect("Root node id should always refer to a node").metadata.position + IVec2::new(8, -3)
} else {
document_network.exports_metadata.1
};
// If moved to top of a layer stack, move to the left of the post node. If moved within a stack, move directly on the post node. The stack will be shifted down later.
let offset_to_post_node = if insert_index == 0 {
new_position - current_position - IVec2::new(8, 0)
} else {
new_position - current_position
};
responses.add(GraphOperationMessage::ShiftUpstream {
node_id,
shift: offset_to_post_node,
shift_self: true,
});
match (post_node_id, pre_node_id) {
(Some(post_node_id), Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id,
post_node_input_index,
insert_node_output_index: 0,
insert_node_id: node_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id,
}),
(None, Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: document_network.exports_metadata.0,
post_node_input_index: 0,
insert_node_output_index: 0,
insert_node_id: node_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id,
}),
(Some(post_node_id), None) => responses.add(GraphOperationMessage::SetNodeInput {
node_id: post_node_id,
input_index: post_node_input_index,
input: NodeInput::node(node_id, 0),
}),
(None, None) => {
if let Some(primary_export) = document_network.exports.get_mut(0) {
*primary_export = NodeInput::node(node_id, 0)
}
}
}
// Shift stack down, starting at the moved node.
responses.add(GraphOperationMessage::ShiftUpstream {
node_id,
shift: IVec2::new(0, 3),
shift_self: true,
});
}
GraphOperationMessage::InsertNodeBetween {
post_node_id,
post_node_input_index,
insert_node_output_index,
insert_node_id,
insert_node_input_index,
pre_node_output_index,
pre_node_id,
} => {
let post_node = document_network.nodes.get(&post_node_id);
let Some((post_node_input_index, _)) = post_node
.map_or(&document_network.exports, |post_node| &post_node.inputs)
.iter()
.enumerate()
.filter(|input| input.1.is_exposed())
.nth(post_node_input_index)
else {
error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}");
return;
};
let Some(insert_node) = document_network.nodes.get(&insert_node_id) else {
error!("Insert node not found");
return;
};
let Some((insert_node_input_index, _)) = insert_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(insert_node_input_index) else {
error!("Failed to find input index {insert_node_input_index} on node {insert_node_id:#?}");
return;
};
let post_input = NodeInput::node(insert_node_id, insert_node_output_index);
responses.add(GraphOperationMessage::SetNodeInput {
node_id: post_node_id,
input_index: post_node_input_index,
input: post_input,
});
let insert_input = NodeInput::node(pre_node_id, pre_node_output_index);
responses.add(GraphOperationMessage::SetNodeInput {
node_id: insert_node_id,
input_index: insert_node_input_index,
input: insert_input,
});
}
GraphOperationMessage::OpacitySet { layer, opacity } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run OpacitySet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.opacity_set(opacity);
}
}
GraphOperationMessage::BlendModeSet { layer, blend_mode } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run BlendModeSet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.blend_mode_set(blend_mode);
}
}
GraphOperationMessage::StrokeSet { layer, stroke } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run StrokeSet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.stroke_set(stroke);
}
}
@@ -334,12 +56,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
transform_in,
skip_rerender,
} => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformChange on ROOT_PARENT");
return;
}
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
let parent_transform = network_interface.document_metadata().downstream_transform_to_viewport(layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.transform_change(transform, transform_in, parent_transform, skip_rerender);
}
}
@@ -349,14 +67,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
transform_in,
skip_rerender,
} => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformSet on ROOT_PARENT");
return;
}
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let current_transform = Some(document_metadata.transform_to_viewport(layer));
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
let parent_transform = network_interface.document_metadata().downstream_transform_to_viewport(layer);
let current_transform = Some(network_interface.document_metadata().transform_to_viewport(layer));
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, skip_rerender);
}
}
@@ -365,7 +78,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.pivot_set(pivot);
}
}
@@ -374,96 +87,51 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
log::error!("Cannot run Vector on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.vector_modify(modification_type);
}
}
GraphOperationMessage::Brush { layer, strokes } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run Brush on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.brush_modify(strokes);
}
}
GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent } => {
let Some(group_parent) = new_parent.parent(document_metadata) else {
log::error!("Could not find parent for layer {:?}", new_parent);
GraphOperationMessage::SetUpstreamToChain { layer } => {
let Some(first_chain_node) = network_interface
.upstream_flow_back_from_nodes(
vec![layer.to_node()],
&[],
crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow,
)
.nth(1)
else {
return;
};
// Create a vec of nodes to move with all selected layers in the parent layer child stack, as well as each non layer sibling directly upstream of the selected layer
let mut selected_siblings = Vec::new();
// Skip over horizontal non layer node chain that feeds into parent
let Some(mut current_stack_node_id) = group_parent.first_child(document_metadata).map(|current_stack_node| current_stack_node.to_node()) else {
log::error!("Folder should always have child");
return;
};
let current_stack_node_id = &mut current_stack_node_id;
loop {
let mut current_stack_node = document_network.nodes.get(current_stack_node_id).expect("Current stack node id should always be a node");
// Check if the current stack node is a selected layer
if selected_nodes
.selected_layers(document_metadata)
.any(|selected_node_id| selected_node_id.to_node() == *current_stack_node_id)
{
selected_siblings.push(*current_stack_node_id);
// Push all non layer sibling nodes directly upstream of the selected layer
loop {
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.first() else { break };
let next_node = document_network.nodes.get(node_id).expect("Stack node id should always be a node");
// If the next node is a layer, immediately break and leave current stack node as the non layer node
if next_node.is_layer {
break;
}
*current_stack_node_id = *node_id;
current_stack_node = next_node;
selected_siblings.push(*current_stack_node_id);
}
}
// Get next node
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.first() else { break };
*current_stack_node_id = *node_id;
}
// Start with the furthest upstream node, move it as a child of the new folder, and continue downstream for each layer in vec
for node_to_move in selected_siblings.iter().rev() {
// Disconnect node, then reconnect as new child
responses.add(GraphOperationMessage::DisconnectNodeFromStack {
node_id: *node_to_move,
reconnect_to_sibling: true,
});
responses.add(GraphOperationMessage::InsertNodeAtStackIndex {
node_id: *node_to_move,
parent: new_parent,
insert_index: 0,
});
}
let Some(most_upstream_sibling) = selected_siblings.last() else {
return;
};
responses.add(GraphOperationMessage::DisconnectInput {
node_id: *most_upstream_sibling,
input_index: 0,
});
network_interface.force_set_upstream_to_chain(&first_chain_node, &[]);
}
GraphOperationMessage::NewArtboard { id, artboard } => {
if let Some(artboard_id) = ModifyInputsContext::create_artboard(node_graph, document_network, id, artboard) {
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![artboard_id] });
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let artboard_layer = modify_inputs.create_artboard(id, artboard);
network_interface.move_layer_to_stack(artboard_layer, LayerNodeIdentifier::ROOT_PARENT, 0, &[]);
// If there is a non artboard feeding into the primary input of the artboard, move it to the secondary input
let Some(artboard) = network_interface.network(&[]).unwrap().nodes.get(&id) else {
log::error!("Artboard not created");
return;
};
let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();
if let NodeInput::Node { node_id, .. } = &primary_input {
if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) {
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface), artboard_layer, 0, &[]);
} else {
network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]);
network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]);
}
}
load_network_structure(document_network, document_metadata, collapsed);
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::NewBitmapLayer {
id,
@@ -471,71 +139,38 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
ModifyInputsContext::insert_image_data(node_graph, document_network, image_frame, layer, responses);
}
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_image_data(image_frame, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::NewCustomLayer {
id,
nodes,
parent,
insert_index,
alias,
} => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
if !nodes.is_empty() {
// Add the nodes to the network
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
// Since all the new nodes are already connected, just connect the input of the layer to first new node
let first_new_node_id = new_ids[&NodeId(0)];
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
if let Some(node) = modify_inputs.document_network.nodes.get_mut(&id) {
node.alias.clone_from(&alias);
}
let shift = nodes
.get(&NodeId(0))
.and_then(|node| {
modify_inputs
.document_network
.nodes
.get(&layer)
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
.unwrap_or_default();
for (old_id, mut document_node) in nodes {
// Shift copied node
document_node.metadata.position += shift;
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
let default_inputs = NodeGraphMessageHandler::get_default_inputs(document_network, &Vec::new(), node_id, &node_graph.resolved_types, &document_node);
document_node = document_node.map_ids(default_inputs, &new_ids);
// Insert node into network
node_graph.insert_node(node_id, document_node, document_network, &Vec::new());
node_graph.update_click_target(node_id, document_network, Vec::new());
}
if let Some(layer_node) = document_network.nodes.get_mut(&layer) {
if let Some(&input) = new_ids.get(&NodeId(0)) {
layer_node.inputs[1] = NodeInput::node(input, 0);
}
}
responses.add(NodeGraphMessage::RunDocumentGraph);
} else {
error!("Creating new custom layer failed");
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(layer.to_node(), 1),
input: NodeInput::node(first_new_node_id, 0),
});
}
load_network_structure(document_network, document_metadata, collapsed);
// Move the layer and all nodes to the correct position in the network
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
modify_inputs.insert_vector_data(subpaths, layer);
}
load_network_structure(document_network, document_metadata, collapsed);
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_vector_data(subpaths, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::NewTextLayer {
id,
@@ -545,22 +180,25 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
parent,
insert_index,
} => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) {
modify_inputs.insert_text(text, font, size, layer);
}
load_network_structure(document_network, document_metadata, collapsed);
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_text(text, font, size, layer);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(id, document_network, document_metadata, node_graph, responses) {
GraphOperationMessage::ResizeArtboard { layer, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.resize_artboard(location, dimensions);
}
}
GraphOperationMessage::ClearArtboards => {
for &artboard in document_metadata.all_artboards() {
responses.add(GraphOperationMessage::DeleteLayer { layer: artboard, reconnect: true });
for artboard in network_interface.all_artboards() {
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![artboard.to_node()],
reconnect: false,
});
}
load_network_structure(document_network, document_metadata, collapsed);
// TODO: Replace deleted artboards with merge nodes
}
GraphOperationMessage::NewSvg {
id,
@@ -580,112 +218,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
return;
}
};
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
import_usvg_node(&mut modify_inputs, &usvg::Node::Group(Box::new(tree.root().clone())), transform, id, parent, insert_index);
load_network_structure(document_network, document_metadata, collapsed);
}
GraphOperationMessage::SetNodePosition { node_id, position } => {
let Some(node) = document_network.nodes.get_mut(&node_id) else {
log::error!("Failed to find node {node_id} when setting position");
return;
};
node.metadata.position = position;
node_graph.update_click_target(node_id, document_network, Vec::new());
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
}
GraphOperationMessage::SetName { layer, name } => {
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::SetNameImpl { layer, name });
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::SetNameImpl { layer, name } => {
if let Some(node) = document_network.nodes.get_mut(&layer.to_node()) {
node.alias = name;
if let Some(node_metadata) = node_graph.node_metadata.get_mut(&layer.to_node()) {
node_metadata.layer_width = Some(NodeGraphMessageHandler::layer_width_cells(node));
};
node_graph.update_click_target(layer.to_node(), document_network, Vec::new());
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
GraphOperationMessage::SetNodeInput { node_id, input_index, input } => {
if ModifyInputsContext::set_input(node_graph, document_network, &Vec::new(), node_id, input_index, input, true) {
load_network_structure(document_network, document_metadata, collapsed);
}
}
GraphOperationMessage::ShiftUpstream { node_id, shift, shift_self } => {
ModifyInputsContext::shift_upstream(node_graph, document_network, &Vec::new(), node_id, shift, shift_self);
}
GraphOperationMessage::ToggleSelectedVisibility => {
responses.add(DocumentMessage::StartTransaction);
// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
let visible = !selected_nodes.selected_layers(document_metadata).all(|layer| document_metadata.node_is_visible(layer.to_node()));
for layer in selected_nodes.selected_layers(document_metadata) {
responses.add(GraphOperationMessage::SetVisibility { node_id: layer.to_node(), visible });
}
}
GraphOperationMessage::ToggleVisibility { node_id } => {
let visible = !document_metadata.node_is_visible(node_id);
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::SetVisibility { node_id, visible });
}
GraphOperationMessage::SetVisibility { node_id, visible } => {
// Set what we determined shall be the visibility of the node
let Some(node) = document_network.nodes.get_mut(&node_id) else {
log::error!("Could not get node {:?} in GraphOperationMessage::SetVisibility", node_id);
return;
};
node.visible = visible;
// Only generate node graph if one of the selected nodes is connected to the output
if document_network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
document_metadata.load_structure(document_network);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(PropertiesPanelMessage::Refresh);
}
GraphOperationMessage::StartPreviewingWithoutRestore => {
document_network.start_previewing_without_restore();
}
GraphOperationMessage::ToggleSelectedLocked => {
responses.add(DocumentMessage::StartTransaction);
// If any of the selected nodes are locked, show them all. Otherwise, hide them all.
let locked = !selected_nodes.selected_layers(document_metadata).all(|layer| document_metadata.node_is_locked(layer.to_node()));
for layer in selected_nodes.selected_layers(document_metadata) {
responses.add(GraphOperationMessage::SetLocked { node_id: layer.to_node(), locked });
}
}
GraphOperationMessage::ToggleLocked { node_id } => {
let Some(node) = document_network.nodes.get(&node_id) else {
log::error!("Cannot get node {:?} in GraphOperationMessage::ToggleLocked", node_id);
return;
};
let locked = !node.locked;
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::SetLocked { node_id, locked });
}
GraphOperationMessage::SetLocked { node_id, locked } => {
let Some(node) = document_network.nodes.get_mut(&node_id) else { return };
node.locked = locked;
if document_network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
document_metadata.load_structure(document_network);
responses.add(NodeGraphMessage::SelectedNodesUpdated)
}
}
}
@@ -695,11 +230,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
}
}
pub fn load_network_structure(document_network: &NodeNetwork, document_metadata: &mut DocumentMetadata, collapsed: &mut CollapsedLayers) {
document_metadata.load_structure(document_network);
collapsed.0.retain(|&layer| document_metadata.layer_exists(layer));
}
fn usvg_color(c: usvg::Color, a: f32) -> Color {
Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)
}
@@ -708,15 +238,13 @@ fn usvg_transform(c: usvg::Transform) -> DAffine2 {
DAffine2::from_cols_array(&[c.sx as f64, c.ky as f64, c.kx as f64, c.sy as f64, c.tx as f64, c.ty as f64])
}
fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, transform: DAffine2, id: NodeId, parent: LayerNodeIdentifier, insert_index: isize) {
let Some(layer) = modify_inputs.create_layer(id, parent, insert_index) else {
return;
};
fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, transform: DAffine2, id: NodeId, parent: LayerNodeIdentifier, insert_index: usize) {
let layer = modify_inputs.create_layer(id);
modify_inputs.layer_node = Some(layer);
match node {
usvg::Node::Group(group) => {
for child in group.children() {
import_usvg_node(modify_inputs, child, transform, NodeId(generate_uuid()), LayerNodeIdentifier::new_unchecked(layer), -1);
import_usvg_node(modify_inputs, child, transform, NodeId(generate_uuid()), layer, 0);
}
modify_inputs.layer_node = Some(layer);
}
@@ -725,9 +253,12 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();
modify_inputs.insert_vector_data(subpaths, layer);
modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
transform_utils::update_transform(inputs, transform * usvg_transform(node.abs_transform()));
});
modify_inputs.network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
if let Some(transform_node_id) = modify_inputs.get_existing_node_id("Transform") {
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, transform * usvg_transform(node.abs_transform()));
}
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
apply_usvg_fill(path.fill(), modify_inputs, transform * usvg_transform(node.abs_transform()), bounds_transform);
apply_usvg_stroke(path.stroke(), modify_inputs);
@@ -1,5 +1,7 @@
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, NodeInput};
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
use graphene_core::vector::PointId;
use glam::{DAffine2, DVec2};
@@ -30,13 +32,13 @@ pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64
}
/// Update the inputs of the transform node to match a new transform
pub fn update_transform(inputs: &mut [NodeInput], transform: DAffine2) {
pub fn update_transform(network_interface: &mut NodeNetworkInterface, node_id: &NodeId, transform: DAffine2) {
let (scale, angle, translation, shear) = compute_scale_angle_translation_shear(transform);
inputs[1] = NodeInput::value(TaggedValue::DVec2(translation), false);
inputs[2] = NodeInput::value(TaggedValue::F64(angle), false);
inputs[3] = NodeInput::value(TaggedValue::DVec2(scale), false);
inputs[4] = NodeInput::value(TaggedValue::DVec2(shear), false);
network_interface.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::DVec2(translation), false), &[]);
network_interface.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(angle), false), &[]);
network_interface.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::DVec2(scale), false), &[]);
network_interface.set_input(&InputConnector::node(*node_id, 4), NodeInput::value(TaggedValue::DVec2(shear), false), &[]);
}
// TODO: This should be extracted from the graph at the location of the transform node.
File diff suppressed because it is too large Load Diff
@@ -6,8 +6,8 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
use crate::messages::portfolio::document::navigation::utility_types::NavigationOperation;
use crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata;
use crate::messages::portfolio::document::utility_types::misc::PTZ;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
@@ -16,14 +16,12 @@ use graph_craft::document::NodeId;
use glam::{DAffine2, DVec2};
pub struct NavigationMessageData<'a> {
pub metadata: &'a DocumentMetadata,
pub network_interface: &'a mut NodeNetworkInterface,
pub breadcrumb_network_path: &'a [NodeId],
pub ipp: &'a InputPreprocessorMessageHandler,
pub selection_bounds: Option<[DVec2; 2]>,
pub document_ptz: &'a mut PTZ,
pub node_graph_ptz: &'a mut HashMap<Vec<NodeId>, PTZ>,
pub graph_view_overlay_open: bool,
pub node_graph_handler: &'a NodeGraphMessageHandler,
pub node_graph_to_viewport: &'a DAffine2,
}
#[derive(Debug, Clone, PartialEq, Default)]
@@ -36,24 +34,46 @@ pub struct NavigationMessageHandler {
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
let NavigationMessageData {
metadata,
network_interface,
breadcrumb_network_path,
ipp,
selection_bounds,
document_ptz,
node_graph_ptz,
graph_view_overlay_open,
node_graph_handler,
node_graph_to_viewport,
} = data;
let ptz = if !graph_view_overlay_open {
document_ptz
} else {
node_graph_ptz.entry(node_graph_handler.network.clone()).or_insert(PTZ::default())
fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {
if !graph_view_overlay_open {
Some(document_ptz)
} else {
let network_metadata = network_interface.network_metadata(breadcrumb_network_path)?;
Some(&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz)
}
}
fn get_ptz_mut<'a>(document_ptz: &'a mut PTZ, network_interface: &'a mut NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a mut PTZ> {
if !graph_view_overlay_open {
Some(document_ptz)
} else {
let Some(node_graph_ptz) = network_interface.node_graph_ptz_mut(breadcrumb_network_path) else {
log::error!("Could not get node graph PTZ in NavigationMessageHandler process_message");
return None;
};
Some(node_graph_ptz)
}
}
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get PTZ in NavigationMessageHandler process_message");
return;
};
let old_zoom = ptz.zoom();
match message {
NavigationMessage::BeginCanvasPan => {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
responses.add(FrontendMessage::UpdateInputHints {
@@ -64,6 +84,9 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
self.navigation_operation = NavigationOperation::Pan { pan_original_for_abort: ptz.pan };
}
NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu } => {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
// If the node graph is open, prevent tilt and instead start panning
if graph_view_overlay_open {
responses.add(NavigationMessage::BeginCanvasPan);
@@ -94,6 +117,10 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
}
}
NavigationMessage::BeginCanvasZoom => {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::ZoomIn });
responses.add(FrontendMessage::UpdateInputHints {
hint_data: HintData(vec![
@@ -117,24 +144,27 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
self.mouse_position = ipp.mouse.position;
}
NavigationMessage::CanvasPan { delta } => {
let transformed_delta = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse().transform_vector2(delta)
} else {
node_graph_to_viewport.inverse().transform_vector2(delta)
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get PTZ in CanvasPan");
return;
};
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
ptz.pan += transformed_delta;
responses.add(BroadcastEvent::CanvasTransformed);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(DocumentMessage::PTZUpdate);
}
NavigationMessage::CanvasPanByViewportFraction { delta } => {
let transformed_delta = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size())
} else {
node_graph_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size())
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction");
return;
};
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
ptz.pan += transformed_delta;
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(DocumentMessage::PTZUpdate);
}
NavigationMessage::CanvasPanMouseWheel { use_y_as_x } => {
let delta = match use_y_as_x {
@@ -144,16 +174,28 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
responses.add(NavigationMessage::CanvasPan { delta });
}
NavigationMessage::CanvasTiltResetAndZoomTo100Percent => {
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get mutable PTZ in CanvasTiltResetAndZoomTo100Percent");
return;
};
ptz.tilt = 0.;
ptz.set_zoom(1.);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(DocumentMessage::PTZUpdate);
}
NavigationMessage::CanvasTiltSet { angle_radians } => {
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get mutable PTZ in CanvasTiltSet");
return;
};
ptz.tilt = angle_radians;
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(DocumentMessage::PTZUpdate);
}
NavigationMessage::CanvasZoomDecrease { center_on_mouse } => {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom()).unwrap_or(&ptz.zoom());
if center_on_mouse {
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position));
@@ -161,6 +203,10 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale });
}
NavigationMessage::CanvasZoomIncrease { center_on_mouse } => {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom()).unwrap_or(&ptz.zoom());
if center_on_mouse {
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position));
@@ -175,10 +221,14 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
}
let document_bounds = if !graph_view_overlay_open {
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
metadata.document_bounds_viewport_space()
network_interface.document_metadata().document_bounds_viewport_space()
} else {
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
network_interface.graph_bounds_viewport_space(breadcrumb_network_path)
};
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
zoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, ipp);
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
@@ -189,17 +239,25 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
NavigationMessage::CanvasZoomSet { zoom_factor } => {
let document_bounds = if !graph_view_overlay_open {
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
metadata.document_bounds_viewport_space()
network_interface.document_metadata().document_bounds_viewport_space()
} else {
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
network_interface.graph_bounds_viewport_space(breadcrumb_network_path)
};
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get mutable PTZ in CanvasZoomSet");
return;
};
let zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, ipp);
ptz.set_zoom(zoom);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(DocumentMessage::PTZUpdate);
}
NavigationMessage::EndCanvasPTZ { abort_transform } => {
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get mutable PTZ in EndCanvasPTZ");
return;
};
// If an abort was requested, reset the active PTZ value to its original state
if abort_transform && self.navigation_operation != NavigationOperation::None {
match self.navigation_operation {
@@ -215,7 +273,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
}
}
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(DocumentMessage::PTZUpdate);
}
// Final chance to apply snapping if the key was pressed during this final frame
@@ -248,18 +306,20 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
return;
}
let transform = (if graph_view_overlay_open { *node_graph_to_viewport } else { metadata.document_to_viewport }).inverse();
let (v1, v2) = (transform.transform_point2(DVec2::ZERO), transform.transform_point2(ipp.viewport_bounds.size()));
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction");
return;
};
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
let v1 = document_to_viewport.inverse().transform_point2(DVec2::ZERO);
let v2 = document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size());
let center = ((v2 + v1) - (pos2 + pos1)) / 2.;
let size = (v2 - v1) / diagonal;
let new_scale = size.min_element();
let viewport_change = if !graph_view_overlay_open {
metadata.document_to_viewport.transform_vector2(center)
} else {
node_graph_to_viewport.transform_vector2(center)
};
let viewport_change = document_to_viewport.transform_vector2(center);
// Only change the pan if the change will be visible in the viewport
if viewport_change.x.abs() > 0.5 || viewport_change.y.abs() > 0.5 {
@@ -275,17 +335,17 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
}
responses.add(PortfolioMessage::UpdateDocumentWidgets);
self.create_document_transform(ipp.viewport_bounds.center(), ptz, responses);
responses.add(DocumentMessage::PTZUpdate);
}
NavigationMessage::FitViewportToSelection => {
if let Some(bounds) = selection_bounds {
let transform = if !graph_view_overlay_open {
metadata.document_to_viewport.inverse()
} else {
node_graph_to_viewport.inverse()
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get node graph PTZ in FitViewportToSelection");
return;
};
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
responses.add(NavigationMessage::FitViewportToBounds {
bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])],
bounds: [document_to_viewport.inverse().transform_point2(bounds[0]), document_to_viewport.inverse().transform_point2(bounds[1])],
prevent_zoom_past_100: false,
})
}
@@ -310,6 +370,10 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
tilt_raw_not_snapped + angle
};
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get mutable PTZ in Tilt");
return;
};
ptz.tilt = self.snapped_tilt(tilt_raw_not_snapped);
let snap = ipp.keyboard.get(snap as usize);
@@ -334,13 +398,17 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
let document_bounds = if !graph_view_overlay_open {
// TODO: Cache this in node graph coordinates and apply the transform to the rectangle to get viewport coordinates
metadata.document_bounds_viewport_space()
network_interface.document_metadata().document_bounds_viewport_space()
} else {
node_graph_handler.graph_bounds_viewport_space(*node_graph_to_viewport)
network_interface.graph_bounds_viewport_space(breadcrumb_network_path)
};
updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, ipp)
};
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get mutable PTZ in Zoom");
return;
};
ptz.set_zoom(self.snapped_zoom(zoom_raw_not_snapped));
let snap = ipp.keyboard.get(snap as usize);
@@ -413,7 +481,11 @@ impl NavigationMessageHandler {
}
}
pub fn calculate_offset_transform(&self, viewport_center: DVec2, pan: DVec2, tilt: f64, zoom: f64) -> DAffine2 {
pub fn calculate_offset_transform(&self, viewport_center: DVec2, ptz: &PTZ) -> DAffine2 {
let pan = ptz.pan;
let tilt = ptz.tilt;
let zoom = ptz.zoom();
let scaled_center = viewport_center / self.snapped_zoom(zoom);
// Try to avoid fractional coordinates to reduce anti aliasing.
@@ -428,11 +500,6 @@ impl NavigationMessageHandler {
scale_transform * offset_transform * angle_transform * translation_transform
}
fn create_document_transform(&self, viewport_center: DVec2, ptz: &PTZ, responses: &mut VecDeque<Message>) {
let transform = self.calculate_offset_transform(viewport_center, ptz.pan, ptz.tilt, ptz.zoom());
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
}
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
let new_viewport_bounds = viewport_bounds / zoom_factor;
let delta_size = viewport_bounds - new_viewport_bounds;
File diff suppressed because it is too large Load Diff
@@ -1,31 +1,34 @@
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::proto::GraphErrors;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
#[impl_message(Message, DocumentMessage, NodeGraph)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum NodeGraphMessage {
// Messages
AddNodes {
nodes: Vec<(NodeId, NodeTemplate)>,
new_ids: HashMap<NodeId, NodeId>,
},
Init,
SelectedNodesUpdated,
ConnectNodesByWire {
output_node: NodeId,
output_node_connector_index: usize,
input_node: NodeId,
input_node_connector_index: usize,
},
Copy,
CloseCreateNodeMenu,
CreateNode {
CreateNodeFromContextMenu {
node_id: Option<NodeId>,
node_type: String,
x: i32,
y: i32,
},
CreateWire {
output_connector: OutputConnector,
input_connector: InputConnector,
},
Cut,
DeleteNodes {
node_ids: Vec<NodeId>,
@@ -35,17 +38,10 @@ pub enum NodeGraphMessage {
reconnect: bool,
},
DisconnectInput {
node_id: NodeId,
input_index: usize,
input_connector: InputConnector,
},
EnterNestedNetwork,
DuplicateSelectedNodes,
EnforceLayerHasNoMultiParams {
node_id: NodeId,
},
ExitNestedNetwork {
steps_back: usize,
},
ExposeInput {
node_id: NodeId,
input_index: usize,
@@ -53,21 +49,17 @@ pub enum NodeGraphMessage {
},
InsertNode {
node_id: NodeId,
document_node: DocumentNode,
node_template: NodeTemplate,
},
InsertNodeBetween {
post_node_id: NodeId,
post_node_input_index: usize,
insert_node_output_index: usize,
insert_node_id: NodeId,
node_id: NodeId,
input_connector: InputConnector,
insert_node_input_index: usize,
pre_node_output_index: usize,
pre_node_id: NodeId,
},
MoveSelectedNodes {
displacement_x: i32,
displacement_y: i32,
move_upstream: bool,
MoveLayerToStack {
layer: LayerNodeIdentifier,
parent: LayerNodeIdentifier,
insert_index: usize,
},
PasteNodes {
serialized_nodes: String,
@@ -87,6 +79,7 @@ pub enum NodeGraphMessage {
},
PrintSelectedNodeCoordinates,
RunDocumentGraph,
ForceRunDocumentGraph,
SelectedNodesAdd {
nodes: Vec<NodeId>,
},
@@ -96,48 +89,35 @@ pub enum NodeGraphMessage {
SelectedNodesSet {
nodes: Vec<NodeId>,
},
SendClickTargets,
EndSendClickTargets,
SendGraph,
SetInputValue {
node_id: NodeId,
input_index: usize,
value: TaggedValue,
},
SetNodeInput {
node_id: NodeId,
input_index: usize,
SetInput {
input_connector: InputConnector,
input: NodeInput,
},
SetQualifiedInputValue {
SetDisplayName {
node_id: NodeId,
input_index: usize,
value: TaggedValue,
alias: String,
},
/// Move all the downstream nodes to the right in the graph to allow space for a newly inserted node
ShiftNode {
SetDisplayNameImpl {
node_id: NodeId,
},
SetVisibility {
node_id: NodeId,
visible: bool,
},
SetLocked {
node_id: NodeId,
locked: bool,
},
SetName {
node_id: NodeId,
name: String,
},
SetNameImpl {
node_id: NodeId,
name: String,
alias: String,
},
SetToNodeOrLayer {
node_id: NodeId,
is_layer: bool,
},
StartPreviewingWithoutRestore {
node_id: NodeId,
ShiftNodes {
node_ids: Vec<NodeId>,
displacement_x: i32,
displacement_y: i32,
move_upstream: bool,
},
TogglePreview {
node_id: NodeId,
@@ -146,10 +126,28 @@ pub enum NodeGraphMessage {
node_id: NodeId,
},
ToggleSelectedAsLayersOrNodes,
ToggleSelectedLocked,
ToggleLocked {
node_id: NodeId,
},
SetLocked {
node_id: NodeId,
locked: bool,
},
ToggleSelectedVisibility,
ToggleVisibility {
node_id: NodeId,
},
SetVisibility {
node_id: NodeId,
visible: bool,
},
SetLockedOrVisibilitySideEffects {
node_ids: Vec<NodeId>,
},
UpdateEdges,
UpdateBoxSelection,
UpdateLayerPanel,
UpdateNewNodeGraph,
UpdateTypes {
#[serde(skip)]
@@ -157,4 +155,7 @@ pub enum NodeGraphMessage {
#[serde(skip)]
node_graph_errors: GraphErrors,
},
UpdateActionButtons,
UpdateInSelectedNetwork,
SendSelectedNodes,
}
File diff suppressed because it is too large Load Diff
@@ -73,11 +73,7 @@ fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, data_type: FrontendGraphDataType, blank_assist: bool) -> Vec<WidgetHolder> {
let Some(input) = document_node.inputs.get(index) else {
log::warn!(
"A widget named '{name}' for node {} (alias '{}') failed to be built because its node's input index {index} is invalid.",
document_node.name,
document_node.alias
);
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
};
let mut widgets = vec![expose_widget(node_id, index, data_type, input.is_exposed()), TextLabel::new(name).widget_holder()];
@@ -1632,9 +1628,17 @@ pub fn text_properties(document_node: &DocumentNode, node_id: NodeId, _context:
}
pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let imaginate_node = [context.nested_path, &[node_id]].concat();
let imaginate_node = [context.selection_network_path, &[node_id]].concat();
let resolve_input = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
let resolve_input = |name: &str| {
IMAGINATE_NODE
.default_node_template()
.persistent_node_metadata
.input_names
.iter()
.position(|input| input == name)
.unwrap_or_else(|| panic!("Input {name} not found"))
};
let seed_index = resolve_input("Seed");
let resolution_index = resolve_input("Resolution");
let samples_index = resolve_input("Samples");
@@ -1830,7 +1834,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let image_size = context
.executor
.introspect_node_in_network(
context.document_network,
context.network_interface.network(&[]).unwrap(),
&imaginate_node,
|network| {
network
@@ -2093,12 +2097,16 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
layout
}
fn unknown_node_properties(document_node: &DocumentNode) -> Vec<LayoutGroup> {
string_properties(format!("Node '{}' cannot be found in library", document_node.name))
fn unknown_node_properties(reference: &String) -> Vec<LayoutGroup> {
string_properties(format!("Node '{}' cannot be found in library", reference))
}
pub fn node_no_properties(document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
string_properties(if document_node.is_layer { "Layer has no properties" } else { "Node has no properties" })
pub fn node_no_properties(_document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
string_properties(if context.network_interface.is_layer(&node_id, context.selection_network_path) {
"Layer has no properties"
} else {
"Node has no properties"
})
}
pub fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
@@ -2108,13 +2116,18 @@ pub fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context:
}
pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup {
let name = document_node.name.clone();
let layout = match super::document_node_types::resolve_document_node_type(&name) {
Some(document_node_type) => (document_node_type.properties)(document_node, node_id, context),
None => unknown_node_properties(document_node),
let reference = context.network_interface.reference(&node_id, context.selection_network_path).clone();
let layout = if let Some(ref reference) = reference {
match super::document_node_types::resolve_document_node_type(reference) {
Some(document_node_type) => (document_node_type.properties)(document_node, node_id, context),
None => unknown_node_properties(reference),
}
} else {
node_no_properties(document_node, node_id, context)
};
LayoutGroup::Section {
name,
name: reference.unwrap_or_default(),
visible: document_node.visible,
id: node_id.0,
layout,
@@ -1,7 +1,8 @@
use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeId;
use graphene_core::Type;
use graphene_std::renderer::ClickTarget;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
@@ -43,7 +44,8 @@ pub struct FrontendGraphInput {
pub name: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
pub connected: Option<NodeId>,
#[serde(rename = "connectedTo")]
pub connected_to: Option<OutputConnector>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -53,9 +55,8 @@ pub struct FrontendGraphOutput {
pub name: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
pub connected: Vec<NodeId>,
#[serde(rename = "connectedIndex")]
pub connected_index: Vec<usize>,
#[serde(rename = "connectedTo")]
pub connected_to: Vec<InputConnector>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -65,8 +66,9 @@ pub struct FrontendNode {
pub is_layer: bool,
#[serde(rename = "canBeLayer")]
pub can_be_layer: bool,
pub alias: String,
pub name: String,
pub reference: Option<String>,
#[serde(rename = "displayName")]
pub display_name: String,
#[serde(rename = "primaryInput")]
pub primary_input: Option<FrontendGraphInput>,
#[serde(rename = "exposedInputs")]
@@ -87,13 +89,9 @@ pub struct FrontendNode {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeWire {
#[serde(rename = "wireStart")]
pub wire_start: NodeId,
#[serde(rename = "wireStartOutputIndex")]
pub wire_start_output_index: usize,
pub wire_start: OutputConnector,
#[serde(rename = "wireEnd")]
pub wire_end: NodeId,
#[serde(rename = "wireEndInputIndex")]
pub wire_end_input_index: usize,
pub wire_end: InputConnector,
pub dashed: bool,
}
@@ -168,16 +166,16 @@ pub struct ContextMenuInformation {
pub context_menu_data: ContextMenuData,
}
#[derive(Debug, Clone)]
pub struct NodeMetadata {
/// Cache for all node click targets in node graph space. Ensure `update_click_target` is called when modifying a node property that changes its size. Currently this is `alias`, `inputs`, `is_layer`, and `metadata`.
pub node_click_target: ClickTarget,
/// Cache for all node inputs. Should be automatically updated when `update_click_target` is called.
pub input_click_targets: Vec<ClickTarget>,
/// Cache for all node outputs. Should be automatically updated when `update_click_target` is called.
pub output_click_targets: Vec<ClickTarget>,
/// Cache for all visibility buttons. Should be automatically updated when `update_click_target` is called.
pub visibility_click_target: Option<ClickTarget>,
/// Stores the width in grid cell units for layer nodes from the left edge of the thumbnail (+12px padding since thumbnail ends between grid spaces) to the end of the node.
pub layer_width: Option<u32>,
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendClickTargets {
#[serde(rename = "nodeClickTargets")]
pub node_click_targets: Vec<String>,
#[serde(rename = "layerClickTargets")]
pub layer_click_targets: Vec<String>,
#[serde(rename = "portClickTargets")]
pub port_click_targets: Vec<String>,
#[serde(rename = "visibilityClickTargets")]
pub visibility_click_targets: Vec<String>,
#[serde(rename = "allNodesBoundingBox")]
pub all_nodes_bounding_box: String,
}
@@ -15,7 +15,8 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
return;
};
let document_to_viewport = document.metadata().document_to_viewport;
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
for primary in 0..2 {
@@ -57,7 +58,8 @@ fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_conte
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
return;
};
let document_to_viewport = document.metadata().document_to_viewport;
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
let min = bounds.0.iter().map(|corner| corner.y).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
@@ -92,7 +94,8 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
let grid_color = document.snapping_state.grid.grid_color;
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
let origin = document.snapping_state.grid.origin;
let document_to_viewport = document.metadata().document_to_viewport;
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
let tan_a = angle_a.to_radians().tan();
let tan_b = angle_b.to_radians().tan();
@@ -142,7 +145,8 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
let grid_color = document.snapping_state.grid.grid_color;
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
let origin = document.snapping_state.grid.origin;
let document_to_viewport = document.metadata().document_to_viewport;
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
let tan_a = angle_a.to_radians().tan();
let tan_b = angle_b.to_radians().tan();
@@ -24,10 +24,11 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
}
pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
for layer in document.selected_nodes.selected_layers(document.metadata()) {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
continue;
};
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let transform = document.metadata().transform_to_viewport(layer);
let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
@@ -62,10 +63,11 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape
}
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
for layer in document.selected_nodes.selected_layers(document.metadata()) {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
continue;
};
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let transform = document.metadata().transform_to_viewport(layer);
let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
@@ -10,12 +10,10 @@ pub struct PropertiesPanelMessageHandler {}
impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'a>)> for PropertiesPanelMessageHandler {
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
let PropertiesPanelMessageHandlerData {
node_graph_message_handler,
executor,
document_network: network,
document_metadata: metadata,
selected_nodes,
network_interface,
selection_path,
document_name,
executor,
} = data;
match message {
@@ -33,13 +31,12 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let mut context = NodePropertiesContext {
persistent_data,
responses,
nested_path: &node_graph_message_handler.network,
executor,
document_network: network,
metadata,
network_interface,
selection_network_path: selection_path,
};
let properties_sections = node_graph_message_handler.collate_properties(&mut context, selected_nodes);
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut context);
let options_bar = vec![LayoutGroup::Row {
widgets: vec![
@@ -1,15 +1,11 @@
use crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata;
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::prelude::NodeGraphMessageHandler;
use graph_craft::document::NodeId;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::document::NodeNetwork;
pub struct PropertiesPanelMessageHandlerData<'a> {
pub network_interface: &'a NodeNetworkInterface,
pub selection_path: &'a [NodeId],
pub document_name: &'a str,
pub document_network: &'a NodeNetwork,
pub document_metadata: &'a mut DocumentMetadata,
pub selected_nodes: &'a SelectedNodes,
pub node_graph_message_handler: &'a NodeGraphMessageHandler,
pub executor: &'a mut NodeGraphExecutor,
}
@@ -1,7 +1,6 @@
use graph_craft::document::DocumentNode;
use graph_craft::document::NodeId;
use super::network_interface::NodeTemplate;
use std::collections::HashMap;
use graph_craft::document::NodeId;
#[repr(u8)]
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
@@ -17,10 +16,9 @@ pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CopyBufferEntry {
pub nodes: HashMap<NodeId, DocumentNode>,
pub nodes: Vec<(NodeId, NodeTemplate)>,
pub selected: bool,
pub visible: bool,
pub locked: bool,
pub collapsed: bool,
pub alias: String,
}
@@ -1,9 +1,8 @@
use super::nodes::SelectedNodes;
use super::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::graph_modification_utils;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::FlowType;
use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::document::NodeId;
use graphene_core::renderer::ClickTarget;
use graphene_core::renderer::Quad;
use graphene_core::transform::Footprint;
@@ -11,7 +10,7 @@ use graphene_std::vector::PointId;
use graphene_std::vector::VectorData;
use glam::{DAffine2, DVec2};
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::num::NonZeroU64;
// ================
@@ -22,14 +21,11 @@ use std::num::NonZeroU64;
// TODO: it might be better to have a system that can query the state of the node network on demand.
#[derive(Debug, Clone)]
pub struct DocumentMetadata {
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
artboards: HashSet<LayerNodeIdentifier>,
folders: HashSet<LayerNodeIdentifier>,
hidden: HashSet<NodeId>,
locked: HashSet<NodeId>,
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
vector_modify: HashMap<NodeId, VectorData>,
pub upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub vector_modify: HashMap<NodeId, VectorData>,
// TODO: Remove and derive from document_ptz in document message handler
/// Transform from document space to viewport space.
pub document_to_viewport: DAffine2,
}
@@ -39,10 +35,6 @@ impl Default for DocumentMetadata {
Self {
upstream_transforms: HashMap::new(),
structure: HashMap::new(),
artboards: HashSet::new(),
folders: HashSet::new(),
hidden: HashSet::new(),
locked: HashSet::new(),
vector_modify: HashMap::new(),
click_targets: HashMap::new(),
document_to_viewport: DAffine2::IDENTITY,
@@ -67,9 +59,10 @@ impl DocumentMetadata {
self.click_targets.get(&layer)
}
/// Get vector data after the modification is appled
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<VectorData> {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network);
// TODO: Move into network interface so that it does not have to be passed as an argument
/// Get vector data after the modification is applied
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<VectorData> {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
if let Some(vector_data) = graph_layer.upstream_node_id_from_name("Path").and_then(|node| self.vector_modify.get(&node)) {
let mut modified = vector_data.clone();
@@ -93,164 +86,7 @@ impl DocumentMetadata {
fn get_structure_mut(&mut self, node_identifier: LayerNodeIdentifier) -> &mut NodeRelations {
self.structure.entry(node_identifier).or_default()
}
/// Layers excluding ones that are children of other layers in the list.
pub fn shallowest_unique_layers(&self, layers: impl Iterator<Item = 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<_>>();
// Sorting here creates groups of similar UUID paths
sorted_layers.sort();
sorted_layers.dedup_by(|a, b| a.starts_with(b));
sorted_layers
}
/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root. Skips selected non-folder, non-artboard layers
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>, include_self: bool) -> Option<LayerNodeIdentifier> {
layers
.map(|layer| {
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
layer_path.reverse();
if !include_self || !self.is_artboard(layer) {
layer_path.pop();
}
layer_path
})
.reduce(|mut a, b| {
a.truncate(a.iter().zip(b.iter()).position(|(&a, &b)| a != b).unwrap_or_else(|| a.len().min(b.len())));
a
})
.and_then(|layer| layer.last().copied())
}
pub fn active_artboard(&self) -> LayerNodeIdentifier {
self.artboards.iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT_PARENT)
}
pub fn all_artboards(&self) -> &HashSet<LayerNodeIdentifier> {
&self.artboards
}
pub fn is_folder(&self, layer: LayerNodeIdentifier) -> bool {
self.folders.contains(&layer)
}
pub fn is_artboard(&self, layer: LayerNodeIdentifier) -> bool {
self.artboards.contains(&layer)
}
pub fn node_is_visible(&self, layer: NodeId) -> bool {
!self.hidden.contains(&layer)
}
pub fn node_is_locked(&self, layer: NodeId) -> bool {
self.locked.contains(&layer)
}
/// Folders sorted from most nested to least nested
pub fn folders_sorted_by_most_nested(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Vec<LayerNodeIdentifier> {
let mut folders: Vec<_> = layers.filter(|layer| self.folders.contains(layer)).collect();
folders.sort_by_cached_key(|a| std::cmp::Reverse(a.ancestors(self).count()));
folders
}
}
// ==============================================
// DocumentMetadata: Selected layer modifications
// ==============================================
impl DocumentMetadata {
/// 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_PARENT, NodeRelations::default())]);
self.artboards = HashSet::new();
self.folders = HashSet::new();
self.hidden = HashSet::new();
self.locked = HashSet::new();
// Should refer to output node
let mut awaiting_horizontal_flow = vec![(NodeId(u64::MAX), LayerNodeIdentifier::ROOT_PARENT)];
let mut awaiting_primary_flow = vec![];
while let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() {
let horizontal_flow_iter = graph.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], FlowType::HorizontalFlow);
// Skip the horizontal_root_node_id node
for (current_node, current_node_id) in horizontal_flow_iter.skip(if horizontal_root_node_id == NodeId(u64::MAX) { 0 } else { 1 }) {
if !current_node.visible {
self.hidden.insert(current_node_id);
}
if current_node.locked {
self.locked.insert(current_node_id);
}
if current_node.is_layer {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
if !self.structure.contains_key(&current_layer_node) {
awaiting_primary_flow.push((current_node_id, parent_layer_node));
parent_layer_node.push_child(self, current_layer_node);
parent_layer_node = current_layer_node;
if is_artboard(current_layer_node, graph) {
self.artboards.insert(current_layer_node);
}
if graph.nodes.get(&current_layer_node.to_node()).map(|node| node.layer_has_child_layers(graph)).unwrap_or_default() {
self.folders.insert(current_layer_node);
}
}
}
}
while let Some((primary_root_node_id, parent_layer_node)) = awaiting_primary_flow.pop() {
let primary_flow_iter = graph.upstream_flow_back_from_nodes(vec![primary_root_node_id], FlowType::PrimaryFlow);
// Skip the primary_root_node_id node
for (current_node, current_node_id) in primary_flow_iter.skip(1) {
if !current_node.visible {
self.hidden.insert(current_node_id);
}
if current_node.locked {
self.locked.insert(current_node_id);
}
if current_node.is_layer {
// Create a new layer for the top of each stack, and add it as a child to the previous parent
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
if !self.structure.contains_key(&current_layer_node) {
parent_layer_node.push_child(self, current_layer_node);
// The layer nodes for the horizontal flow is itself
awaiting_horizontal_flow.push((current_node_id, current_layer_node));
if is_artboard(current_layer_node, graph) {
self.artboards.insert(current_layer_node);
}
if graph.nodes.get(&current_layer_node.to_node()).map(|node| node.layer_has_child_layers(graph)).unwrap_or_default() {
self.folders.insert(current_layer_node);
}
}
}
}
}
}
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
self.vector_modify.retain(|node, _| graph.nodes.contains_key(node));
}
}
// ============================
// DocumentMetadata: Transforms
// ============================
@@ -351,23 +187,6 @@ impl DocumentMetadata {
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
}
/// Calculates the document bounds in document space
pub fn document_bounds_document_space(&self, include_artboards: bool) -> Option<[DVec2; 2]> {
self.all_layers()
.filter(|&layer| include_artboards || !self.is_artboard(layer))
.filter_map(|layer| self.bounding_box_document(layer))
.reduce(Quad::combine_bounds)
}
/// Calculates the selected layer bounds in document space
pub fn selected_bounds_document_space(&self, include_artboards: bool, selected_nodes: &SelectedNodes) -> Option<[DVec2; 2]> {
selected_nodes
.selected_layers(self)
.filter(|&layer| include_artboards || !self.is_artboard(layer))
.filter_map(|layer| self.bounding_box_document(layer))
.reduce(Quad::combine_bounds)
}
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &bezier_rs::Subpath<PointId>> {
static EMPTY: Vec<ClickTarget> = Vec::new();
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
@@ -398,7 +217,7 @@ impl Default for LayerNodeIdentifier {
}
impl LayerNodeIdentifier {
/// A conceptual node used to represent the UI-only "Export" node
/// A conceptual layer used to represent the parent of layers that feed into the export
pub const ROOT_PARENT: Self = LayerNodeIdentifier::new_unchecked(NodeId(0));
/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
@@ -407,13 +226,13 @@ impl LayerNodeIdentifier {
Self(unsafe { NonZeroU64::new_unchecked(node_id.0 + 1) })
}
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node in the document network
#[track_caller]
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self {
debug_assert!(
network.nodes.get(&node_id).is_some_and(|node| node.is_layer),
network_interface.is_layer(&node_id, &Vec::new()),
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
network.nodes.get(&node_id)
network_interface.network(&[]).unwrap().nodes.get(&node_id)
);
Self::new_unchecked(node_id)
}
@@ -421,6 +240,7 @@ impl LayerNodeIdentifier {
/// Access the node id of this layer
pub fn to_node(self) -> NodeId {
let id = NodeId(u64::from(self.0) - 1);
debug_assert!(id != NodeId(0), "LayerNodeIdentifier::ROOT_PARENT cannot be converted to NodeId");
id
}
@@ -450,7 +270,7 @@ impl LayerNodeIdentifier {
metadata.get_relations(self).and_then(|relations| relations.last_child)
}
/// Does the layer have children?
/// Does the layer have children? If so, then it is a folder
pub fn has_children(self, metadata: &DocumentMetadata) -> bool {
self.first_child(metadata).is_some()
}
@@ -672,7 +492,7 @@ impl<'a> DoubleEndedIterator for DescendantsIter<'a> {
// =============
#[derive(Debug, Clone, Copy, Default)]
struct NodeRelations {
pub struct NodeRelations {
parent: Option<LayerNodeIdentifier>,
previous_sibling: Option<LayerNodeIdentifier>,
next_sibling: Option<LayerNodeIdentifier>,
@@ -684,14 +504,6 @@ struct NodeRelations {
// Helper functions
// ================
pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
if layer == LayerNodeIdentifier::ROOT_PARENT {
return false;
}
let Some(node) = network.nodes.get(&layer.to_node()) else { return false };
node.is_artboard()
}
#[test]
fn test_tree() {
let mut metadata = DocumentMetadata::default();
@@ -404,7 +404,7 @@ impl fmt::Display for SnappingOptions {
}
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct PTZ {
pub pan: DVec2,
@@ -2,5 +2,6 @@ pub mod clipboards;
pub mod document_metadata;
pub mod error;
pub mod misc;
pub mod network_interface;
pub mod nodes;
pub mod transformation;
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use super::network_interface::NodeNetworkInterface;
use graph_craft::document::{NodeId, NodeNetwork};
@@ -33,7 +34,6 @@ impl serde::Serialize for JsRawBuffer {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct LayerPanelEntry {
pub id: NodeId,
pub name: String,
pub alias: String,
pub tooltip: String,
#[serde(rename = "childrenAllowed")]
@@ -50,68 +50,75 @@ pub struct LayerPanelEntry {
pub parents_unlocked: bool,
#[serde(rename = "parentId")]
pub parent_id: Option<NodeId>,
pub selected: bool,
#[serde(rename = "inSelectedNetwork")]
pub in_selected_network: bool,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct SelectedNodes(pub Vec<NodeId>);
impl SelectedNodes {
pub fn layer_visible(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
layer.ancestors(metadata).all(|layer| {
pub fn layer_visible(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {
layer.ancestors(network_interface.document_metadata()).all(|layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
metadata.node_is_visible(layer.to_node())
network_interface.is_visible(&layer.to_node(), &[])
} else {
true
}
})
}
pub fn selected_visible_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(metadata).filter(move |&layer| self.layer_visible(layer, metadata))
pub fn selected_visible_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(network_interface.document_metadata())
.filter(move |&layer| self.layer_visible(layer, network_interface))
}
pub fn layer_locked(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
layer.ancestors(metadata).any(|layer| {
pub fn layer_locked(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {
layer.ancestors(network_interface.document_metadata()).any(|layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
metadata.node_is_locked(layer.to_node())
network_interface.is_locked(&layer.to_node(), &[])
} else {
false
}
})
}
pub fn selected_unlocked_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(metadata).filter(move |&layer| !self.layer_locked(layer, metadata))
pub fn selected_unlocked_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(network_interface.document_metadata())
.filter(move |&layer| !self.layer_locked(layer, network_interface))
}
pub fn selected_visible_and_unlocked_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(metadata)
.filter(move |&layer| self.layer_visible(layer, metadata) && !self.layer_locked(layer, metadata))
pub fn selected_visible_and_unlocked_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(network_interface.document_metadata())
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
}
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
}
pub fn selected_layers_except_artboards<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(metadata).filter(move |&layer| !metadata.is_artboard(layer))
pub fn selected_layers_except_artboards<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(network_interface.document_metadata())
.filter(move |&layer| !network_interface.is_artboard(&layer.to_node(), &[]))
}
pub fn selected_layers_contains(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
self.selected_layers(metadata).any(|selected| selected == layer)
}
// All selected nodes must be in the same network
pub fn selected_nodes<'a>(&'a self, network: &'a NodeNetwork) -> impl Iterator<Item = &NodeId> + '_ {
self.0
.iter()
.filter(|node_id| network.nodes.contains_key(*node_id) || **node_id == network.imports_metadata.0 || **node_id == network.exports_metadata.0)
pub fn selected_nodes(&self) -> impl Iterator<Item = &NodeId> + '_ {
self.0.iter()
}
pub fn selected_nodes_ref(&self) -> &Vec<NodeId> {
&self.0
}
pub fn network_has_selected_nodes(&self, network: &NodeNetwork) -> bool {
self.0.iter().any(|node_id| network.nodes.contains_key(node_id))
}
pub fn has_selected_nodes(&self) -> bool {
!self.0.is_empty()
}
@@ -120,38 +127,11 @@ impl SelectedNodes {
self.0.retain(f);
}
// TODO: This function is run when a node in the layer panel is currently selected, and a new node is selected in the graph, as well as when a node is currently selected in the graph and a node in the layer panel is selected. These are fundamentally different operations, since different nodes should be selected in each case, but cannot be distinguished. Currently it is not possible to shift+click a node in the node graph while a layer is selected. Instead of set_selected_nodes, add_selected_nodes should be used.
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>, document_network: &NodeNetwork, network_path: &[NodeId]) {
let Some(network) = document_network.nested_network(network_path) else { return };
let mut new_nodes = new;
// If any nodes to add are in the document network, clear selected nodes in the current network
if new_nodes.iter().any(|node_to_add| document_network.nodes.contains_key(node_to_add)) {
new_nodes.retain(|selected_node| {
document_network.nodes.contains_key(selected_node) || document_network.imports_metadata.0 == *selected_node || document_network.exports_metadata.0 == *selected_node
});
}
// If not, then clear any nodes that are not in the current network
else {
new_nodes.retain(|selected_node| network.nodes.contains_key(selected_node) || network.imports_metadata.0 == *selected_node || network.exports_metadata.0 == *selected_node);
}
self.0 = new_nodes;
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) {
self.0 = new;
}
pub fn add_selected_nodes(&mut self, new: Vec<NodeId>, document_network: &NodeNetwork, network_path: &[NodeId]) {
let Some(network) = document_network.nested_network(network_path) else { return };
// If the nodes to add are in the document network, clear selected nodes in the current network
if new.iter().any(|node_to_add| document_network.nodes.contains_key(node_to_add)) {
self.retain_selected_nodes(|selected_node| {
document_network.nodes.contains_key(selected_node) || document_network.imports_metadata.0 == *selected_node || document_network.exports_metadata.0 == *selected_node
});
} else {
self.retain_selected_nodes(|selected_node| network.nodes.contains_key(selected_node) || network.imports_metadata.0 == *selected_node || network.exports_metadata.0 == *selected_node);
}
pub fn add_selected_nodes(&mut self, new: Vec<NodeId>) {
self.0.extend(new);
}
@@ -1,3 +1,4 @@
use super::network_interface::NodeNetworkInterface;
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
@@ -6,7 +7,6 @@ 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 graph_craft::document::NodeNetwork;
use graphene_core::renderer::Quad;
use graphene_core::vector::ManipulatorPointId;
use graphene_core::vector::VectorModificationType;
@@ -53,7 +53,9 @@ impl OriginalTransforms {
}
}
pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], document_network: &NodeNetwork, document_metadata: &DocumentMetadata, shape_editor: Option<&'a ShapeState>) {
pub fn update<'a>(&mut self, selected: &'a [LayerNodeIdentifier], network_interface: &NodeNetworkInterface, shape_editor: Option<&'a ShapeState>) {
let document_metadata = network_interface.document_metadata();
match self {
OriginalTransforms::Layer(layer_map) => {
layer_map.retain(|layer, _| selected.contains(layer));
@@ -73,7 +75,7 @@ impl OriginalTransforms {
if path_map.contains_key(&layer) {
continue;
}
let Some(vector_data) = document_metadata.compute_modified_vector(layer, document_network) else {
let Some(vector_data) = network_interface.document_metadata().compute_modified_vector(layer, network_interface) else {
continue;
};
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
@@ -343,8 +345,7 @@ impl TransformOperation {
pub struct Selected<'a> {
pub selected: &'a [LayerNodeIdentifier],
pub responses: &'a mut VecDeque<Message>,
pub document_network: &'a NodeNetwork,
pub document_metadata: &'a DocumentMetadata,
pub network_interface: &'a NodeNetworkInterface,
pub original_transforms: &'a mut OriginalTransforms,
pub pivot: &'a mut DVec2,
pub shape_editor: Option<&'a ShapeState>,
@@ -358,8 +359,7 @@ impl<'a> Selected<'a> {
pivot: &'a mut DVec2,
selected: &'a [LayerNodeIdentifier],
responses: &'a mut VecDeque<Message>,
document_network: &'a NodeNetwork,
document_metadata: &'a DocumentMetadata,
network_interface: &'a NodeNetworkInterface,
shape_editor: Option<&'a ShapeState>,
tool_type: &'a ToolType,
) -> Self {
@@ -368,13 +368,12 @@ impl<'a> Selected<'a> {
*original_transforms = OriginalTransforms::Layer(HashMap::new());
}
original_transforms.update(selected, document_network, document_metadata, shape_editor);
original_transforms.update(selected, network_interface, shape_editor);
Self {
selected,
responses,
document_network,
document_metadata,
network_interface,
original_transforms,
pivot,
shape_editor,
@@ -386,7 +385,7 @@ impl<'a> Selected<'a> {
let xy_summation = self
.selected
.iter()
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document_network, self.document_metadata))
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface))
.reduce(|a, b| a + b)
.unwrap_or_default();
@@ -397,7 +396,7 @@ impl<'a> Selected<'a> {
let [min, max] = self
.selected
.iter()
.filter_map(|&layer| self.document_metadata.bounding_box_viewport(layer))
.filter_map(|&layer| self.network_interface.document_metadata().bounding_box_viewport(layer))
.reduce(Quad::combine_bounds)
.unwrap_or_default();
(min + max) / 2.
@@ -446,14 +445,14 @@ impl<'a> Selected<'a> {
pub fn apply_transformation(&mut self, transformation: DAffine2) {
if !self.selected.is_empty() {
// 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_ancestors in self.document_metadata.shallowest_unique_layers(self.selected.iter().copied()) {
let layer = *layer_ancestors.last().unwrap();
for layer in self.network_interface.shallowest_unique_layers(&[]) {
match &mut self.original_transforms {
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.document_metadata, layer, layer_transforms.get(&layer), transformation, self.responses),
OriginalTransforms::Layer(layer_transforms) => {
Self::transform_layer(self.network_interface.document_metadata(), layer, layer_transforms.get(&layer), transformation, self.responses)
}
OriginalTransforms::Path(path_transforms) => {
if let Some(initial_points) = path_transforms.get_mut(&layer) {
Self::transform_path(self.document_metadata, layer, initial_points, transformation, self.responses)
Self::transform_path(self.network_interface.document_metadata(), layer, initial_points, transformation, self.responses)
}
}
}