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
parent ea44d1440a
commit 0dbbabe73e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 11361 additions and 8011 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,6 @@
// Graph
pub const GRID_SIZE: u32 = 24;
// Viewport
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
pub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.;

View File

@ -165,9 +165,10 @@ impl Dispatcher {
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
}
Message::Tool(message) => {
if let Some(document) = self.message_handlers.portfolio_message_handler.active_document() {
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
if let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) {
let data = ToolMessageData {
document_id: self.message_handlers.portfolio_message_handler.active_document_id().unwrap(),
document_id,
document,
input: &self.message_handlers.input_preprocessor_message_handler,
persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data,
@ -308,12 +309,12 @@ mod test {
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
insert_index: 0,
});
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 4);
@ -337,20 +338,20 @@ mod test {
let mut editor = create_editor_with_three_layers();
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let shape_id = document_before_copy.metadata.all_layers().nth(1).unwrap();
let shape_id = document_before_copy.metadata().all_layers().nth(1).unwrap();
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![shape_id.to_node()] });
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
insert_index: 0,
});
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 4);
@ -376,7 +377,7 @@ mod test {
let mut editor = create_editor_with_three_layers();
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let mut layers = document_before_copy.metadata.all_layers();
let mut layers = document_before_copy.metadata().all_layers();
let rect_id = layers.next().expect("rectangle");
let shape_id = layers.next().expect("shape");
let ellipse_id = layers.next().expect("ellipse");
@ -385,23 +386,23 @@ mod test {
nodes: vec![rect_id.to_node(), ellipse_id.to_node()],
});
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
editor.draw_rect(0., 800., 12., 200.);
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
insert_index: 0,
});
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
insert_index: 0,
});
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_before_copy = document_before_copy.metadata().all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata().all_layers().collect::<Vec<_>>();
assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 6);

View File

@ -70,22 +70,25 @@ impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandl
DialogMessage::RequestExportDialog => {
if let Some(document) = portfolio.active_document() {
let artboards = document
.metadata
.metadata()
.all_layers()
.filter(|&layer| document.metadata.is_artboard(layer))
.filter(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[]))
.map(|layer| {
let name = document
.network
.nodes
.get(&layer.to_node())
.and_then(|node| if node.alias.is_empty() { None } else { Some(node.alias.clone()) })
.network_interface
.node_metadata(&layer.to_node(), &[])
.map(|node| node.persistent_metadata.display_name.clone())
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.unwrap_or_else(|| "Artboard".to_string());
(layer, name)
})
.collect();
self.export_dialog.artboards = artboards;
self.export_dialog.has_selection = document.selected_nodes.selected_layers(document.metadata()).next().is_some();
self.export_dialog.has_selection = document
.network_interface
.selected_nodes(&[])
.is_some_and(|selected_nodes| selected_nodes.selected_layers(document.metadata()).next().is_some());
self.export_dialog.send_dialog_to_frontend(responses);
}
}

View File

@ -1,6 +1,8 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{BoxSelection, ContextMenuInformation, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath};
use crate::messages::portfolio::document::node_graph::utility_types::{
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath,
};
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
@ -121,6 +123,14 @@ pub enum FrontendMessage {
#[serde(rename = "documentId")]
document_id: DocumentId,
},
UpdateImportsExports {
imports: Vec<(FrontendGraphOutput, i32, i32)>,
exports: Vec<(FrontendGraphInput, i32, i32)>,
},
UpdateInSelectedNetwork {
#[serde(rename = "inSelectedNetwork")]
in_selected_network: bool,
},
UpdateBox {
#[serde(rename = "box")]
box_selection: Option<BoxSelection>,
@ -129,9 +139,15 @@ pub enum FrontendMessage {
#[serde(rename = "contextMenuInformation")]
context_menu_information: Option<ContextMenuInformation>,
},
UpdateClickTargets {
#[serde(rename = "clickTargets")]
click_targets: Option<FrontendClickTargets>,
},
UpdateLayerWidths {
#[serde(rename = "layerWidths")]
layer_widths: HashMap<NodeId, u32>,
#[serde(rename = "chainWidths")]
chain_widths: HashMap<NodeId, u32>,
},
UpdateDialogButtons {
#[serde(rename = "layoutTarget")]

View File

@ -69,10 +69,12 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy),
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes),
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=GraphOperationMessage::ToggleSelectedVisibility),
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=GraphOperationMessage::ToggleSelectedLocked),
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility),
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked),
entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedAsLayersOrNodes),
entry!(KeyDown(KeyC); modifiers=[Shift], action_dispatch=NodeGraphMessage::PrintSelectedNodeCoordinates),
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=NodeGraphMessage::SendClickTargets),
entry!(KeyUp(KeyC); action_dispatch=NodeGraphMessage::EndSendClickTargets),
//
// TransformLayerMessage
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),

View File

@ -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,

View File

@ -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,
},
}

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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,
}

View File

@ -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,

View File

@ -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,
}

View File

@ -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();

View File

@ -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));

View File

@ -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![

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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();

View File

@ -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,

View File

@ -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

View File

@ -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);
}

View File

@ -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)
}
}
}

View File

@ -1,4 +1,5 @@
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
use super::utility_types::PanelType;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
@ -81,12 +82,15 @@ pub enum PortfolioMessage {
PasteIntoFolder {
clipboard: Clipboard,
parent: LayerNodeIdentifier,
insert_index: isize,
insert_index: usize,
},
PasteSerializedData {
data: String,
},
PrevDocument,
SetActivePanel {
panel: PanelType,
},
SelectDocument {
document_id: DocumentId,
},
@ -99,6 +103,7 @@ pub enum PortfolioMessage {
},
SubmitGraphRender {
document_id: DocumentId,
ignore_hash: bool,
},
ToggleRulers,
UpdateDocumentWidgets,

View File

@ -1,12 +1,12 @@
use super::utility_types::PersistentData;
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
use super::document::utility_types::network_interface::{self, InputConnector};
use super::utility_types::{PanelType, PersistentData};
use crate::application::generate_uuid;
use crate::consts::DEFAULT_DOCUMENT_NAME;
use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::portfolio::document::DocumentMessageData;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup};
@ -18,6 +18,7 @@ use graphene_core::text::Font;
use graphene_std::vector::style::{Fill, FillType, Gradient};
use std::sync::Arc;
use std::vec;
pub struct PortfolioMessageData<'a> {
pub ipp: &'a InputPreprocessorMessageHandler,
@ -29,6 +30,7 @@ pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler,
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
document_ids: Vec<DocumentId>,
active_panel: PanelType,
pub(crate) active_document_id: Option<DocumentId>,
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
pub persistent_data: PersistentData,
@ -181,54 +183,37 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
PortfolioMessage::Copy { clipboard } => {
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get(&id)) else {
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
return;
};
let copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
let binding = active_document
.metadata()
.shallowest_unique_layers(active_document.selected_nodes.selected_layers(active_document.metadata()));
let get_last_elements: Vec<_> = binding.iter().map(|x| x.last().expect("empty path")).collect();
let ordered_last_elements: Vec<_> = active_document.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).collect();
let mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
let ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]);
for layer in ordered_last_elements {
let layer_node_id = layer.to_node();
let previous_alias = active_document.network().nodes.get(&layer_node_id).map(|node| node.alias.clone()).unwrap_or_default();
let mut copy_ids = HashMap::new();
copy_ids.insert(layer_node_id, NodeId(0_u64));
if let Some(input_node) = active_document
.network()
.nodes
.get(&layer_node_id)
.and_then(|node| if node.is_layer { node.inputs.get(1) } else { node.inputs.first() })
.and_then(|input| input.as_node())
{
active_document
.network()
.upstream_flow_back_from_nodes(vec![input_node], graph_craft::document::FlowType::UpstreamFlow)
.enumerate()
.for_each(|(index, (_, node_id))| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
};
copy_ids.insert(layer_node_id, NodeId(0));
active_document
.network_interface
.upstream_flow_back_from_nodes(vec![layer_node_id], &[], network_interface::FlowType::LayerChildrenUpstreamFlow)
.enumerate()
.for_each(|(index, node_id)| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
buffer.push(CopyBufferEntry {
nodes: NodeGraphMessageHandler::copy_nodes(
active_document.network(),
&active_document.node_graph_handler.network,
&active_document.node_graph_handler.resolved_types,
&copy_ids,
)
.collect(),
selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()),
visible: active_document.selected_nodes.layer_visible(layer, active_document.metadata()),
locked: active_document.selected_nodes.layer_locked(layer, active_document.metadata()),
nodes: active_document.network_interface.copy_nodes(&copy_ids, &[]).collect(),
selected: active_document
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_layers_contains(layer, active_document.metadata()),
visible: active_document.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &active_document.network_interface),
locked: active_document.network_interface.selected_nodes(&[]).unwrap().layer_locked(layer, &active_document.network_interface),
collapsed: false,
alias: previous_alias.to_string(),
});
}
};
@ -345,7 +330,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
}
PortfolioMessage::NewDocumentWithName { name } => {
let new_document = DocumentMessageHandler::with_name(name, ipp, responses);
let mut new_document = DocumentMessageHandler::default();
new_document.name = name;
responses.add(DocumentMessage::PTZUpdate);
let document_id = DocumentId(generate_uuid());
if self.active_document().is_some() {
responses.add(BroadcastEvent::ToolAbort);
@ -409,36 +397,70 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
// TODO: Eventually remove this (probably starting late 2024)
// Upgrade all old nodes to support editable subgraphs introduced in #1750
if upgrade_from_before_editable_subgraphs {
for node in document.network.nodes.values_mut() {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type(&node.name).unwrap();
let default_definition_node = node_definition.default_document_node();
node.implementation = default_definition_node.implementation.clone();
// This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed.
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
for node_id in &document
.network_interface
.network_metadata(&[])
.unwrap()
.persistent_metadata
.node_metadata
.keys()
.cloned()
.collect::<Vec<NodeId>>()
{
if let Some(reference) = document
.network_interface
.network_metadata(&[])
.unwrap()
.persistent_metadata
.node_metadata
.get(node_id)
.and_then(|node| node.persistent_metadata.reference.as_ref())
{
let node_definition = crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type(reference).unwrap();
let default_definition_node = node_definition.default_node_template();
document.network_interface.set_implementation(node_id, &[], default_definition_node.document_node.implementation);
}
}
}
if document.network.nodes.iter().any(|(node_id, node)| node.name == "Output" && *node_id == NodeId(0)) {
ModifyInputsContext::delete_nodes(
&mut document.node_graph_handler,
&mut document.network,
&mut SelectedNodes(vec![]),
vec![NodeId(0)],
true,
responses,
Vec::new(),
);
if document
.network_interface
.network_metadata(&[])
.unwrap()
.persistent_metadata
.node_metadata
.iter()
.any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0))
{
document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]);
}
// TODO: Eventually remove this (probably starting late 2024)
// Upgrade Fill nodes to the format change in #1778
for node in document.network.nodes.values_mut() {
if node.name == "Fill" && node.inputs.len() == 8 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type(&node.name).unwrap();
let default_definition_node = node_definition.default_document_node();
let node_ids = document.network_interface.network(&[]).unwrap().nodes.keys().cloned().collect::<Vec<_>>();
for node_id in &node_ids {
let Some(node) = document.network_interface.network(&[]).unwrap().nodes.get(node_id) else {
log::error!("could not get node in deserialize_document");
continue;
};
let Some(node_metadata) = document.network_interface.network_metadata(&[]).unwrap().persistent_metadata.node_metadata.get(node_id) else {
log::error!("could not get node metadata for node {node_id} in deserialize_document");
continue;
};
node.implementation = default_definition_node.implementation.clone();
let old_inputs = std::mem::replace(&mut node.inputs, default_definition_node.inputs.clone());
// Upgrade Fill nodes to the format change in #1778
// TODO: Eventually remove this (probably starting late 2024)
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
continue;
};
if reference == "Fill" && node.inputs.len() == 8 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node;
document.network_interface.set_implementation(node_id, &[], document_node.implementation.clone());
node.inputs[0] = old_inputs[0].clone();
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), &[]);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), &[]);
let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue };
let TaggedValue::FillType(fill_type) = fill_type else { continue };
@ -466,19 +488,35 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
transform,
}),
};
node.inputs[1] = NodeInput::value(TaggedValue::Fill(fill.clone()), false);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), &[]);
match fill {
Fill::None => {
node.inputs[2] = NodeInput::value(TaggedValue::OptionalColor(None), false);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), &[]);
}
Fill::Solid(color) => {
node.inputs[2] = NodeInput::value(TaggedValue::OptionalColor(Some(color)), false);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), &[]);
}
Fill::Gradient(gradient) => {
node.inputs[3] = NodeInput::value(TaggedValue::Gradient(gradient), false);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), &[]);
}
}
}
// Upgrade artboard name being passed as hidden value input to "To Artboard"
if reference == "Artboard" {
let label = document.network_interface.display_name(node_id, &[]);
document
.network_interface
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
}
}
// TODO: Eventually remove this (probably starting late 2024)
@ -505,8 +543,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
if self.active_document().is_some() {
trace!("Pasting into folder {parent:?} as index: {insert_index}");
let nodes = entry.clone().nodes;
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
responses.add(GraphOperationMessage::AddNodesAsChild { nodes, new_ids, parent, insert_index });
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
}
};
@ -515,6 +555,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
for entry in self.copy_buffer[clipboard as usize].iter().rev() {
paste(entry, responses)
}
responses.add(NodeGraphMessage::RunDocumentGraph);
}
PortfolioMessage::PasteSerializedData { data } => {
if let Some(document) = self.active_document() {
@ -526,14 +567,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
for entry in data.into_iter().rev() {
document.load_layer_resources(responses);
let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
responses.add(GraphOperationMessage::AddNodesAsChild {
nodes: entry.nodes,
new_ids,
parent,
insert_index: -1,
});
let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);
responses.add(NodeGraphMessage::AddNodes { nodes: entry.nodes, new_ids });
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index: 0 });
}
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
}
@ -546,6 +585,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
responses.add(PortfolioMessage::SelectDocument { document_id: prev_id });
}
}
PortfolioMessage::SetActivePanel { panel } => {
self.active_panel = panel;
responses.add(DocumentMessage::SetActivePanel { active_panel: self.active_panel });
}
PortfolioMessage::SelectDocument { document_id } => {
// Auto-save the document we are leaving
let mut node_graph_open = false;
@ -598,11 +641,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
});
}
}
PortfolioMessage::SubmitGraphRender { document_id } => {
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
let result = self.executor.submit_node_graph_evaluation(
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
ipp.viewport_bounds.size().as_uvec2(),
false,
ignore_hash,
);
if let Err(description) = result {
@ -666,7 +709,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
common.extend(document.actions());
// Extend with actions that must have a selected layer
if document.selected_nodes.selected_layers(document.metadata()).next().is_some() {
if document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).next().is_some() {
common.extend(actions!(PortfolioMessageDiscriminant;
Copy,
Cut,
@ -730,12 +773,10 @@ impl PortfolioMessageHandler {
// TODO: Fix how this doesn't preserve tab order upon loading new document from *File > Open*
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>) {
let mut new_document = new_document;
let new_document = new_document;
self.document_ids.push(document_id);
new_document.update_layers_panel_options_bar_widgets(responses);
new_document.node_graph_handler.update_all_click_targets(&new_document.network, Vec::new());
self.documents.insert(document_id, new_document);
if self.active_document().is_some() {
@ -743,7 +784,7 @@ impl PortfolioMessageHandler {
responses.add(ToolMessage::DeactivateTools);
}
//TODO: Remove this and find a way to fix the issue where creating a new document when the node graph is open causes the transform in the new document to be incorrect
// TODO: Remove this and find a way to fix the issue where creating a new document when the node graph is open causes the transform in the new document to be incorrect
responses.add(DocumentMessage::GraphViewOverlay { open: false });
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(PortfolioMessage::SelectDocument { document_id });

View File

@ -37,3 +37,22 @@ pub enum KeyboardPlatformLayout {
/// Keyboard mapping used by Macs where Command is sometimes used in favor of Control
Mac,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize)]
pub enum PanelType {
#[default]
Document,
Layers,
Properties,
}
impl From<String> for PanelType {
fn from(value: String) -> Self {
match value.as_str() {
"Document" => PanelType::Document,
"Layers" => PanelType::Layers,
"Properties" => PanelType::Properties,
_ => panic!("Unknown panel type: {}", value),
}
}
}

View File

@ -1,7 +1,8 @@
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::vector::style::Gradient;
@ -13,7 +14,7 @@ use std::collections::VecDeque;
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = -1;
let insert_index = 0;
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
@ -22,7 +23,7 @@ pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: Lay
/// Create a new bitmap layer from an [`graphene_core::raster::ImageFrame<Color>`]
pub fn new_image_layer(image_frame: ImageFrame<Color>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = -1;
let insert_index = 0;
responses.add(GraphOperationMessage::NewBitmapLayer {
id,
image_frame,
@ -34,7 +35,7 @@ pub fn new_image_layer(image_frame: ImageFrame<Color>, id: NodeId, parent: Layer
/// Create a new group layer from an svg
pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
let insert_index = -1;
let insert_index = 0;
responses.add(DocumentMessage::ImportSvg {
id,
svg,
@ -44,39 +45,37 @@ pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent:
});
LayerNodeIdentifier::new_unchecked(id)
}
pub fn new_custom(id: NodeId, nodes: HashMap<NodeId, DocumentNode>, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
responses.add(GraphOperationMessage::NewCustomLayer {
id,
nodes,
parent,
insert_index: -1,
alias: String::new(),
pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
responses.add(GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index: 0 });
responses.add(GraphOperationMessage::SetUpstreamToChain {
layer: LayerNodeIdentifier::new_unchecked(id),
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
LayerNodeIdentifier::new_unchecked(id)
}
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
pub fn get_pivot(layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<DVec2> {
pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
let pivot_node_input_index = 5;
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network).find_input("Transform", pivot_node_input_index)? {
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? {
Some(*pivot)
} else {
None
}
}
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document_network: &NodeNetwork, document_metadata: &DocumentMetadata) -> DVec2 {
let [min, max] = document_metadata.nonzero_bounding_box(layer);
let pivot = get_pivot(layer, document_network).unwrap_or(DVec2::splat(0.5));
document_metadata.transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5));
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
}
/// Get the current gradient of a layer from the closest Fill node
pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Gradient> {
pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
let fill_index = 1;
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Fill")?;
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
let TaggedValue::Fill(graphene_std::vector::style::Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
return None;
};
@ -84,10 +83,10 @@ pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork)
}
/// Get the current fill of a layer from the closest Fill node
pub fn get_fill_color(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Color> {
pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<Color> {
let fill_index = 1;
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Fill")?;
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
let TaggedValue::Fill(graphene_std::vector::style::Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
return None;
};
@ -95,8 +94,8 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, document_network: &NodeNetwork
}
/// Get the current blend mode of a layer from the closest Blend Mode node
pub fn get_blend_mode(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<BlendMode> {
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Blend Mode")?;
pub fn get_blend_mode(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<BlendMode> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Blend Mode")?;
let TaggedValue::BlendMode(blend_mode) = inputs.get(1)?.as_value()? else {
return None;
};
@ -110,25 +109,25 @@ pub fn get_blend_mode(layer: LayerNodeIdentifier, document_network: &NodeNetwork
/// - Already factored into the pixel alpha channel of an image
/// - The default value of 100% if no Opacity node is present, but this function returns None in that case
/// With those limitations in mind, the intention of this function is to show just the value already present in an upstream Opacity node so that value can be directly edited.
pub fn get_opacity(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<f64> {
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Opacity")?;
pub fn get_opacity(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Opacity")?;
let TaggedValue::F64(opacity) = inputs.get(1)?.as_value()? else {
return None;
};
Some(*opacity)
}
pub fn get_fill_id(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<NodeId> {
NodeGraphLayer::new(layer, document_network).upstream_node_id_from_name("Fill")
pub fn get_fill_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Fill")
}
pub fn get_text_id(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<NodeId> {
NodeGraphLayer::new(layer, document_network).upstream_node_id_from_name("Text")
pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
NodeGraphLayer::new(layer, network_interface).upstream_node_id_from_name("Text")
}
/// Gets properties from the Text node
pub fn get_text(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<(&String, &Font, f64)> {
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Text")?;
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, f64)> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?;
let Some(TaggedValue::String(text)) = &inputs[1].as_value() else { return None };
let Some(TaggedValue::Font(font)) = &inputs[2].as_value() else { return None };
@ -137,9 +136,9 @@ pub fn get_text(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> O
Some((text, font, font_size))
}
pub fn get_stroke_width(layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<f64> {
pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
let weight_node_input_index = 2;
if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network).find_input("Stroke", weight_node_input_index)? {
if let TaggedValue::F64(width) = NodeGraphLayer::new(layer, network_interface).find_input("Stroke", weight_node_input_index)? {
Some(*width)
} else {
None
@ -147,43 +146,44 @@ pub fn get_stroke_width(layer: LayerNodeIdentifier, network: &NodeNetwork) -> Op
}
/// Checks if a specified layer uses an upstream node matching the given name.
pub fn is_layer_fed_by_node_of_name(layer: LayerNodeIdentifier, document_network: &NodeNetwork, node_name: &str) -> bool {
NodeGraphLayer::new(layer, document_network).find_node_inputs(node_name).is_some()
pub fn is_layer_fed_by_node_of_name(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, node_name: &str) -> bool {
NodeGraphLayer::new(layer, network_interface).find_node_inputs(node_name).is_some()
}
/// An immutable reference to a layer within the document node graph for easy access.
pub struct NodeGraphLayer<'a> {
node_graph: &'a NodeNetwork,
network_interface: &'a NodeNetworkInterface,
layer_node: NodeId,
}
impl<'a> NodeGraphLayer<'a> {
/// Get the layer node from the document
pub fn new(layer: LayerNodeIdentifier, network: &'a NodeNetwork) -> Self {
pub fn new(layer: LayerNodeIdentifier, network_interface: &'a NodeNetworkInterface) -> Self {
debug_assert!(layer != LayerNodeIdentifier::ROOT_PARENT, "Cannot create new NodeGraphLayer from ROOT_PARENT");
Self {
node_graph: network,
network_interface,
layer_node: layer.to_node(),
}
}
/// Return an iterator up the horizontal flow of the layer
pub fn horizontal_layer_flow(&self) -> impl Iterator<Item = (&'a DocumentNode, NodeId)> {
self.node_graph.upstream_flow_back_from_nodes(vec![self.layer_node], graph_craft::document::FlowType::HorizontalFlow)
pub fn horizontal_layer_flow(&self) -> impl Iterator<Item = NodeId> + 'a {
self.network_interface.upstream_flow_back_from_nodes(vec![self.layer_node], &[], FlowType::HorizontalFlow)
}
/// Node id of a node if it exists in the layer's primary flow
pub fn upstream_node_id_from_name(&self, node_name: &str) -> Option<NodeId> {
self.horizontal_layer_flow().find(|(node, _)| node.name == node_name).map(|(_, id)| id)
self.horizontal_layer_flow()
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| reference == node_name))
}
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
self.horizontal_layer_flow()
.skip(1)// Skip self
.take_while(|(node, _)| !node.is_layer)
.find(|(node, _)| node.name == node_name)
.map(|(node, _id)| &node.inputs)
.take_while(|node_id| !self.network_interface.is_layer(node_id,&[]))
.find(|node_id| self.network_interface.reference(node_id,&[]).is_some_and(|reference| reference == node_name))
.and_then(|node_id| self.network_interface.network(&[]).unwrap().nodes.get(&node_id).map(|node| &node.inputs))
}
/// Find a specific input of a node within the layer's primary flow

View File

@ -45,7 +45,11 @@ impl Pivot {
/// Recomputes the pivot position and transform.
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
let mut layers = document.selected_nodes.selected_visible_and_unlocked_layers(document.metadata());
let mut layers = document
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else {
// If no layers are selected then we revert things back to default
self.normalized_pivot = DVec2::splat(0.5);
@ -58,16 +62,18 @@ impl Pivot {
// If just one layer is selected we can use its inner transform (as it accounts for rotation)
if selected_layers_count == 1 {
let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network).unwrap_or(DVec2::splat(0.5));
let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5));
self.normalized_pivot = normalized_pivot;
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
} else {
// If more than one layer is selected we use the AABB with the mean of the pivots
let xy_summation = document
.selected_nodes
.selected_visible_and_unlocked_layers(document.metadata())
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network, &document.metadata))
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface)
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface))
.reduce(|a, b| a + b)
.unwrap_or_default();
@ -101,7 +107,12 @@ impl Pivot {
/// Sets the viewport position of the pivot for all selected layers.
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for layer in document.selected_nodes.selected_visible_and_unlocked_layers(document.metadata()) {
for layer in document
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface)
{
let transform = Self::get_layer_pivot_transform(layer, document);
let pivot = transform.inverse().transform_point2(position);
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.

View File

@ -37,15 +37,15 @@ impl Resize {
return None;
}
if !document.network().nodes.contains_key(&layer.to_node()) {
if !document.network_interface.network(&[]).unwrap().nodes.contains_key(&layer.to_node()) {
self.layer.take();
return None;
}
let start = self.viewport_drag_start(document);
let mouse = input.mouse.position;
let to_viewport = document.metadata().document_to_viewport;
let document_mouse = to_viewport.inverse().transform_point2(mouse);
let document_to_viewport = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
let document_mouse = document_to_viewport.inverse().transform_point2(mouse);
let mut points_viewport = [start, mouse];
let ignore = if let Some(layer) = self.layer { vec![layer] } else { vec![] };
let ratio = input.keyboard.get(lock_ratio as usize);
@ -55,7 +55,7 @@ impl Resize {
let size = points_viewport[1] - points_viewport[0];
let size = size.abs().max(size.abs().yx()) * size.signum();
points_viewport[1] = points_viewport[0] + size;
let end_document = to_viewport.inverse().transform_point2(points_viewport[1]);
let end_document = document_to_viewport.inverse().transform_point2(points_viewport[1]);
let constraint = SnapConstraint::Line {
origin: self.drag_start,
direction: end_document - self.drag_start,
@ -65,24 +65,24 @@ impl Resize {
let far = SnapCandidatePoint::handle(2. * self.drag_start - end_document);
let snapped_far = self.snap_manager.constrained_snap(&snap_data, &far, constraint, None);
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
points_viewport[0] = to_viewport.transform_point2(best.snapped_point_document);
points_viewport[1] = to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
self.snap_manager.update_indicator(best);
} else {
let snapped = self.snap_manager.constrained_snap(&snap_data, &SnapCandidatePoint::handle(end_document), constraint, None);
points_viewport[1] = to_viewport.transform_point2(snapped.snapped_point_document);
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
self.snap_manager.update_indicator(snapped);
}
} else if center {
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), None, false);
let snapped_far = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(2. * self.drag_start - document_mouse), None, false);
let best = if snapped_far.other_snap_better(&snapped) { snapped } else { snapped_far };
points_viewport[0] = to_viewport.transform_point2(best.snapped_point_document);
points_viewport[1] = to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
points_viewport[0] = document_to_viewport.transform_point2(best.snapped_point_document);
points_viewport[1] = document_to_viewport.transform_point2(self.drag_start * 2. - best.snapped_point_document);
self.snap_manager.update_indicator(best);
} else {
let snapped = self.snap_manager.free_snap(&snap_data, &SnapCandidatePoint::handle(document_mouse), None, false);
points_viewport[1] = to_viewport.transform_point2(snapped.snapped_point_document);
points_viewport[1] = document_to_viewport.transform_point2(snapped.snapped_point_document);
self.snap_manager.update_indicator(snapped);
}

View File

@ -2,10 +2,10 @@ use super::graph_modification_utils;
use super::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{GeometrySnapSource, SnapSource};
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::prelude::*;
use bezier_rs::{Bezier, BezierHandles, TValue};
use graph_craft::document::NodeNetwork;
use graphene_core::transform::Transform;
use graphene_core::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};
@ -171,7 +171,7 @@ impl ShapeState {
let mut snap_data = SnapData::new(document, input);
for (layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.metadata.compute_modified_vector(*layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(*layer, &document.network_interface) else {
continue;
};
for point in &state.selected_points {
@ -180,15 +180,20 @@ impl ShapeState {
}
}
let mouse_delta = document.metadata.document_to_viewport.inverse().transform_vector2(input.mouse.position - previous_mouse);
let mouse_delta = document
.network_interface
.document_metadata()
.document_to_viewport
.inverse()
.transform_vector2(input.mouse.position - previous_mouse);
let mut offset = mouse_delta;
let mut best_snapped = SnappedPoint::infinite_snap(document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position));
let mut best_snapped = SnappedPoint::infinite_snap(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
for (layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.metadata.compute_modified_vector(*layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(*layer, &document.network_interface) else {
continue;
};
let to_document = document.metadata.transform_to_document(*layer);
let to_document = document.metadata().transform_to_document(*layer);
for &selected in &state.selected_points {
let source = match selected {
@ -218,25 +223,18 @@ impl ShapeState {
}
}
snap_manager.update_indicator(best_snapped);
document.metadata.document_to_viewport.transform_vector2(offset)
document.metadata().document_to_viewport.transform_vector2(offset)
}
/// Select/deselect the first point within the selection threshold.
/// Returns a tuple of the points if found and the offset, or `None` otherwise.
pub fn change_point_selection(
&mut self,
document_network: &NodeNetwork,
document_metadata: &DocumentMetadata,
mouse_position: DVec2,
select_threshold: f64,
add_to_selection: bool,
) -> Option<Option<SelectedPointsInfo>> {
pub fn change_point_selection(&mut self, network_interface: &NodeNetworkInterface, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool) -> Option<Option<SelectedPointsInfo>> {
if self.selected_shape_state.is_empty() {
return None;
}
if let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(document_network, document_metadata, mouse_position, select_threshold) {
let vector_data = document_metadata.compute_modified_vector(layer, document_network)?;
if let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(network_interface, mouse_position, select_threshold) {
let vector_data = network_interface.document_metadata().compute_modified_vector(layer, network_interface)?;
let point_position = manipulator_point_id.get_position(&vector_data)?;
let selected_shape_state = self.selected_shape_state.get(&layer)?;
@ -246,7 +244,7 @@ impl ShapeState {
let new_selected = if already_selected { !add_to_selection } else { true };
// Offset to snap the selected point to the cursor
let offset = mouse_position - document_metadata.transform_to_viewport(layer).transform_point2(point_position);
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
// This is selecting the manipulator only for now, next to generalize to points
if new_selected {
@ -287,10 +285,10 @@ impl ShapeState {
/// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer.
pub fn select_connected_anchors(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2) {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
return;
};
let to_viewport = document.metadata.transform_to_viewport(layer);
let to_viewport = document.metadata().transform_to_viewport(layer);
let layer_mouse = to_viewport.inverse().transform_point2(mouse);
let state = self.selected_shape_state.entry(layer).or_default();
@ -335,7 +333,7 @@ impl ShapeState {
/// Internal helper function that selects all anchors, and deselects all handles, for a layer given its [`LayerNodeIdentifier`] and [`SelectedLayerState`].
fn select_all_anchors_in_layer_with_state(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, state: &mut SelectedLayerState) {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
return;
};
@ -429,14 +427,13 @@ impl ShapeState {
pub fn reposition_control_point(
&self,
point: &ManipulatorPointId,
network: &NodeNetwork,
metadata: &DocumentMetadata,
network_interface: &NodeNetworkInterface,
new_position: DVec2,
layer: LayerNodeIdentifier,
responses: &mut VecDeque<Message>,
) -> Option<()> {
let vector_data = metadata.compute_modified_vector(layer, network)?;
let transform = metadata.transform_to_document(layer).inverse();
let vector_data = network_interface.document_metadata().compute_modified_vector(layer, network_interface)?;
let transform = network_interface.document_metadata().transform_to_document(layer).inverse();
let position = transform.transform_point2(new_position);
let current_position = point.get_position(&vector_data)?;
let delta = position - current_position;
@ -464,12 +461,12 @@ impl ShapeState {
/// Iterates over the selected manipulator groups, returning whether their handles have mixed, colinear, or free angles.
/// If there are no points selected this function returns mixed.
pub fn selected_manipulator_angles(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata) -> ManipulatorAngle {
pub fn selected_manipulator_angles(&self, network_interface: &NodeNetworkInterface) -> ManipulatorAngle {
// This iterator contains a bool indicating whether or not selected points' manipulator groups have colinear handles.
let mut points_colinear_status = self
.selected_shape_state
.iter()
.map(|(&layer, selection_state)| (document_metadata.compute_modified_vector(layer, document_network), selection_state))
.map(|(&layer, selection_state)| (network_interface.document_metadata().compute_modified_vector(layer, network_interface), selection_state))
.flat_map(|(data, selection_state)| selection_state.selected_points.iter().map(move |&point| data.as_ref().map_or(false, |data| data.colinear(point))));
let Some(first_is_colinear) = points_colinear_status.next() else { return ManipulatorAngle::Mixed };
@ -547,10 +544,10 @@ impl ShapeState {
let mut skip_set = HashSet::new();
for (&layer, layer_state) in self.selected_shape_state.iter() {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
continue;
};
let transform = document.metadata.transform_to_document(layer);
let transform = document.metadata().transform_to_document(layer);
for &point in layer_state.selected_points.iter() {
let Some(handles) = point.get_handle_pair(&vector_data) else { continue };
@ -622,12 +619,12 @@ impl ShapeState {
/// Move the selected points by dragging the mouse.
pub fn move_selected_points(&self, handle_lengths: Option<OpposingHandleLengths>, document: &DocumentMessageHandler, delta: DVec2, equidistant: bool, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
continue;
};
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
let transform = document.metadata.transform_to_viewport(layer);
let transform = document.metadata().transform_to_viewport(layer);
let delta = transform.inverse().transform_vector2(delta);
for &point in state.selected_points.iter() {
@ -662,7 +659,7 @@ impl ShapeState {
let new_relative = if equidistant {
-(handle_position - anchor_position)
} else {
let transform = document.metadata.document_to_viewport.inverse() * transform;
let transform = document.metadata().document_to_viewport.inverse() * transform;
let Some(other_position) = other.to_manipulator_point().get_position(&vector_data) else {
continue;
};
@ -683,8 +680,8 @@ impl ShapeState {
self.selected_shape_state
.iter()
.filter_map(|(&layer, state)| {
let vector_data = document.metadata.compute_modified_vector(layer, &document.network)?;
let transform = document.metadata.transform_to_document(layer);
let vector_data = document.metadata().compute_modified_vector(layer, &document.network_interface)?;
let transform = document.metadata().transform_to_document(layer);
let opposing_handle_lengths = vector_data
.colinear_manipulators
.iter()
@ -749,7 +746,7 @@ impl ShapeState {
pub fn delete_selected_points(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let mut missing_anchors = HashMap::new();
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
continue;
};
@ -840,7 +837,7 @@ impl ShapeState {
pub fn break_path_at_selected_point(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
continue;
};
@ -886,7 +883,7 @@ impl ShapeState {
/// Delete point(s) and adjacent segments.
pub fn delete_point_and_break_path(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
let Some(vector_data) = document.metadata().compute_modified_vector(layer, &document.network_interface) else {
continue;
};
@ -907,9 +904,11 @@ impl ShapeState {
}
/// Disable colinear handles colinear.
pub fn disable_colinear_handles_state_on_selected(&self, metadata: &DocumentMetadata, network: &NodeNetwork, responses: &mut VecDeque<Message>) {
pub fn disable_colinear_handles_state_on_selected(&self, network_interface: &NodeNetworkInterface, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = metadata.compute_modified_vector(layer, network) else { continue };
let Some(vector_data) = network_interface.document_metadata().compute_modified_vector(layer, network_interface) else {
continue;
};
for &point in &state.selected_points {
if let ManipulatorPointId::Anchor(point) = point {
@ -928,13 +927,7 @@ impl ShapeState {
}
/// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint].
pub fn find_nearest_point_indices(
&mut self,
document_network: &NodeNetwork,
document_metadata: &DocumentMetadata,
mouse_position: DVec2,
select_threshold: f64,
) -> Option<(LayerNodeIdentifier, ManipulatorPointId)> {
pub fn find_nearest_point_indices(&mut self, network_interface: &NodeNetworkInterface, mouse_position: DVec2, select_threshold: f64) -> Option<(LayerNodeIdentifier, ManipulatorPointId)> {
if self.selected_shape_state.is_empty() {
return None;
}
@ -943,7 +936,7 @@ impl ShapeState {
// Find the closest control point among all elements of shapes_to_modify
for &layer in self.selected_shape_state.keys() {
if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(document_network, document_metadata, layer, mouse_position) {
if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(network_interface, layer, mouse_position) {
// Choose the first point under the threshold
if distance_squared < select_threshold_squared {
trace!("Selecting... manipulator point: {manipulator_point_id:?}");
@ -959,12 +952,12 @@ impl ShapeState {
/// Find the closest manipulator, manipulator point, and distance so we can select path elements.
/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.
/// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`.
fn closest_point_in_layer(document_network: &NodeNetwork, document_metadata: &DocumentMetadata, layer: LayerNodeIdentifier, pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> {
fn closest_point_in_layer(network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> {
let mut closest_distance_squared: f64 = f64::MAX;
let mut manipulator_point = None;
let vector_data = document_metadata.compute_modified_vector(layer, document_network)?;
let viewspace = document_metadata.transform_to_viewport(layer);
let vector_data = network_interface.document_metadata().compute_modified_vector(layer, network_interface)?;
let viewspace = network_interface.document_metadata().transform_to_viewport(layer);
// Handles
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
@ -999,8 +992,8 @@ impl ShapeState {
}
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
fn closest_segment(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
let transform = document_metadata.transform_to_viewport(layer);
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
let transform = network_interface.document_metadata().transform_to_viewport(layer);
let layer_pos = transform.inverse().transform_point2(position);
let tolerance = tolerance + 0.5;
@ -1008,7 +1001,7 @@ impl ShapeState {
let mut closest = None;
let mut closest_distance_squared: f64 = tolerance * tolerance;
let vector_data = document_metadata.compute_modified_vector(layer, document_network)?;
let vector_data = network_interface.document_metadata().compute_modified_vector(layer, network_interface)?;
for (segment, mut bezier, start, end) in vector_data.segment_bezier_iter() {
let t = bezier.project(layer_pos);
@ -1023,7 +1016,7 @@ impl ShapeState {
// 0.5 is half the line (center to side) but it's convenient to allow targeting slightly more than half the line width
const STROKE_WIDTH_PERCENT: f64 = 0.7;
let stroke_width = graph_modification_utils::get_stroke_width(layer, document_network).unwrap_or(1.) as f64 * STROKE_WIDTH_PERCENT;
let stroke_width = graph_modification_utils::get_stroke_width(layer, network_interface).unwrap_or(1.) as f64 * STROKE_WIDTH_PERCENT;
// Convert to linear if handes are on top of control points
if let bezier_rs::BezierHandles::Cubic { handle_start, handle_end } = bezier.handles {
@ -1054,22 +1047,22 @@ impl ShapeState {
}
/// find closest to the position segment on selected layers. If there is more than one layers with close enough segment it return upper from them
pub fn upper_closest_segment(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
let closest_seg = |layer| self.closest_segment(document_network, document_metadata, layer, position, tolerance);
pub fn upper_closest_segment(&self, network_interface: &NodeNetworkInterface, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
let closest_seg = |layer| self.closest_segment(network_interface, layer, position, tolerance);
match self.selected_shape_state.len() {
0 => None,
1 => self.selected_layers().next().copied().and_then(closest_seg),
_ => self.sorted_selected_layers(document_metadata).find_map(closest_seg),
_ => self.sorted_selected_layers(network_interface.document_metadata()).find_map(closest_seg),
}
}
/// Converts a nearby clicked anchor point's handles between sharp (zero-length handles) and smooth (pulled-apart handle(s)).
/// If both handles aren't zero-length, they are set that. If both are zero-length, they are stretched apart by a reasonable amount.
/// This can can be activated by double clicking on an anchor with the Path tool.
pub fn flip_smooth_sharp(&self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer| {
let vector_data = document_metadata.compute_modified_vector(layer, document_network)?;
let transform_to_screenspace = document_metadata.transform_to_viewport(layer);
let vector_data = network_interface.document_metadata().compute_modified_vector(layer, network_interface)?;
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer);
let mut result = None;
let mut closest_distance_squared = tolerance * tolerance;
@ -1145,15 +1138,15 @@ impl ShapeState {
false
}
pub fn select_all_in_quad(&mut self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, quad: [DVec2; 2], clear_selection: bool) {
pub fn select_all_in_quad(&mut self, network_interface: &NodeNetworkInterface, quad: [DVec2; 2], clear_selection: bool) {
for (&layer, state) in &mut self.selected_shape_state {
if clear_selection {
state.clear_points()
}
let vector_data = document_metadata.compute_modified_vector(layer, document_network);
let vector_data = network_interface.document_metadata().compute_modified_vector(layer, network_interface);
let Some(vector_data) = vector_data else { continue };
let transform = document_metadata.transform_to_viewport(layer);
let transform = network_interface.document_metadata().transform_to_viewport(layer);
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.segment_domain.start_point().len());
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.segment_domain.end_point().len());

View File

@ -187,7 +187,7 @@ impl SnapManager {
self.indicator = None;
}
pub fn preview_draw(&mut self, snap_data: &SnapData, mouse: DVec2) {
let point = SnapCandidatePoint::handle(snap_data.document.metadata.document_to_viewport.inverse().transform_point2(mouse));
let point = SnapCandidatePoint::handle(snap_data.document.metadata().document_to_viewport.inverse().transform_point2(mouse));
let snapped = self.free_snap(snap_data, &point, None, false);
self.update_indicator(snapped);
}
@ -230,7 +230,7 @@ impl SnapManager {
let mut best_point = None;
for point in snapped_points {
let viewport_point = document.metadata.document_to_viewport.transform_point2(point.snapped_point_document);
let viewport_point = document.metadata().document_to_viewport.transform_point2(point.snapped_point_document);
let on_screen = viewport_point.cmpgt(DVec2::ZERO).all() && viewport_point.cmplt(snap_data.input.viewport_bounds.size()).all();
if !on_screen && !off_screen {
continue;
@ -258,29 +258,29 @@ impl SnapManager {
if candidates.len() > 10 {
return;
}
if !document.selected_nodes.layer_visible(layer, &document.metadata) {
if !document.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &document.network_interface) {
return;
}
if snap_data.ignore.contains(&layer) {
return;
}
if document.metadata.is_folder(layer) {
for layer in layer.children(&document.metadata) {
if layer.has_children(document.metadata()) {
for layer in layer.children(document.metadata()) {
add_candidates(layer, snap_data, quad, candidates);
}
return;
}
let Some(bounds) = document.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
return;
};
let layer_bounds = document.metadata.transform_to_document(layer) * Quad::from_box(bounds);
let screen_bounds = document.metadata.document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.input.viewport_bounds.size()]);
let layer_bounds = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);
let screen_bounds = document.metadata().document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, snap_data.input.viewport_bounds.size()]);
if quad.intersects(layer_bounds) && screen_bounds.intersects(layer_bounds) {
candidates.push(layer);
}
}
for layer in LayerNodeIdentifier::ROOT_PARENT.children(&document.metadata) {
for layer in LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()) {
add_candidates(layer, snap_data, quad, &mut candidates);
}
@ -330,7 +330,7 @@ impl SnapManager {
}
pub fn draw_overlays(&mut self, snap_data: SnapData, overlay_context: &mut OverlayContext) {
let to_viewport = snap_data.document.metadata.document_to_viewport;
let to_viewport = snap_data.document.metadata().document_to_viewport;
if let Some(ind) = &self.indicator {
for curve in &ind.curves {
let Some(curve) = curve else { continue };

View File

@ -22,13 +22,16 @@ impl LayerSnapper {
return;
}
let bounds = if document.metadata.is_artboard(layer) {
document.metadata.bounding_box_with_transform(layer, document.metadata.transform_to_document(layer)).map(Quad::from_box)
let bounds = if document.network_interface.is_artboard(&layer.to_node(), &[]) {
document
.metadata()
.bounding_box_with_transform(layer, document.metadata().transform_to_document(layer))
.map(Quad::from_box)
} else {
document
.metadata
.metadata()
.bounding_box_with_transform(layer, DAffine2::IDENTITY)
.map(|bounds| document.metadata.transform_to_document(layer) * Quad::from_box(bounds))
.map(|bounds| document.metadata().transform_to_document(layer) * Quad::from_box(bounds))
};
let Some(bounds) = bounds else { return };
@ -53,21 +56,21 @@ impl LayerSnapper {
let document = snap_data.document;
self.paths_to_snap.clear();
for layer in document.metadata.all_layers() {
if !document.metadata.is_artboard(layer) || snap_data.ignore.contains(&layer) {
for layer in document.metadata().all_layers() {
if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {
continue;
}
self.add_layer_bounds(document, layer, SnapTarget::Board(BoardSnapTarget::Edge));
}
for &layer in snap_data.get_candidates() {
let transform = document.metadata.transform_to_document(layer);
let transform = document.metadata().transform_to_document(layer);
if !transform.is_finite() {
continue;
}
if document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Intersection)) || document.snapping_state.target_enabled(SnapTarget::Geometry(GeometrySnapTarget::Path))
{
for subpath in document.metadata.layer_outline(layer) {
for subpath in document.metadata().layer_outline(layer) {
for (start_index, curve) in subpath.iter().enumerate() {
let document_curve = curve.apply_transformation(|p| transform.transform_point2(p));
let start = subpath.manipulator_groups()[start_index].id;
@ -175,13 +178,17 @@ impl LayerSnapper {
let document = snap_data.document;
self.points_to_snap.clear();
for layer in document.metadata.all_layers() {
if !document.metadata.is_artboard(layer) || snap_data.ignore.contains(&layer) {
for layer in document.metadata().all_layers() {
if !document.network_interface.is_artboard(&layer.to_node(), &[]) || snap_data.ignore.contains(&layer) {
continue;
}
if document.snapping_state.target_enabled(SnapTarget::Board(BoardSnapTarget::Corner)) {
let Some(bounds) = document.metadata.bounding_box_with_transform(layer, document.metadata.transform_to_document(layer)) else {
let Some(bounds) = document
.network_interface
.document_metadata()
.bounding_box_with_transform(layer, document.metadata().transform_to_document(layer))
else {
continue;
};
@ -194,10 +201,10 @@ impl LayerSnapper {
if snap_data.ignore_bounds(layer) {
continue;
}
let Some(bounds) = document.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
continue;
};
let quad = document.metadata.transform_to_document(layer) * Quad::from_box(bounds);
let quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);
let values = BBoxSnapValues::BOUNDING_BOX;
get_bbox_points(quad, &mut self.points_to_snap, values, document);
}
@ -441,17 +448,17 @@ pub fn are_manipulator_handles_colinear(group: &bezier_rs::ManipulatorGroup<Poin
pub fn get_layer_snap_points(layer: LayerNodeIdentifier, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>) {
let document = snap_data.document;
if document.metadata().is_artboard(layer) {
if document.network_interface.is_artboard(&layer.to_node(), &[]) {
return;
}
if document.metadata().is_folder(layer) {
if layer.has_children(document.metadata()) {
for child in layer.descendants(document.metadata()) {
get_layer_snap_points(child, snap_data, points);
}
} else if document.metadata.layer_outline(layer).next().is_some() {
let to_document = document.metadata.transform_to_document(layer);
for subpath in document.metadata.layer_outline(layer) {
} else if document.metadata().layer_outline(layer).next().is_some() {
let to_document = document.metadata().transform_to_document(layer);
for subpath in document.metadata().layer_outline(layer) {
subpath_anchor_snap_points(layer, subpath, snap_data, points, to_document);
}
}

View File

@ -118,7 +118,7 @@ impl SelectedEdges {
}
if let Some(SizeSnapData { manager, points, snap_data }) = snap {
let view_to_doc = snap_data.document.metadata.document_to_viewport.inverse();
let view_to_doc = snap_data.document.metadata().document_to_viewport.inverse();
let bounds_to_doc = view_to_doc * transform;
let mut best_snap = SnappedPoint::infinite_snap(pivot);
let mut best_scale_factor = DVec2::ONE;
@ -220,10 +220,10 @@ pub fn axis_align_drag(axis_align: bool, position: DVec2, start: DVec2) -> DVec2
pub fn snap_drag(start: DVec2, current: DVec2, axis_align: bool, snap_data: SnapData, snap_manager: &mut SnapManager, candidates: &Vec<SnapCandidatePoint>) -> DVec2 {
let mouse_position = axis_align_drag(axis_align, snap_data.input.mouse.position, start);
let document = snap_data.document;
let total_mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - start);
let mouse_delta_document = document.metadata.document_to_viewport.inverse().transform_vector2(mouse_position - current);
let total_mouse_delta_document = document.metadata().document_to_viewport.inverse().transform_vector2(mouse_position - start);
let mouse_delta_document = document.metadata().document_to_viewport.inverse().transform_vector2(mouse_position - current);
let mut offset = mouse_delta_document;
let mut best_snap = SnappedPoint::infinite_snap(document.metadata.document_to_viewport.inverse().transform_point2(mouse_position));
let mut best_snap = SnappedPoint::infinite_snap(document.metadata().document_to_viewport.inverse().transform_point2(mouse_position));
for point in candidates {
let mut point = point.clone();
@ -251,7 +251,7 @@ pub fn snap_drag(start: DVec2, current: DVec2, axis_align: bool, snap_data: Snap
snap_manager.update_indicator(best_snap);
document.metadata.document_to_viewport.transform_vector2(offset)
document.metadata().document_to_viewport.transform_vector2(offset)
}
/// Contains info on the overlays for the bounding box and transform handles

View File

@ -10,10 +10,10 @@ pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance:
let mut best = None;
let mut best_distance_squared = tolerance * tolerance;
for layer in document.selected_nodes.selected_layers(document.metadata()) {
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
let viewspace = document.metadata().transform_to_viewport(layer);
let vector_data = document.metadata.compute_modified_vector(layer, document.network())?;
let vector_data = document.metadata().compute_modified_vector(layer, &document.network_interface)?;
for id in vector_data.single_connected_points() {
let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };

View File

@ -11,7 +11,7 @@ use graphene_core::raster::color::Color;
pub struct ToolMessageData<'a> {
pub document_id: DocumentId,
pub document: &'a DocumentMessageHandler,
pub document: &'a mut DocumentMessageHandler,
pub input: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
pub node_graph: &'a NodeGraphExecutor,

View File

@ -119,7 +119,7 @@ impl ArtboardToolData {
let Some(layer) = self.selected_artboard else { return };
if let Some(bounds) = document.metadata.bounding_box_with_transform(layer, document.metadata.transform_to_document(layer)) {
if let Some(bounds) = document.metadata().bounding_box_with_transform(layer, document.metadata().transform_to_document(layer)) {
snapping::get_bbox_points(Quad::from_box(bounds), &mut self.snap_candidates, snapping::BBoxSnapValues::ARTBOARD, document);
}
}
@ -142,9 +142,7 @@ impl ArtboardToolData {
}
fn hovered_artboard(document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
document
.click_xray(input.mouse.position)
.find(|&layer| document.network.nodes.get(&layer.to_node()).map_or(false, |document_node| document_node.is_artboard()))
document.click_xray(input).find(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[]))
}
fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
@ -196,7 +194,7 @@ impl ArtboardToolData {
let size = (max - min).abs();
responses.add(GraphOperationMessage::ResizeArtboard {
id: self.selected_artboard.unwrap().to_node(),
layer: self.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
@ -294,7 +292,7 @@ impl Fsm for ArtboardToolFsmState {
return ArtboardToolFsmState::Ready { hovered };
}
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap().to_node(),
layer: tool_data.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
@ -350,7 +348,7 @@ impl Fsm for ArtboardToolFsmState {
log::error!("Selected artboard cannot be ROOT_PARENT");
} else {
responses.add(GraphOperationMessage::ResizeArtboard {
id: artboard.to_node(),
layer: artboard,
location: start.min(end).round().as_ivec2(),
dimensions: (start.round() - end.round()).abs().as_ivec2(),
});
@ -460,12 +458,17 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Ready { hovered }
}
(_, ArtboardToolMessage::UpdateSelectedArtboard) => {
tool_data.selected_artboard = document.selected_nodes.selected_layers(document.metadata()).find(|layer| document.metadata().is_artboard(*layer));
tool_data.selected_artboard = document
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_layers(document.metadata())
.find(|layer| document.network_interface.is_artboard(&layer.to_node(), &[]));
self
}
(_, ArtboardToolMessage::DeleteSelected) => {
tool_data.selected_artboard.take();
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
responses.add(DocumentMessage::DeleteSelectedLayers);
ArtboardToolFsmState::Ready { hovered }
}
@ -475,7 +478,7 @@ impl Fsm for ArtboardToolFsmState {
log::error!("Selected artboard cannot be ROOT_PARENT");
} else {
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap().to_node(),
layer: tool_data.selected_artboard.unwrap(),
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
});

View File

@ -2,10 +2,11 @@ use super::tool_prelude::*;
use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform};
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNodeMetadata, NodeId};
use graph_craft::document::NodeId;
use graphene_core::raster::BlendMode;
use graphene_core::uuid::generate_uuid;
use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
@ -259,14 +260,20 @@ impl BrushToolData {
fn load_existing_strokes(&mut self, document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
self.transform = DAffine2::IDENTITY;
if document.selected_nodes.selected_layers(document.metadata()).count() != 1 {
if document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).count() != 1 {
return None;
}
let layer = document.selected_nodes.selected_layers(document.metadata()).next()?;
let layer = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).next()?;
self.layer = Some(layer);
for (node, node_id) in document.network().upstream_flow_back_from_nodes(vec![layer.to_node()], graph_craft::document::FlowType::HorizontalFlow) {
if node.name == "Brush" && node_id != layer.to_node() {
for node_id in document.network_interface.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::HorizontalFlow) {
let Some(node) = document.network_interface.network(&[]).unwrap().nodes.get(&node_id) else {
continue;
};
let Some(reference) = document.network_interface.reference(&node_id, &[]) else {
continue;
};
if reference == "Brush" && node_id != layer.to_node() {
let points_input = node.inputs.get(2)?;
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else {
continue;
@ -274,7 +281,7 @@ impl BrushToolData {
self.strokes.clone_from(strokes);
return Some(layer);
} else if node.name == "Transform" {
} else if reference == "Transform" {
let upstream = document.metadata().upstream_transform(node_id);
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
@ -313,7 +320,12 @@ impl Fsm for BrushToolFsmState {
tool_data.layer = Some(layer);
let parent = layer.parent(document.metadata()).unwrap_or_else(|| document.new_layer_parent(true));
let parent_transform = document.metadata().transform_to_viewport(parent).inverse().transform_point2(input.mouse.position);
let parent_transform = document
.network_interface
.document_metadata()
.transform_to_viewport(parent)
.inverse()
.transform_point2(input.mouse.position);
let layer_position = tool_data.transform.inverse().transform_point2(parent_transform);
let layer_document_scale = document.metadata().transform_to_document(parent) * tool_data.transform;
@ -351,7 +363,12 @@ impl Fsm for BrushToolFsmState {
if let Some(layer) = tool_data.layer {
if let Some(stroke) = tool_data.strokes.last_mut() {
let parent = layer.parent(document.metadata()).unwrap_or(LayerNodeIdentifier::ROOT_PARENT);
let parent_position = document.metadata().transform_to_viewport(parent).inverse().transform_point2(input.mouse.position);
let parent_position = document
.network_interface
.document_metadata()
.transform_to_viewport(parent)
.inverse()
.transform_point2(input.mouse.position);
let layer_position = tool_data.transform.inverse().transform_point2(parent_position);
stroke.trace.push(BrushInputSample { position: layer_position })
@ -407,17 +424,14 @@ impl Fsm for BrushToolFsmState {
fn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
responses.add(DocumentMessage::DeselectAllLayers);
let brush_node = resolve_document_node_type("Brush")
.expect("Brush node does not exist")
.to_document_node_default_inputs([], DocumentNodeMetadata::position((-6, 0)));
let brush_node = resolve_document_node_type("Brush").expect("Brush node does not exist").default_node_template();
let id = NodeId(generate_uuid());
responses.add(GraphOperationMessage::NewCustomLayer {
id,
nodes: HashMap::from([(NodeId(0), brush_node)]),
nodes: vec![(NodeId(0), brush_node)],
parent: document.new_layer_parent(true),
insert_index: -1,
alias: String::new(),
insert_index: 0,
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });

View File

@ -201,15 +201,10 @@ impl Fsm for EllipseToolFsmState {
responses.add(DocumentMessage::StartTransaction);
// Create a new ellipse vector shape
let nodes = {
let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist");
let node = node_type.to_document_node_default_inputs(
[None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))],
Default::default(),
);
let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist");
let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))]);
let nodes = vec![(NodeId(0), node)];
HashMap::from([(NodeId(0), node)])
};
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);

View File

@ -87,7 +87,7 @@ impl Fsm for FillToolFsmState {
match (self, event) {
(FillToolFsmState::Ready, color_event) => {
let Some(layer_identifier) = document.click(input.mouse.position, &document.network) else {
let Some(layer_identifier) = document.click(input) else {
return self;
};
let fill = match color_event {

View File

@ -224,12 +224,9 @@ impl Fsm for FreehandToolFsmState {
let parent = document.new_layer_parent(true);
let nodes = {
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
let node = node_type.to_document_node_default_inputs([], Default::default());
HashMap::from([(NodeId(0), node)])
};
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
let node = node_type.default_node_template();
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses);
tool_options.fill.apply_fill(layer, responses);

View File

@ -249,8 +249,8 @@ impl Fsm for GradientToolFsmState {
(_, GradientToolMessage::Overlays(mut overlay_context)) => {
let selected = tool_data.selected_gradient.as_ref();
for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(gradient) = get_gradient(layer, &document.network) else { continue };
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
let transform = gradient_space_transform(layer, document);
let dragging = selected
.filter(|selected| selected.layer.map_or(false, |selected_layer| selected_layer == layer))
@ -324,8 +324,8 @@ impl Fsm for GradientToolFsmState {
self
}
(_, GradientToolMessage::InsertStop) => {
for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(mut gradient) = get_gradient(layer, &document.network) else { continue };
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
let Some(mut gradient) = get_gradient(layer, &document.network_interface) else { continue };
let transform = gradient_space_transform(layer, document);
let mouse = input.mouse.position;
@ -363,8 +363,8 @@ impl Fsm for GradientToolFsmState {
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
let mut dragging = false;
for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(gradient) = get_gradient(layer, &document.network) else { continue };
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
let transform = gradient_space_transform(layer, document);
// Check for dragging step
@ -399,11 +399,11 @@ impl Fsm for GradientToolFsmState {
document.backup_nonmut(responses);
GradientToolFsmState::Drawing
} else {
let selected_layer = document.click(input.mouse.position, &document.network);
let selected_layer = document.click(input);
// Apply the gradient to the selected layer
if let Some(layer) = selected_layer {
if !document.selected_nodes.selected_layers_contains(layer, document.metadata()) {
if !document.network_interface.selected_nodes(&[]).unwrap().selected_layers_contains(layer, document.metadata()) {
let nodes = vec![layer.to_node()];
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
@ -412,7 +412,7 @@ impl Fsm for GradientToolFsmState {
responses.add(DocumentMessage::StartTransaction);
// Use the already existing gradient if it exists
let gradient = if let Some(gradient) = get_gradient(layer, &document.network) {
let gradient = if let Some(gradient) = get_gradient(layer, &document.network_interface) {
gradient.clone()
} else {
// Generate a new gradient

View File

@ -101,7 +101,7 @@ impl Fsm for ImaginateToolFsmState {
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
shape_data.start(document, input);
responses.add(DocumentMessage::StartTransaction);
shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), document.network()));
shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), &document.network_interface));
responses.add(DocumentMessage::DeselectAllLayers);
// // Utility function to offset the position of each consecutive node
@ -120,7 +120,7 @@ impl Fsm for ImaginateToolFsmState {
// // Give them a unique ID
// let transform_node_id = NodeId(100);
let imaginate_node_id = NodeId(101);
//let imaginate_node_id = NodeId(101);
// Create the network based on the Input -> Output passthrough default network
// let mut network = new_image_network(16, imaginate_node_id);
@ -134,7 +134,7 @@ impl Fsm for ImaginateToolFsmState {
// imaginate_node_id,
// imaginate_node_type.to_document_node_default_inputs([Some(NodeInput::node(transform_node_id, 0))], next_pos()),
// );
responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
// responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
// // Add a layer with a frame to the document
// responses.add(Operation::AddFrame {

View File

@ -173,25 +173,20 @@ impl Fsm for LineToolFsmState {
self
}
(LineToolFsmState::Ready, LineToolMessage::DragStart) => {
let point = SnapCandidatePoint::handle(document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position));
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
tool_data.drag_start = snapped.snapped_point_document;
responses.add(DocumentMessage::StartTransaction);
let nodes = {
let node_type = resolve_document_node_type("Line").expect("Line node does not exist");
let node = node_type.to_document_node_default_inputs(
[
None,
Some(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)),
Some(NodeInput::value(TaggedValue::DVec2(DVec2::X), false)),
],
Default::default(),
);
let node_type = resolve_document_node_type("Line").expect("Line node does not exist");
let node = node_type.node_template_input_override([
None,
Some(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)),
Some(NodeInput::value(TaggedValue::DVec2(DVec2::X), false)),
]);
let nodes = vec![(NodeId(0), node)];
HashMap::from([(NodeId(0), node)])
};
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
tool_data.layer = Some(layer);
@ -296,7 +291,7 @@ impl Fsm for LineToolFsmState {
}
fn generate_transform(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle: bool, snap_angle: bool, center: bool) -> Message {
let document_to_viewport = snap_data.document.metadata.document_to_viewport;
let document_to_viewport = snap_data.document.metadata().document_to_viewport;
let mut document_points = [tool_data.drag_start, document_to_viewport.inverse().transform_point2(tool_data.drag_current)];
let mut angle = -(document_points[1] - document_points[0]).angle_to(DVec2::X);

View File

@ -2,12 +2,12 @@ use super::tool_prelude::*;
use crate::consts::{COLOR_OVERLAY_YELLOW, DRAG_THRESHOLD, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::shape_editor::{ClosestSegment, ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
use graph_craft::document::NodeNetwork;
use graphene_core::renderer::Quad;
use graphene_core::vector::ManipulatorPointId;
@ -272,10 +272,10 @@ impl PathToolData {
PathToolFsmState::InsertPoint
}
fn update_insertion(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, mouse_position: DVec2) -> PathToolFsmState {
fn update_insertion(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, input: &InputPreprocessorMessageHandler) -> PathToolFsmState {
if let Some(closed_segment) = &mut self.segment {
closed_segment.update_closest_point(&document.metadata, mouse_position);
if closed_segment.too_far(mouse_position, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, &document.metadata) {
closed_segment.update_closest_point(document.metadata(), input.mouse.position);
if closed_segment.too_far(input.mouse.position, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, document.metadata()) {
self.end_insertion(shape_editor, responses, InsertEndKind::Abort)
} else {
PathToolFsmState::InsertPoint
@ -317,13 +317,10 @@ impl PathToolData {
self.double_click_handled = false;
self.opposing_handle_lengths = None;
let document_network = document.network();
let document_metadata = document.metadata();
self.drag_start_pos = input.mouse.position;
// Select the first point within the threshold (in pixels)
if let Some(selected_points) = shape_editor.change_point_selection(document_network, document_metadata, input.mouse.position, SELECTION_THRESHOLD, add_to_selection) {
if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, add_to_selection) {
if let Some(selected_points) = selected_points {
self.drag_start_pos = input.mouse.position;
self.start_dragging_point(selected_points, input, document, responses);
@ -332,7 +329,7 @@ impl PathToolData {
PathToolFsmState::Dragging
}
// We didn't find a point nearby, so now we'll try to add a point into the closest path segment
else if let Some(closed_segment) = shape_editor.upper_closest_segment(document_network, document_metadata, input.mouse.position, SELECTION_TOLERANCE) {
else if let Some(closed_segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE) {
if direct_insert_without_sliding {
self.start_insertion(responses, closed_segment);
self.end_insertion(shape_editor, responses, InsertEndKind::Add { shift: add_to_selection })
@ -341,14 +338,14 @@ impl PathToolData {
}
}
// We didn't find a segment path, so consider selecting the nearest shape instead
else if let Some(layer) = document.click(input.mouse.position, &document.network) {
else if let Some(layer) = document.click(input) {
if add_to_selection {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
} else {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
}
self.drag_start_pos = input.mouse.position;
self.previous_mouse_position = document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
shape_editor.select_connected_anchors(document, layer, input.mouse.position);
PathToolFsmState::Dragging
@ -356,7 +353,7 @@ impl PathToolData {
// Start drawing a box
else {
self.drag_start_pos = input.mouse.position;
self.previous_mouse_position = document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
PathToolFsmState::DrawingBox
}
@ -383,7 +380,7 @@ impl PathToolData {
}
selected_points.points.extend(additional_selected_points);
let viewport_to_document = document.metadata.document_to_viewport.inverse();
let viewport_to_document = document.metadata().document_to_viewport.inverse();
self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position - selected_points.offset);
}
@ -397,7 +394,7 @@ impl PathToolData {
ManipulatorAngle::Mixed => false,
});
if colinear {
shape_editor.disable_colinear_handles_state_on_selected(&document.metadata, &document.network, responses);
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
} else {
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
}
@ -414,11 +411,11 @@ impl PathToolData {
fn drag(&mut self, equidistant: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
// Move the selected points with the mouse
let previous_mouse = document.metadata.document_to_viewport.transform_point2(self.previous_mouse_position);
let previous_mouse = document.metadata().document_to_viewport.transform_point2(self.previous_mouse_position);
let snapped_delta = shape_editor.snap(&mut self.snap_manager, document, input, previous_mouse);
let handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() };
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, responses);
self.previous_mouse_position += document.metadata.document_to_viewport.inverse().transform_vector2(snapped_delta);
self.previous_mouse_position += document.metadata().document_to_viewport.inverse().transform_vector2(snapped_delta);
}
}
@ -435,7 +432,7 @@ impl Fsm for PathToolFsmState {
match (self, event) {
(_, PathToolMessage::SelectionChanged) => {
// Set the newly targeted layers to visible
let target_layers = document.selected_nodes.selected_layers(document.metadata()).collect();
let target_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
shape_editor.set_selected_layers(target_layers);
responses.add(OverlaysMessage::Draw);
@ -454,7 +451,7 @@ impl Fsm for PathToolFsmState {
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
}
Self::InsertPoint => {
let state = tool_data.update_insertion(shape_editor, document, responses, input.mouse.position);
let state = tool_data.update_insertion(shape_editor, document, responses, input);
if let Some(closest_segment) = &tool_data.segment {
overlay_context.manipulator_anchor(closest_segment.closest_point_to_viewport(), false, Some(COLOR_OVERLAY_YELLOW));
@ -552,7 +549,7 @@ impl Fsm for PathToolFsmState {
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
shape_editor.select_all_in_quad(&document.network_interface, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
}
responses.add(OverlaysMessage::Draw);
@ -576,7 +573,7 @@ impl Fsm for PathToolFsmState {
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !equidistant);
shape_editor.select_all_in_quad(&document.network_interface, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !equidistant);
}
responses.add(OverlaysMessage::Draw);
responses.add(PathToolMessage::SelectedPointUpdated);
@ -586,7 +583,7 @@ impl Fsm for PathToolFsmState {
(_, PathToolMessage::DragStop { equidistant }) => {
let equidistant = input.keyboard.get(equidistant as usize);
let nearest_point = shape_editor.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD);
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
if let Some((layer, nearest_point)) = nearest_point {
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !equidistant {
@ -623,7 +620,7 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::FlipSmoothSharp) => {
if !tool_data.double_click_handled {
shape_editor.flip_smooth_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
shape_editor.flip_smooth_sharp(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE, responses);
responses.add(PathToolMessage::SelectedPointUpdated);
}
self
@ -650,18 +647,18 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, &document.network, &document.metadata, DVec2::new(new_x, coordinates.y), layer, responses);
shape_editor.reposition_control_point(&id, &document.network_interface, DVec2::new(new_x, coordinates.y), layer, responses);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, &document.network, &document.metadata, DVec2::new(coordinates.x, new_y), layer, responses);
shape_editor.reposition_control_point(&id, &document.network_interface, DVec2::new(coordinates.x, new_y), layer, responses);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointUpdated) => {
tool_data.selection_status = get_selection_status(&document.network, &document.metadata, shape_editor);
tool_data.selection_status = get_selection_status(&document.network_interface, shape_editor);
self
}
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
@ -673,7 +670,7 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.disable_colinear_handles_state_on_selected(&document.metadata, &document.network, responses);
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
responses.add(DocumentMessage::CommitTransaction);
PathToolFsmState::Ready
}
@ -776,7 +773,7 @@ struct SingleSelectedPoint {
/// Sets the cumulative description of the selected points: if `None` are selected, if `One` is selected, or if `Multiple` are selected.
/// Applies to any selected points, whether they are anchors or handles; and whether they are from a single shape or across multiple shapes.
fn get_selection_status(document_network: &NodeNetwork, document_metadata: &DocumentMetadata, shape_state: &mut ShapeState) -> SelectionStatus {
fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &mut ShapeState) -> SelectionStatus {
let mut selection_layers = shape_state.selected_shape_state.iter().map(|(k, v)| (*k, v.selected_points_count()));
let total_selected_points = selection_layers.clone().map(|(_, v)| v).sum::<usize>();
@ -785,7 +782,7 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
return SelectionStatus::None;
};
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 {
return SelectionStatus::None;
};
let Some(&point) = shape_state.selected_points().next() else {
@ -795,7 +792,7 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
return SelectionStatus::None;
};
let coordinates = document_metadata.transform_to_document(layer).transform_point2(local_position);
let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position);
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
return SelectionStatus::One(SingleSelectedPoint {
@ -809,7 +806,7 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
// Check to see if multiple manipulator groups are selected
if total_selected_points > 1 {
return SelectionStatus::Multiple(MultipleSelectedPoints {
manipulator_angle: shape_state.selected_manipulator_angles(document_network, document_metadata),
manipulator_angle: shape_state.selected_manipulator_angles(network_interface),
});
}

View File

@ -241,7 +241,7 @@ impl PenToolData {
// Break the control
let Some(last_pos) = self.latest_point().map(|point| point.pos) else { return };
let transform = document.metadata.document_to_viewport * transform;
let transform = document.metadata().document_to_viewport * transform;
let on_top = transform.transform_point2(self.next_point).distance_squared(transform.transform_point2(last_pos)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if on_top {
if let Some(point) = self.latest_point_mut() {
@ -269,9 +269,9 @@ impl PenToolData {
// Get close path
let mut end = None;
let layer = self.layer?;
let vector_data = document.metadata.compute_modified_vector(layer, &document.network)?;
let vector_data = document.metadata().compute_modified_vector(layer, &document.network_interface)?;
let start = self.latest_point()?.id;
let transform = document.metadata.document_to_viewport * transform;
let transform = document.metadata().document_to_viewport * transform;
for id in vector_data.single_connected_points().filter(|&point| point != start) {
let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue };
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
@ -348,7 +348,7 @@ impl PenToolData {
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, colinear: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
let ModifierState { snap_angle, lock_angle, .. } = self.modifiers;
let document = snap_data.document;
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
let mut document_pos = document.metadata().document_to_viewport.inverse().transform_point2(mouse);
let snap = &mut self.snap_manager;
let neighbors = relative.filter(|_| neighbor).map_or(Vec::new(), |neighbor| vec![neighbor]);
@ -455,7 +455,7 @@ impl Fsm for PenToolFsmState {
self
}
(_, PenToolMessage::Overlays(mut overlay_context)) => {
let transform = document.metadata.document_to_viewport * transform;
let transform = document.metadata().document_to_viewport * transform;
if let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {
let handles = BezierHandles::Cubic { handle_start, handle_end };
let bezier = Bezier {
@ -508,9 +508,9 @@ impl Fsm for PenToolFsmState {
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction);
let point = SnapCandidatePoint::handle(document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position));
let point = SnapCandidatePoint::handle(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
let viewport = document.metadata.document_to_viewport.transform_point2(snapped.snapped_point_document);
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
// Perform extension of an existing path
if let Some((layer, point, position)) = should_extend(document, viewport, crate::consts::SNAP_POINT_TOLERANCE) {
@ -525,10 +525,8 @@ impl Fsm for PenToolFsmState {
tool_data.next_handle_start = position;
} else {
// New path layer
let nodes = {
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
HashMap::from([(NodeId(0), node_type.to_document_node_default_inputs([], Default::default()))])
};
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
let nodes = vec![(NodeId(0), node_type.default_node_template())];
let parent = document.new_layer_parent(true);
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses);

View File

@ -244,31 +244,24 @@ impl Fsm for PolygonToolFsmState {
polygon_data.start(document, input);
responses.add(DocumentMessage::StartTransaction);
let nodes = {
let node = match tool_options.polygon_type {
PolygonType::Convex => resolve_document_node_type("Regular Polygon")
.expect("Regular Polygon node does not exist")
.to_document_node_default_inputs(
[
None,
Some(NodeInput::value(TaggedValue::U32(tool_options.vertices), false)),
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
],
Default::default(),
),
PolygonType::Star => resolve_document_node_type("Star").expect("Star node does not exist").to_document_node_default_inputs(
[
None,
Some(NodeInput::value(TaggedValue::U32(tool_options.vertices), false)),
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
Some(NodeInput::value(TaggedValue::F64(0.25), false)),
],
Default::default(),
),
};
HashMap::from([(NodeId(0), node)])
let node = match tool_options.polygon_type {
PolygonType::Convex => resolve_document_node_type("Regular Polygon")
.expect("Regular Polygon node does not exist")
.node_template_input_override([
None,
Some(NodeInput::value(TaggedValue::U32(tool_options.vertices), false)),
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
]),
PolygonType::Star => resolve_document_node_type("Star").expect("Star node does not exist").node_template_input_override([
None,
Some(NodeInput::value(TaggedValue::U32(tool_options.vertices), false)),
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
Some(NodeInput::value(TaggedValue::F64(0.25), false)),
]),
};
let nodes = vec![(NodeId(0), node)];
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);

View File

@ -207,15 +207,10 @@ impl Fsm for RectangleToolFsmState {
responses.add(DocumentMessage::StartTransaction);
let nodes = {
let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist");
let node = node_type.to_document_node_default_inputs(
[None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))],
Default::default(),
);
let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist");
let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))]);
let nodes = vec![(NodeId(0), node)];
HashMap::from([(NodeId(0), node)])
};
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);

View File

@ -8,6 +8,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Transf
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};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, NodeNetworkInterface, NodeTemplate};
use crate::messages::portfolio::document::utility_types::transformation::Selected;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
@ -15,7 +16,7 @@ use crate::messages::tool::common_functionality::pivot::Pivot;
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
use graph_craft::document::NodeId;
use graphene_core::renderer::Quad;
use graphene_std::vector::misc::BooleanOperation;
@ -288,8 +289,8 @@ impl SelectToolData {
if (self.snap_candidates.len() as f64) < document.snapping_state.tolerance {
snapping::get_layer_snap_points(layer, &SnapData::new(document, input), &mut self.snap_candidates);
}
if let Some(bounds) = document.metadata.bounding_box_with_transform(layer, DAffine2::IDENTITY) {
let quad = document.metadata.transform_to_document(layer) * Quad::from_box(bounds);
if let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) {
let quad = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);
snapping::get_bbox_points(quad, &mut self.snap_candidates, snapping::BBoxSnapValues::BOUNDING_BOX, document);
}
}
@ -310,20 +311,11 @@ impl SelectToolData {
}
/// Duplicates the currently dragging layers. Called when Alt is pressed and the layers have not yet been duplicated.
fn start_duplicates(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
fn start_duplicates(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) {
self.non_duplicated_layers = Some(self.layers_dragging.clone());
let mut new_dragging = Vec::new();
for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied().rev()) {
let Some(layer) = layer_ancestors.last().copied() else { continue };
// `layer` cannot be `ROOT_PARENT`, since `ROOT_PARENT` cannot be part of `layers_dragging`
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be in layers_dragging");
continue;
}
// `parent` can be `ROOT_PARENT`
let Some(parent) = layer.parent(&document.metadata) else { continue };
for layer in document.network_interface.shallowest_unique_layers(&[]) {
let Some(parent) = layer.parent(document.metadata()) else { continue };
// Moves the layer back to its starting position.
responses.add(GraphOperationMessage::TransformChange {
@ -335,36 +327,32 @@ impl SelectToolData {
// Copy the layer
let mut copy_ids = HashMap::new();
let node = layer.to_node();
copy_ids.insert(node, NodeId(0_u64));
if let Some(input_node) = document
.network()
.nodes
.get(&node)
.and_then(|node| if node.is_layer { node.inputs.get(1) } else { node.inputs.first() })
.and_then(|input| input.as_node())
{
document
.network()
.upstream_flow_back_from_nodes(vec![input_node], graph_craft::document::FlowType::UpstreamFlow)
.enumerate()
.for_each(|(index, (_, node_id))| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
};
let nodes: HashMap<NodeId, DocumentNode> =
NodeGraphMessageHandler::copy_nodes(document.network(), &document.node_graph_handler.network, &document.node_graph_handler.resolved_types, &copy_ids).collect();
let node_id = layer.to_node();
copy_ids.insert(node_id, NodeId(0));
let insert_index = DocumentMessageHandler::get_calculated_insert_index(&document.metadata, &document.selected_nodes, parent);
document
.network_interface
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)
.enumerate()
.for_each(|(index, node_id)| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
let nodes = document.network_interface.copy_nodes(&copy_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();
let insert_index = DocumentMessageHandler::get_calculated_insert_index(document.metadata(), document.network_interface.selected_nodes(&[]).unwrap(), parent);
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId(generate_uuid()))).collect();
let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer");
responses.add(GraphOperationMessage::AddNodesAsChild { nodes, new_ids, parent, insert_index });
new_dragging.push(LayerNodeIdentifier::new_unchecked(layer_id));
let layer = LayerNodeIdentifier::new_unchecked(layer_id);
new_dragging.push(layer);
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
}
let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
self.layers_dragging = new_dragging;
}
@ -375,12 +363,7 @@ impl SelectToolData {
};
// Delete the duplicated layers
for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied()) {
let layer = layer_ancestors.last().unwrap();
if *layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be in layers_dragging");
continue;
}
for layer in document.network_interface.shallowest_unique_layers(&[]) {
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![layer.to_node()],
reconnect: true,
@ -425,19 +408,26 @@ impl Fsm for SelectToolFsmState {
(_, SelectToolMessage::Overlays(mut overlay_context)) => {
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
let selected_layers_count = document.selected_nodes.selected_unlocked_layers(document.metadata()).count();
let selected_layers_count = document.network_interface.selected_nodes(&[]).unwrap().selected_unlocked_layers(&document.network_interface).count();
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
tool_data.selected_layers_count = selected_layers_count;
// Outline selected layers
for layer in document.selected_nodes.selected_visible_and_unlocked_layers(document.metadata()) {
for layer in document
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface)
{
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
}
// Update bounds
let transform = document
.selected_nodes
.selected_visible_and_unlocked_layers(document.metadata())
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface)
.next()
.map(|layer| document.metadata().transform_to_viewport(layer));
let transform = transform.unwrap_or(DAffine2::IDENTITY);
@ -445,8 +435,10 @@ impl Fsm for SelectToolFsmState {
return self;
}
let bounds = document
.selected_nodes
.selected_visible_and_unlocked_layers(document.metadata())
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface)
.filter_map(|layer| {
document
.metadata()
@ -473,16 +465,17 @@ impl Fsm for SelectToolFsmState {
let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
// Draw outline visualizations on the layers to be selected
for layer in document.intersect_quad(quad, &document.network) {
for layer in document.intersect_quad(quad, input) {
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
}
// Update the selection box
overlay_context.quad(quad);
} else {
// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)
} else if !input.keyboard.get(Key::Mmb as usize) {
// Get the layer the user is hovering over
let click = document.click(input.mouse.position, &document.network);
let not_selected_click = click.filter(|&hovered_layer| !document.selected_nodes.selected_layers_contains(hovered_layer, document.metadata()));
let click = document.click(input);
let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes(&[]).unwrap().selected_layers_contains(hovered_layer, document.metadata()));
if let Some(layer) = not_selected_click {
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
}
@ -492,10 +485,10 @@ impl Fsm for SelectToolFsmState {
}
(_, SelectToolMessage::EditLayer) => {
// Edit the clicked layer
if let Some(intersect) = document.click(input.mouse.position, &document.network) {
if let Some(intersect) = document.click(input) {
match tool_data.nested_selection_behavior {
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect, responses),
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.network, responses),
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.network_interface, responses),
}
}
@ -523,9 +516,14 @@ impl Fsm for SelectToolFsmState {
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
.unwrap_or_default();
let mut selected: Vec<_> = document.selected_nodes.selected_visible_and_unlocked_layers(document.metadata()).collect();
let intersection_list = document.click_list(input.mouse.position, &document.network).collect::<Vec<_>>();
let intersection = document.find_deepest(&intersection_list, &document.network);
let mut selected: Vec<_> = document
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface)
.collect();
let intersection_list = document.click_list(input).collect::<Vec<_>>();
let intersection = document.find_deepest(&intersection_list);
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
// If the user is dragging the rotate trigger, go into RotatingBounds mode.
@ -554,7 +552,7 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network.nodes.contains_key(&layer.to_node())
document.network_interface.network(&[]).unwrap().nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of layers_dragging");
false
@ -566,8 +564,7 @@ impl Fsm for SelectToolFsmState {
&mut bounds.center_of_transformation,
&tool_data.layers_dragging,
responses,
&document.network,
&document.metadata,
&document.network_interface,
None,
&ToolType::Select,
);
@ -584,7 +581,7 @@ impl Fsm for SelectToolFsmState {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network.nodes.contains_key(&layer.to_node())
document.network_interface.network(&[]).unwrap().nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of layers_dragging");
false
@ -595,8 +592,7 @@ impl Fsm for SelectToolFsmState {
&mut bounds.center_of_transformation,
&selected,
responses,
&document.network,
&document.metadata,
&document.network_interface,
None,
&ToolType::Select,
);
@ -615,7 +611,7 @@ impl Fsm for SelectToolFsmState {
if tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest {
tool_data.select_single_layer = intersection;
} else {
tool_data.select_single_layer = intersection.and_then(|intersection| intersection.ancestors(&document.metadata).find(|ancestor| selected.contains(ancestor)));
tool_data.select_single_layer = intersection.and_then(|intersection| intersection.ancestors(document.metadata()).find(|ancestor| selected.contains(ancestor)));
}
tool_data.layers_dragging = selected;
@ -686,9 +682,9 @@ impl Fsm for SelectToolFsmState {
let mouse_delta = snap_drag(start, current, axis_align, snap_data, &mut tool_data.snap_manager, &tool_data.snap_candidates);
// 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 document.metadata().shallowest_unique_layers(tool_data.layers_dragging.iter().copied()) {
for layer in document.network_interface.shallowest_unique_layers(&[]) {
responses.add_front(GraphOperationMessage::TransformChange {
layer: *layer_ancestors.last().unwrap(),
layer,
transform: DAffine2::from_translation(mouse_delta),
transform_in: TransformIn::Viewport,
skip_rerender: false,
@ -724,23 +720,14 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network.nodes.contains_key(&layer.to_node())
document.network_interface.network(&[]).unwrap().nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of layers_dragging");
false
}
});
let selected = &tool_data.layers_dragging;
let mut selected = Selected::new(
&mut bounds.original_transforms,
&mut pivot,
selected,
responses,
&document.network,
&document.metadata,
None,
&ToolType::Select,
);
let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, selected, responses, &document.network_interface, None, &ToolType::Select);
selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse());
@ -774,7 +761,7 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network().nodes.contains_key(&layer.to_node())
document.network_interface.network(&[]).unwrap().nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of replacement_selected_layers");
false
@ -785,8 +772,7 @@ impl Fsm for SelectToolFsmState {
&mut bounds.center_of_transformation,
&tool_data.layers_dragging,
responses,
&document.network,
&document.metadata,
&document.network_interface,
None,
&ToolType::Select,
);
@ -902,11 +888,13 @@ impl Fsm for SelectToolFsmState {
// Deselect layer if not snap dragging
if !tool_data.has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() {
let quad = tool_data.selection_quad();
let intersection = document.intersect_quad(quad, &document.network);
let intersection = document.intersect_quad(quad, input);
if let Some(path) = intersection.last() {
let replacement_selected_layers: Vec<_> = document
.selected_nodes
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_layers(document.metadata())
.filter(|&layer| !path.starts_with(layer, document.metadata()))
.collect();
@ -994,8 +982,8 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::DrawingBox { .. }, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
let quad = tool_data.selection_quad();
let new_selected: HashSet<_> = document.intersect_quad(quad, &document.network).collect();
let current_selected: HashSet<_> = document.selected_nodes.selected_layers(document.metadata()).collect();
let new_selected: HashSet<_> = document.intersect_quad(quad, input).collect();
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
if new_selected != current_selected {
tool_data.layers_dragging = new_selected.into_iter().collect();
responses.add(DocumentMessage::StartTransaction);
@ -1020,11 +1008,11 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Ready { selection }
}
(SelectToolFsmState::Ready { .. }, SelectToolMessage::Enter) => {
let mut selected_layers = document.selected_nodes.selected_layers(document.metadata());
let mut selected_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata());
if let Some(layer) = selected_layers.next() {
// Check that only one layer is selected
if selected_layers.next().is_none() && is_layer_fed_by_node_of_name(layer, &document.network, "Text") {
if selected_layers.next().is_none() && is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text") {
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
responses.add(TextToolMessage::EditSelected);
}
@ -1044,7 +1032,7 @@ impl Fsm for SelectToolFsmState {
(_, SelectToolMessage::Abort) => {
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network().nodes.contains_key(&layer.to_node())
document.network_interface.network(&[]).unwrap().nodes.contains_key(&layer.to_node())
} else {
false
}
@ -1055,8 +1043,7 @@ impl Fsm for SelectToolFsmState {
&mut bounding_box_overlays.opposite_pivot,
&tool_data.layers_dragging,
responses,
&document.network,
&document.metadata,
&document.network_interface,
None,
&ToolType::Select,
);
@ -1155,7 +1142,7 @@ impl Fsm for SelectToolFsmState {
}
fn not_artboard(document: &DocumentMessageHandler) -> impl Fn(&LayerNodeIdentifier) -> bool + '_ {
|&layer| !document.metadata.is_artboard(layer)
|&layer| !document.network_interface.is_artboard(&layer.to_node(), &[])
}
fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) {
@ -1163,7 +1150,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
let ancestor = layer
.ancestors(document.metadata())
.filter(not_artboard(document))
.find(|&ancestor| document.selected_nodes.selected_layers_contains(ancestor, document.metadata()));
.find(|&ancestor| document.network_interface.selected_nodes(&[]).unwrap().selected_layers_contains(ancestor, document.metadata()));
let new_selected = ancestor.unwrap_or_else(|| {
layer
@ -1194,12 +1181,10 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
}
fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) {
tool_data
.layers_dragging
.append(&mut vec![document.find_deepest(&selected, &document.network).unwrap_or(LayerNodeIdentifier::new(
document.network.get_root_node().expect("Root node should exist when dragging layers").id,
&document.network,
))]);
tool_data.layers_dragging.append(&mut vec![document.find_deepest(&selected).unwrap_or(LayerNodeIdentifier::new(
document.network_interface.root_node(&[]).expect("Root node should exist when dragging layers").node_id,
&document.network_interface,
))]);
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data
.layers_dragging
@ -1217,7 +1202,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<La
}
fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
if document.selected_nodes.selected_layers_contains(layer, document.metadata()) {
if document.network_interface.selected_nodes(&[]).unwrap().selected_layers_contains(layer, document.metadata()) {
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path });
return;
}
@ -1225,7 +1210,7 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer:
let Some(new_selected) = layer.ancestors(document.metadata()).filter(not_artboard(document)).find(|ancestor| {
ancestor
.parent(document.metadata())
.is_some_and(|parent| document.selected_nodes.selected_layers_contains(parent, document.metadata()))
.is_some_and(|parent| document.network_interface.selected_nodes(&[]).unwrap().selected_layers_contains(parent, document.metadata()))
}) else {
return;
};
@ -1238,11 +1223,11 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer:
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_selected.to_node()] });
}
fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document_network: &NodeNetwork, responses: &mut VecDeque<Message>) {
if is_layer_fed_by_node_of_name(layer, document_network, "Text") {
fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface, responses: &mut VecDeque<Message>) {
if is_layer_fed_by_node_of_name(layer, network_interface, "Text") {
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
responses.add(TextToolMessage::EditSelected);
} else if is_layer_fed_by_node_of_name(layer, document_network, "Path") {
} else if is_layer_fed_by_node_of_name(layer, network_interface, "Path") {
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path });
}
}

View File

@ -216,12 +216,10 @@ impl Fsm for SplineToolFsmState {
tool_data.weight = tool_options.line_weight;
let nodes = {
let node_type = resolve_document_node_type("Spline").expect("Spline node does not exist");
let node = node_type.to_document_node_default_inputs([None, Some(NodeInput::value(TaggedValue::VecDVec2(Vec::new()), false))], Default::default());
let node_type = resolve_document_node_type("Spline").expect("Spline node does not exist");
let node = node_type.node_template_input_override([None, Some(NodeInput::value(TaggedValue::VecDVec2(Vec::new()), false))]);
let nodes = vec![(NodeId(0), node)];
HashMap::from([(NodeId(0), node)])
};
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses);
tool_options.fill.apply_fill(layer, responses);
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
@ -330,7 +328,7 @@ fn update_spline(document: &DocumentMessageHandler, tool_data: &SplineToolData,
let Some(layer) = tool_data.layer else { return };
let Some(node_id) = graph_modification_utils::NodeGraphLayer::new(layer, document.network()).upstream_node_id_from_name("Spline") else {
let Some(node_id) = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface).upstream_node_id_from_name("Spline") else {
return;
};
responses.add_front(NodeGraphMessage::SetInputValue { node_id, input_index: 1, value });

View File

@ -5,11 +5,12 @@ use crate::application::generate_uuid;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
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::network_interface::InputConnector;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeId;
use graph_craft::document::{NodeId, NodeInput};
use graphene_core::renderer::Quad;
use graphene_core::text::{load_face, Font, FontCache};
use graphene_core::vector::style::Fill;
@ -213,9 +214,8 @@ struct TextToolData {
impl TextToolData {
/// Set the editing state of the currently modifying layer
fn set_editing(&self, editable: bool, font_cache: &FontCache, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
// TODO: Should always set visibility for document network, but `node_id` is not a layer so it crashes
if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.network) {
responses.add(GraphOperationMessage::SetVisibility { node_id, visible: !editable });
if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.network_interface) {
responses.add(NodeGraphMessage::SetVisibility { node_id, visible: !editable });
}
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
@ -234,8 +234,8 @@ impl TextToolData {
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler) -> Option<()> {
let transform = document.metadata().transform_to_viewport(self.layer);
let color = graph_modification_utils::get_fill_color(self.layer, &document.network).unwrap_or(Color::BLACK);
let (text, font, font_size) = graph_modification_utils::get_text(self.layer, &document.network)?;
let color = graph_modification_utils::get_fill_color(self.layer, &document.network_interface).unwrap_or(Color::BLACK);
let (text, font, font_size) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;
self.editing_text = Some(EditingText {
text: text.clone(),
font: font.clone(),
@ -266,12 +266,16 @@ impl TextToolData {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
}
fn interact(&mut self, state: TextToolFsmState, mouse: DVec2, document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque<Message>) -> TextToolFsmState {
fn interact(
&mut self,
state: TextToolFsmState,
input: &InputPreprocessorMessageHandler,
document: &DocumentMessageHandler,
font_cache: &FontCache,
responses: &mut VecDeque<Message>,
) -> TextToolFsmState {
// Check if the user has selected an existing text layer
if let Some(clicked_text_layer_path) = document
.click(mouse, document.network())
.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.network, "Text"))
{
if let Some(clicked_text_layer_path) = document.click(input).filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text")) {
self.start_editing_layer(clicked_text_layer_path, state, document, font_cache, responses);
TextToolFsmState::Editing
@ -288,7 +292,7 @@ impl TextToolData {
font: editing_text.font.clone(),
size: editing_text.font_size,
parent: document.new_layer_parent(true),
insert_index: -1,
insert_index: 0,
});
responses.add(GraphOperationMessage::FillSet {
layer: self.layer,
@ -316,7 +320,7 @@ impl TextToolData {
}
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
let mut selected_layers = document.selected_nodes.selected_layers(document.metadata());
let mut selected_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata());
let layer = selected_layers.next()?;
// Check that only one layer is selected
@ -324,7 +328,7 @@ fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdent
return None;
}
if !is_layer_fed_by_node_of_name(layer, &document.network, "Text") {
if !is_layer_fed_by_node_of_name(layer, &document.network_interface, "Text") {
return None;
}
@ -364,8 +368,8 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Editing
}
(_, TextToolMessage::Overlays(mut overlay_context)) => {
for layer in document.selected_nodes.selected_layers(document.metadata()) {
let Some((text, font, font_size)) = graph_modification_utils::get_text(layer, &document.network) else {
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
let Some((text, font, font_size)) = graph_modification_utils::get_text(layer, &document.network_interface) else {
continue;
};
let buzz_face = font_cache.get(font).map(|data| load_face(data));
@ -387,7 +391,7 @@ impl Fsm for TextToolFsmState {
});
tool_data.new_text = String::new();
tool_data.interact(state, input.mouse.position, document, font_cache, responses)
tool_data.interact(state, input, document, font_cache, responses)
}
(state, TextToolMessage::EditSelected) => {
if let Some(layer) = can_edit_selected(document) {
@ -410,10 +414,9 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Editing
}
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
responses.add(NodeGraphMessage::SetQualifiedInputValue {
node_id: graph_modification_utils::get_text_id(tool_data.layer, &document.network).unwrap(),
input_index: 1,
value: TaggedValue::String(new_text),
responses.add(NodeGraphMessage::SetInput {
input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1),
input: NodeInput::value(TaggedValue::String(new_text), false),
});
tool_data.set_editing(false, font_cache, document, responses);

View File

@ -44,10 +44,13 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
// TODO: Add support for transforming layer not in the document network
let selected_layers = document
.selected_nodes
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_layers(document.metadata())
.filter(|&layer| document.metadata().node_is_visible(layer.to_node()) && !document.metadata().node_is_locked(layer.to_node()))
.filter(|&layer| document.network_interface.is_visible(&layer.to_node(), &[]) && !document.network_interface.is_locked(&layer.to_node(), &[]))
.collect::<Vec<_>>();
let mut selected = Selected::new(
@ -55,8 +58,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
&mut self.pivot,
&selected_layers,
responses,
&document.network,
&document.metadata,
&document.network_interface,
Some(shape_editor),
&tool_data.active_tool_type,
);
@ -68,7 +70,10 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
}
if using_path_tool {
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.metadata.compute_modified_vector(layer, &document.network)) {
if let Some(vector_data) = selected_layers
.first()
.and_then(|&layer| document.metadata().compute_modified_vector(layer, &document.network_interface))
{
*selected.original_transforms = OriginalTransforms::default();
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
@ -212,7 +217,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.mouse_position = input.mouse.position;
}
TransformLayerMessage::SelectionChanged => {
let target_layers = document.selected_nodes.selected_layers(document.metadata()).collect();
let target_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
shape_editor.set_selected_layers(target_layers);
}
TransformLayerMessage::TypeBackspace => self.transform_operation.grs_typed(self.typing.type_backspace(), &mut selected, self.snap),

View File

@ -18,7 +18,7 @@ use graphene_std::text::FontCache;
use std::fmt::{self, Debug};
pub struct ToolActionHandlerData<'a> {
pub document: &'a DocumentMessageHandler,
pub document: &'a mut DocumentMessageHandler,
pub document_id: DocumentId,
pub global_tool_data: &'a DocumentToolData,
pub input: &'a InputPreprocessorMessageHandler,
@ -28,7 +28,7 @@ pub struct ToolActionHandlerData<'a> {
}
impl<'a> ToolActionHandlerData<'a> {
pub fn new(
document: &'a DocumentMessageHandler,
document: &'a mut DocumentMessageHandler,
document_id: DocumentId,
global_tool_data: &'a DocumentToolData,
input: &'a InputPreprocessorMessageHandler,

View File

@ -472,15 +472,17 @@ impl NodeGraphExecutor {
/// Evaluates a node graph, computing the entire graph
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2, ignore_hash: bool) -> Result<(), String> {
// Get the node graph layer
let network_hash = document.network().current_hash();
let network_hash = document.network_interface.network(&[]).unwrap().current_hash();
if network_hash != self.node_graph_hash || ignore_hash {
self.node_graph_hash = network_hash;
self.sender.send(NodeRuntimeMessage::GraphUpdate(document.network.clone())).map_err(|e| e.to_string())?;
self.sender
.send(NodeRuntimeMessage::GraphUpdate(document.network_interface.network(&[]).unwrap().clone()))
.map_err(|e| e.to_string())?;
}
let render_config = RenderConfig {
viewport: Footprint {
transform: document.metadata.document_to_viewport,
transform: document.metadata().document_to_viewport,
resolution: viewport_resolution,
..Default::default()
},
@ -503,12 +505,12 @@ impl NodeGraphExecutor {
/// Evaluates a node graph for export
pub fn submit_document_export(&mut self, document: &mut DocumentMessageHandler, mut export_config: ExportConfig) -> Result<(), String> {
let network = document.network().clone();
let network = document.network_interface.network(&[]).unwrap().clone();
// Calculate the bounding box of the region to be exported
let bounds = match export_config.bounds {
ExportBounds::AllArtwork => document.metadata().document_bounds_document_space(!export_config.transparent_background),
ExportBounds::Selection => document.metadata().selected_bounds_document_space(!export_config.transparent_background, &document.selected_nodes),
ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!export_config.transparent_background),
ExportBounds::Selection => document.network_interface.selected_bounds_document_space(!export_config.transparent_background, &[]),
ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id),
}
.ok_or_else(|| "No bounding box".to_string())?;
@ -587,15 +589,15 @@ impl NodeGraphExecutor {
Ok(output) => output,
Err(e) => {
// Clear the click targets while the graph is in an un-renderable state
document.metadata.update_from_monitor(HashMap::new(), HashMap::new());
document.network_interface.document_metadata_mut().update_from_monitor(HashMap::new(), HashMap::new());
return Err(format!("Node graph evaluation failed:\n{e}"));
}
};
responses.extend(existing_responses.into_iter().map(Into::into));
document.metadata.update_transforms(new_upstream_transforms);
document.metadata.update_from_monitor(new_click_targets, new_vector_modify);
document.network_interface.document_metadata_mut().update_transforms(new_upstream_transforms);
document.network_interface.document_metadata_mut().update_from_monitor(new_click_targets, new_vector_modify);
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
if let Some(export_config) = execution_context.export_config {
@ -613,14 +615,14 @@ impl NodeGraphExecutor {
} = execution_response;
if let Err(e) = result {
// Clear the click targets while the graph is in an un-renderable state
document.metadata.update_from_monitor(HashMap::new(), HashMap::new());
document.network_interface.document_metadata_mut().update_from_monitor(HashMap::new(), HashMap::new());
log::trace!("{e}");
return Err("Node graph evaluation failed".to_string());
};
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors });
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh));

View File

@ -371,11 +371,12 @@
</LayoutRow>
<LayoutRow class="list-area" scrollableY={true}>
<LayoutCol class="list" bind:this={list} on:click={() => deselectAllLayers()} on:dragover={(e) => draggable && updateInsertLine(e)} on:dragend={() => draggable && drop()}>
{#each layers as listing, index (String(listing.entry.id))}
{#each layers as listing, index}
<LayoutRow
class="layer"
classes={{
selected: fakeHighlight !== undefined ? fakeHighlight === listing.entry.id : $nodeGraph.selected.includes(listing.entry.id),
selected: fakeHighlight !== undefined ? fakeHighlight === listing.entry.id : listing.entry.selected,
"in-selected-network": listing.entry.inSelectedNetwork,
"insert-folder": (draggingData?.highlightFolder || false) && draggingData?.insertParentId === listing.entry.id,
}}
styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }}
@ -497,8 +498,13 @@
margin: 0 4px;
padding-left: calc(var(--layer-indent-levels) * 16px);
// Dimming
&.selected {
background: var(--color-4-dimgray);
background: #404040;
&.in-selected-network {
background: var(--color-4-dimgray);
}
}
&.insert-folder {

View File

@ -6,6 +6,7 @@
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { IconName } from "@graphite/utility-functions/icons";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { Node } from "@graphite/wasm-communication/messages";
import type { FrontendNodeWire, FrontendNodeType, FrontendNode, FrontendGraphInput, FrontendGraphOutput, FrontendGraphDataType, WirePath } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
@ -35,6 +36,7 @@
let nodeWirePaths: WirePath[] = [];
let searchTerm = "";
// TODO: Convert these arrays-of-arrays to a Map?
let inputs: SVGSVGElement[][] = [];
let outputs: SVGSVGElement[][] = [];
let nodeElements: HTMLDivElement[] = [];
@ -46,7 +48,7 @@
$: nodeCategories = buildNodeCategories($nodeGraph.nodeTypes, searchTerm);
$: (() => {
if ($nodeGraph.contextMenuInformation?.contextMenuData == "CreateNode") {
if ($nodeGraph.contextMenuInformation?.contextMenuData === "CreateNode") {
setTimeout(() => nodeSearchInput?.focus(), 0);
}
})();
@ -107,49 +109,76 @@
return [...maybeWirePathInProgress, ...nodeWirePaths];
}
async function watchNodes(nodes: FrontendNode[]) {
nodes.forEach((_, index) => {
if (!inputs[index]) inputs[index] = [];
if (!outputs[index]) outputs[index] = [];
async function watchNodes(nodes: Map<bigint, FrontendNode>) {
Array.from(nodes.keys()).forEach((_, index) => {
if (!inputs[index + 1]) inputs[index + 1] = [];
if (!outputs[index + 1]) outputs[index + 1] = [];
});
if (!inputs[0]) inputs[0] = [];
if (!outputs[0]) outputs[0] = [];
await refreshWires();
}
function resolveWire(wire: FrontendNodeWire): { nodeOutput: SVGSVGElement | undefined; nodeInput: SVGSVGElement | undefined } {
const outputIndex = Number(wire.wireStartOutputIndex);
const inputIndex = Number(wire.wireEndInputIndex);
// TODO: Avoid the linear search
const wireStartNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireStart as Node).nodeId);
let nodeOutputConnectors = outputs[wireStartNodeIdIndex + 1];
if (nodeOutputConnectors === undefined && (wire.wireStart as Node).nodeId === undefined) {
nodeOutputConnectors = outputs[0];
}
const indexOutput = Number(wire.wireStart.index);
const nodeOutput = nodeOutputConnectors?.[indexOutput] as SVGSVGElement | undefined;
const nodeOutputConnectors = outputs[$nodeGraph.nodes.findIndex((n) => n.id === wire.wireStart)];
const nodeInputConnectors = inputs[$nodeGraph.nodes.findIndex((n) => n.id === wire.wireEnd)] || undefined;
// TODO: Avoid the linear search
const wireEndNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireEnd as Node).nodeId);
let nodeInputConnectors = inputs[wireEndNodeIdIndex + 1] || undefined;
if (nodeInputConnectors === undefined && (wire.wireEnd as Node).nodeId === undefined) {
nodeInputConnectors = inputs[0];
}
const indexInput = Number(wire.wireEnd.index);
const nodeInput = nodeInputConnectors?.[indexInput] as SVGSVGElement | undefined;
const nodeOutput = nodeOutputConnectors?.[outputIndex] as SVGSVGElement | undefined;
const nodeInput = nodeInputConnectors?.[inputIndex] as SVGSVGElement | undefined;
return { nodeOutput, nodeInput };
}
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement, verticalOut: boolean, verticalIn: boolean, dashed: boolean): WirePath {
const inputPortRect = inputPort.getBoundingClientRect();
const outputPortRect = outputPort.getBoundingClientRect();
const pathString = buildWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
const thick = verticalIn && verticalOut;
return { pathString, dataType, thick, dashed };
}
async function refreshWires() {
await tick();
const wires = $nodeGraph.wires;
nodeWirePaths = wires.flatMap((wire) => {
const { nodeInput, nodeOutput } = resolveWire(wire);
if (!nodeInput || !nodeOutput) return [];
nodeWirePaths = $nodeGraph.wires.flatMap((wire) => {
// TODO: This call contains linear searches, which combined with the loop we're in, causes O(n^2) complexity as the graph grows
const { nodeOutput, nodeInput } = resolveWire(wire);
if (!nodeOutput || !nodeInput) return [];
const wireStart = $nodeGraph.nodes.find((n) => n.id === wire.wireStart)?.isLayer || false;
const wireEnd = ($nodeGraph.nodes.find((n) => n.id === wire.wireEnd)?.isLayer && Number(wire.wireEndInputIndex) == 0) || false;
const wireStartNode = $nodeGraph.nodes.get((wire.wireStart as Node).nodeId);
const wireStart = wireStartNode?.isLayer || false;
return [createWirePath(nodeOutput, nodeInput.getBoundingClientRect(), wireStart, wireEnd, wire.dashed)];
const wireEndNode = $nodeGraph.nodes.get((wire.wireEnd as Node).nodeId);
const wireEnd = (wireEndNode?.isLayer && Number(wire.wireEnd.index) === 0) || false;
return [createWirePath(nodeOutput, nodeInput, wireStart, wireEnd, wire.dashed)];
});
}
onMount(refreshWires);
function nodeIcon(nodeName: string): IconName {
function nodeIcon(icon?: string): IconName {
if (!icon) return "NodeNodes";
const iconMap: Record<string, IconName> = {
Output: "NodeOutput",
};
return iconMap[nodeName] || "NodeNodes";
return iconMap[icon] || "NodeNodes";
}
function buildWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
@ -219,25 +248,13 @@
.join(" ");
}
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement | DOMRect, verticalOut: boolean, verticalIn: boolean, dashed: boolean): WirePath {
const inputPortRect = inputPort instanceof DOMRect ? inputPort : inputPort.getBoundingClientRect();
const outputPortRect = outputPort.getBoundingClientRect();
const pathString = buildWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
return { pathString, dataType, thick: verticalIn && verticalOut, dashed };
}
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
let node = $nodeGraph.nodes.find((node) => node.id === toggleId);
if (node !== undefined) {
editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
}
let node = $nodeGraph.nodes.get(toggleId);
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
}
function canBeToggledBetweenNodeAndLayer(toggleDisplayAsLayerNodeId: bigint) {
return $nodeGraph.nodes.find((node) => node.id === toggleDisplayAsLayerNodeId)?.canBeLayer || false;
return $nodeGraph.nodes.get(toggleDisplayAsLayerNodeId)?.canBeLayer || false;
}
function createNode(nodeType: string) {
@ -296,13 +313,52 @@
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${value.dataType}`;
}
function connectedToText(output: FrontendGraphOutput): string {
if (output.connected.length === 0) {
function outputConnectedToText(output: FrontendGraphOutput): string {
if (output.connectedTo.length === 0) {
return "Connected to nothing";
} else {
return output.connected.map((nodeId, index) => `Connected to ${nodeId}, port index ${output.connectedIndex[index]}`).join("\n");
return output.connectedTo
.map((inputConnector) => {
if ((inputConnector as Node).nodeId === undefined) {
return `Connected to export index ${inputConnector.index}`;
} else {
return `Connected to ${(inputConnector as Node).nodeId}, port index ${inputConnector.index}`;
}
})
.join("\n");
}
}
function inputConnectedToText(input: FrontendGraphInput): string {
if (input.connectedTo === undefined) {
return "Connected to nothing";
} else {
if ((input.connectedTo as Node).nodeId === undefined) {
return `Connected to import index ${input.connectedTo.index}`;
} else {
return `Connected to ${(input.connectedTo as Node).nodeId}, port index ${input.connectedTo.index}`;
}
}
}
function primaryOutputConnectedToLayer(node: FrontendNode): boolean {
let firstConnectedNode = Array.from($nodeGraph.nodes.values()).find((n) =>
node.primaryOutput?.connectedTo.some((connector) => {
if ((connector as Node).nodeId === undefined) return false;
if (connector.index !== 0n) return false;
return n.id === (connector as Node).nodeId || false;
}),
);
return firstConnectedNode?.isLayer || false;
}
function primaryInputConnectedToLayer(node: FrontendNode): boolean {
const connectedNode = Array.from($nodeGraph.nodes.values()).find((n) => {
if ((node.primaryInput?.connectedTo as Node) === undefined) return false;
return n.id === (node.primaryInput?.connectedTo as Node).nodeId;
});
return connectedNode?.isLayer || false;
}
</script>
<div
@ -375,20 +431,93 @@
{/if}
</LayoutCol>
{/if}
<!-- Click target debug visualizations -->
{#if $nodeGraph.clickTargets}
<div class="click-targets" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
<svg>
{#each $nodeGraph.clickTargets.nodeClickTargets as pathString}
<path class="node" d={pathString} />
{/each}
{#each $nodeGraph.clickTargets.layerClickTargets as pathString}
<path class="layer" d={pathString} />
{/each}
{#each $nodeGraph.clickTargets.portClickTargets as pathString}
<path class="port" d={pathString} />
{/each}
{#each $nodeGraph.clickTargets.visibilityClickTargets as pathString}
<path class="visibility" d={pathString} />
{/each}
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.allNodesBoundingBox} />
</svg>
</div>
{/if}
<!-- Node connection wires -->
<div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
<svg>
{#each wirePaths as { pathString, dataType, thick, dashed }}
<path
d={pathString}
style:--data-line-width={`${thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/>
{#if thick}
<path
d={pathString}
style:--data-line-width={`${thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/>
{/if}
{/each}
</svg>
</div>
<!-- Import and Export ports -->
<div class="imports-and-exports" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
{#each $nodeGraph.imports as { outputMetadata, position }, index}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
data-port="output"
data-datatype={outputMetadata.dataType}
style:--data-color={`var(--color-data-${outputMetadata.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
style:--offset-left={position.x / 24}
style:--offset-top={position.y / 24}
bind:this={outputs[0][index]}
>
<title>{`${dataTypeTooltip(outputMetadata)}\n${outputConnectedToText(outputMetadata)}`}</title>
{#if outputMetadata.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
{/if}
</svg>
<p class="import-text" style:--offset-left={position.x / 24} style:--offset-top={position.y / 24}>{outputMetadata.name}</p>
{/each}
{#each $nodeGraph.exports as { inputMetadata, position }, index}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 8 8"
class="port"
data-port="input"
data-datatype={inputMetadata.dataType}
style:--data-color={`var(--color-data-${inputMetadata.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
style:--offset-left={position.x / 24}
style:--offset-top={position.y / 24}
bind:this={inputs[0][index]}
>
<title>{`${dataTypeTooltip(inputMetadata)}\n${inputConnectedToText(inputMetadata)}`}</title>
{#if inputMetadata.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
{/if}
</svg>
<p class="export-text" style:--offset-left={position.x / 24} style:--offset-top={position.y / 24}>{inputMetadata.name}</p>
{/each}
</div>
<!-- Layers and nodes -->
<div
class="layers-and-nodes"
@ -397,22 +526,24 @@
bind:this={nodesContainer}
>
<!-- Layers -->
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
{@const clipPathId = String(Math.random()).substring(2)}
{@const stackDataInput = node.exposedInputs[0]}
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
{@const layerChainWidth = $nodeGraph.chainWidths.get(node.id) || 0}
<div
class="layer"
class:selected={$nodeGraph.selected.includes(node.id)}
class:in-selected-network={$nodeGraph.inSelectedNetwork}
class:previewed={node.previewed}
class:disabled={!node.visible}
style:--offset-left={(node.position?.x || 0) - 1}
style:--offset-left={node.position?.x || 0}
style:--offset-top={node.position?.y || 0}
style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
style:--layer-area-width={layerAreaWidth}
style:--node-chain-area-left-extension={node.exposedInputs.length === 0 ? 0 : 1.5}
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
data-node={node.id}
bind:this={nodeElements[nodeIndex]}
>
@ -434,12 +565,12 @@
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][0]}
bind:this={outputs[nodeIndex + 1][0]}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${connectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connected.length > 0}
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo.length > 0}
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
{#if Number(node.primaryOutput?.connectedIndex) === 0 && $nodeGraph.nodes.find((n) => node.primaryOutput?.connected.includes(n.id))?.isLayer}
{#if primaryOutputConnectedToLayer(node)}
<path d="M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z" fill="var(--data-color-dim)" />
{/if}
{:else}
@ -456,14 +587,14 @@
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][0]}
bind:this={inputs[nodeIndex + 1][0]}
>
{#if node.primaryInput}
<title>{`${dataTypeTooltip(node.primaryInput)}\nConnected to ${node.primaryInput?.connected !== undefined ? node.primaryInput.connected : "nothing"}`}</title>
<title>{`${dataTypeTooltip(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`}</title>
{/if}
{#if node.primaryInput?.connected !== undefined}
{#if node.primaryInput?.connectedTo !== undefined}
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
{#if $nodeGraph.nodes.find((n) => n.id === node.primaryInput?.connected)?.isLayer}
{#if primaryInputConnectedToLayer(node)}
<path d="M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z" fill="var(--data-color-dim)" />
{/if}
{:else}
@ -482,10 +613,10 @@
data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][1]}
bind:this={inputs[nodeIndex + 1][1]}
>
<title>{`${dataTypeTooltip(stackDataInput)}\nConnected to ${stackDataInput.connected !== undefined ? stackDataInput.connected : "nothing"}`}</title>
{#if stackDataInput.connected !== undefined}
<title>{`${dataTypeTooltip(stackDataInput)}\n${inputConnectedToText(stackDataInput)}`}</title>
{#if stackDataInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -496,7 +627,7 @@
<div class="details">
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<span title={editor.handle.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined}>
{node.alias}
{node.displayName}
</span>
</div>
<IconButton
@ -514,14 +645,32 @@
<defs>
<clipPath id={clipPathId}>
<!-- Keep this equation in sync with the equivalent one in the CSS rule for `.layer { width: ... }` below -->
<path clip-rule="evenodd" d={layerBorderMask(24 * layerAreaWidth - 12, node.exposedInputs.length === 0 ? 0 : 36)} />
<path clip-rule="evenodd" d={layerBorderMask(24 * layerAreaWidth - 12, layerChainWidth ? (0.5 + layerChainWidth) * 24 : 0)} />
</clipPath>
</defs>
</svg>
</div>
{/each}
<!-- Node connection wires -->
<div class="wires">
<svg>
{#each wirePaths as { pathString, dataType, thick, dashed }}\
{#if !thick}
<path
d={pathString}
style:--data-line-width={`${thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={dashed ? "4" : undefined}
/>
{/if}
{/each}
</svg>
</div>
<!-- Nodes -->
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
{@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]}
{@const clipPathId = String(Math.random()).substring(2)}
<div
@ -542,14 +691,14 @@
<span class="node-error hover" transition:fade={FADE_TRANSITION} data-node-error>{node.errors}</span>
{/if}
<!-- Primary row -->
<div class="primary" class:no-parameter-section={exposedInputsOutputs.length === 0}>
<IconLabel icon={nodeIcon(node.name)} />
<div class="primary" class:in-selected-network={$nodeGraph.inSelectedNetwork} class:no-parameter-section={exposedInputsOutputs.length === 0}>
<IconLabel icon={nodeIcon(node.reference)} />
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
<TextLabel tooltip={editor.handle.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined}>{node.alias || node.name}</TextLabel>
<TextLabel tooltip={editor.handle.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined}>{node.displayName}</TextLabel>
</div>
<!-- Parameter rows -->
{#if exposedInputsOutputs.length > 0}
<div class="parameters">
<div class="parameters" class:in-selected-network={$nodeGraph.inSelectedNetwork}>
{#each exposedInputsOutputs as parameter, index}
<div class={`parameter expanded ${index < node.exposedInputs.length ? "input" : "output"}`}>
<TextLabel tooltip={parameter.name}>{parameter.name}</TextLabel>
@ -568,10 +717,10 @@
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][0]}
bind:this={inputs[nodeIndex + 1][0]}
>
<title>{`${dataTypeTooltip(node.primaryInput)}\nConnected to ${node.primaryInput.connected !== undefined ? node.primaryInput.connected : "nothing"}`}</title>
{#if node.primaryInput.connected !== undefined}
<title>{`${dataTypeTooltip(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`}</title>
{#if node.primaryInput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -588,10 +737,10 @@
data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][index + (node.primaryInput ? 1 : 0)]}
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
>
<title>{`${dataTypeTooltip(parameter)}\nConnected to ${parameter.connected !== undefined ? parameter.connected : "nothing"}`}</title>
{#if parameter.connected !== undefined}
<title>{`${dataTypeTooltip(parameter)}\n${inputConnectedToText(parameter)}`}</title>
{#if parameter.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -611,10 +760,10 @@
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][0]}
bind:this={outputs[nodeIndex + 1][0]}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${connectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connected !== undefined}
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${outputConnectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -630,10 +779,10 @@
data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][outputIndex + (node.primaryOutput ? 1 : 0)]}
bind:this={outputs[nodeIndex + 1][outputIndex + (node.primaryOutput ? 1 : 0)]}
>
<title>{`${dataTypeTooltip(parameter)}\n${connectedToText(parameter)}`}</title>
{#if parameter.connected !== undefined}
<title>{`${dataTypeTooltip(parameter)}\n${outputConnectedToText(parameter)}`}</title>
{#if parameter.connectedTo !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -768,6 +917,42 @@
}
}
.click-targets {
position: absolute;
pointer-events: none;
width: 100%;
height: 100%;
z-index: 10;
svg {
overflow: visible;
width: 100%;
height: 100%;
stroke-width: 1;
fill: none;
.layer {
stroke: yellow;
}
.node {
stroke: blue;
}
.port {
stroke: green;
}
.visibility {
stroke: red;
}
.all-nodes-bounding-box {
stroke: purple;
}
}
}
.wires {
pointer-events: none;
position: absolute;
@ -788,6 +973,40 @@
}
}
.imports-and-exports {
position: absolute;
width: 100%;
height: 100%;
.port {
position: absolute;
width: 8px;
height: 8px;
margin-top: 4px;
margin-left: 5px;
top: calc(var(--offset-top) * 24px);
left: calc(var(--offset-left) * 24px);
}
.export-text {
position: absolute;
margin-top: 0;
margin-left: 20px;
top: calc(var(--offset-top) * 24px);
left: calc(var(--offset-left) * 24px);
}
.import-text {
position: absolute;
text-align: right;
top: calc(var(--offset-top) * 24px);
left: calc(var(--offset-left) * 24px);
margin-top: 0;
margin-left: calc(-100px - 2px);
width: 100px;
}
}
.layers-and-nodes {
position: absolute;
width: 100%;
@ -930,10 +1149,10 @@
border-radius: 8px;
--extra-width-to-reach-grid-multiple: 8px;
--node-chain-area-left-extension: 0;
// Keep this equation in sync with the equivalent one in the Svelte template `<clipPath><path d="layerBorderMask(...)" /></clipPath>` above
width: calc(24px * var(--layer-area-width) - 12px);
// Keep this equation in sync with the equivalent one in the Svelte template `<clipPath><path d="layerBorderMask(...)" /></clipPath>` above, as well as the `left` port offset CSS rule above in `.ports.input` above.
width: calc((var(--layer-area-width) - 0.5) * 24px);
padding-left: calc(var(--node-chain-area-left-extension) * 24px);
margin-left: calc((1.5 - var(--node-chain-area-left-extension)) * 24px);
margin-left: calc((0.5 - var(--node-chain-area-left-extension)) * 24px);
&::after {
border: 1px solid var(--color-5-dullgray);
@ -943,6 +1162,10 @@
&.selected {
// This is the result of blending `rgba(255, 255, 255, 0.1)` over `rgba(0, 0, 0, 0.33)`
background: rgba(66, 66, 66, 0.4);
&.in-selected-network {
background: rgba(80, 80, 80, 0.5);
}
}
.thumbnail {
@ -1004,6 +1227,10 @@
right: -12px;
}
.input.ports {
left: calc(-3px + var(--node-chain-area-left-extension) * 24px - 36px);
}
.visibility,
.input.ports,
.input.ports .port {
@ -1032,10 +1259,18 @@
&.selected {
.primary {
background: rgba(255, 255, 255, 0.15);
&.in-selected-network {
background: rgba(255, 255, 255, 0.2);
}
}
.parameters {
background: rgba(255, 255, 255, 0.1);
&.in-selected-network {
background: rgba(255, 255, 255, 0.15);
}
}
}

View File

@ -10,7 +10,7 @@
Layers,
Properties,
};
type PanelTypes = keyof typeof PANEL_COMPONENTS;
type PanelType = keyof typeof PANEL_COMPONENTS;
</script>
<script lang="ts">
@ -33,7 +33,7 @@
export let tabCloseButtons = false;
export let tabLabels: { name: string; tooltip?: string }[];
export let tabActiveIndex: number;
export let panelType: PanelTypes | undefined = undefined;
export let panelType: PanelType | undefined = undefined;
export let clickAction: ((index: number) => void) | undefined = undefined;
export let closeAction: ((index: number) => void) | undefined = undefined;
@ -56,7 +56,7 @@
}
</script>
<LayoutCol class="panel">
<LayoutCol class="panel" on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)}>
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
<LayoutRow class="tab-group" scrollableX={true}>
{#each tabLabels as tabLabel, tabIndex}

View File

@ -1,15 +1,20 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/wasm-communication/editor";
import type { FrontendGraphOutput, FrontendGraphInput } from "@graphite/wasm-communication/messages";
import {
type Box,
type FrontendClickTargets,
type ContextMenuInformation,
type FrontendNode,
type FrontendNodeWire as FrontendNodeWire,
type FrontendNodeType,
type WirePath,
UpdateBox,
UpdateClickTargets,
UpdateContextMenuInformation,
UpdateInSelectedNetwork,
UpdateImportsExports,
UpdateLayerWidths,
UpdateNodeGraph,
UpdateNodeGraphSelection,
@ -24,9 +29,13 @@ import {
export function createNodeGraphState(editor: Editor) {
const { subscribe, update } = writable({
box: undefined as Box | undefined,
clickTargets: undefined as FrontendClickTargets | undefined,
contextMenuInformation: undefined as ContextMenuInformation | undefined,
layerWidths: new Map<bigint, number>(),
nodes: [] as FrontendNode[],
chainWidths: new Map<bigint, number>(),
imports: [] as { outputMetadata: FrontendGraphOutput; position: { x: number; y: number } }[],
exports: [] as { inputMetadata: FrontendGraphInput; position: { x: number; y: number } }[],
nodes: new Map<bigint, FrontendNode>(),
wires: [] as FrontendNodeWire[],
wirePathInProgress: undefined as WirePath | undefined,
nodeTypes: [] as FrontendNodeType[],
@ -34,6 +43,7 @@ export function createNodeGraphState(editor: Editor) {
thumbnails: new Map<bigint, string>(),
selected: [] as bigint[],
transform: { scale: 1, x: 0, y: 0 },
inSelectedNetwork: true,
});
// Set up message subscriptions on creation
@ -43,22 +53,45 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateClickTargets, (UpdateClickTargets) => {
update((state) => {
state.clickTargets = UpdateClickTargets.clickTargets;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateContextMenuInformation, (updateContextMenuInformation) => {
update((state) => {
state.contextMenuInformation = updateContextMenuInformation.contextMenuInformation;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateImportsExports, (updateImportsExports) => {
update((state) => {
state.imports = updateImportsExports.imports;
state.exports = updateImportsExports.exports;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateInSelectedNetwork, (updateInSelectedNetwork) => {
update((state) => {
state.inSelectedNetwork = updateInSelectedNetwork.inSelectedNetwork;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateLayerWidths, (updateLayerWidths) => {
update((state) => {
state.layerWidths = updateLayerWidths.layerWidths;
state.chainWidths = updateLayerWidths.chainWidths;
return state;
});
});
// TODO: Add a way to only update the nodes that have changed
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
update((state) => {
state.nodes = updateNodeGraph.nodes;
state.nodes.clear();
updateNodeGraph.nodes.forEach((node) => {
state.nodes.set(node.id, node);
});
state.wires = updateNodeGraph.wires;
return state;
});

View File

@ -12,6 +12,29 @@ export class JsMessage {
}
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
const ImportsToVec2Array = Transform(({ obj }) => {
const imports: { outputMetadata: FrontendGraphOutput; position: XY }[] = [];
obj.imports.forEach(([outputMetadata, x, y]: [FrontendGraphOutput, number, number]) => {
outputMetadata.connectedTo = outputMetadata.connectedTo.map((connector: any) => {
if (connector.export !== undefined) return { index: connector.export.index };
return { nodeId: connector.node.nodeId, index: connector.node.inputIndex };
});
imports.push({ outputMetadata, position: { x, y } });
});
return imports;
});
const ExportsToVec2Array = Transform(({ obj }) => {
const exports: { inputMetadata: FrontendGraphInput; position: XY }[] = [];
obj.exports.forEach(([inputMetadata, x, y]: [FrontendGraphInput, number, number]) => {
inputMetadata.connectedTo = ((connectedTo: any) => {
if (connectedTo?.import !== undefined) return { index: connectedTo?.import.index };
return { nodeId: connectedTo?.node.nodeId, index: connectedTo?.node.outputIndex };
})(inputMetadata.connectedTo);
exports.push({ inputMetadata, position: { x, y } });
});
return exports;
});
// const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) }));
export type XY = { x: number; y: number };
@ -29,6 +52,10 @@ export class UpdateBox extends JsMessage {
readonly box!: Box | undefined;
}
export class UpdateClickTargets extends JsMessage {
readonly clickTargets!: FrontendClickTargets | undefined;
}
const ContextTupleToVec2 = Transform((data) => {
if (data.obj.contextMenuInformation === undefined) return undefined;
const contextMenuCoordinates = { x: data.obj.contextMenuInformation.contextMenuCoordinates[0], y: data.obj.contextMenuInformation.contextMenuCoordinates[1] };
@ -43,11 +70,27 @@ export class UpdateContextMenuInformation extends JsMessage {
@ContextTupleToVec2
readonly contextMenuInformation!: ContextMenuInformation | undefined;
}
export class UpdateImportsExports extends JsMessage {
@ImportsToVec2Array
readonly imports!: { outputMetadata: FrontendGraphOutput; position: XY }[];
@ExportsToVec2Array
readonly exports!: { inputMetadata: FrontendGraphInput; position: XY }[];
}
export class UpdateInSelectedNetwork extends JsMessage {
readonly inSelectedNetwork!: boolean;
}
const LayerWidths = Transform(({ obj }) => obj.layerWidths);
const ChainWidths = Transform(({ obj }) => obj.chainWidths);
export class UpdateLayerWidths extends JsMessage {
@LayerWidths
readonly layerWidths!: Map<bigint, number>;
@ChainWidths
readonly chainWidths!: Map<bigint, number>;
}
export class UpdateNodeGraph extends JsMessage {
@ -123,6 +166,14 @@ export class Box {
readonly endY!: number;
}
export type FrontendClickTargets = {
readonly nodeClickTargets: string[];
readonly layerClickTargets: string[];
readonly portClickTargets: string[];
readonly visibilityClickTargets: string[];
readonly allNodesBoundingBox: string;
};
export type ContextMenuInformation = {
contextMenuCoordinates: XY;
@ -131,6 +182,37 @@ export type ContextMenuInformation = {
export type FrontendGraphDataType = "General" | "Raster" | "VectorData" | "Number" | "Graphic" | "Artboard";
export class Node {
readonly nodeId!: bigint;
readonly index!: bigint;
}
export class Export {
readonly index!: bigint;
}
export class Import {
readonly index!: bigint;
}
export type OutputConnector = Node | Import;
export type InputConnector = Node | Export;
const CreateOutputConnectorOptional = Transform(({ obj }) => {
if (obj.connectedTo?.export !== undefined) {
return { index: obj.connectedTo?.export };
} else if (obj.connectedTo?.import !== undefined) {
return { index: obj.connectedTo?.import };
} else {
if (obj.connectedTo?.node.inputIndex !== undefined) {
return { nodeId: obj.connectedTo?.node.nodeId, index: obj.connectedTo?.node.inputIndex };
} else {
return { nodeId: obj.connectedTo?.node.nodeId, index: obj.connectedTo?.node.outputIndex };
}
}
});
export class FrontendGraphInput {
readonly dataType!: FrontendGraphDataType;
@ -138,9 +220,28 @@ export class FrontendGraphInput {
readonly resolvedType!: string | undefined;
readonly connected!: bigint | undefined;
@CreateOutputConnectorOptional
readonly connectedTo!: OutputConnector | undefined;
}
const CreateInputConnectorArray = Transform(({ obj }) => {
const newInputConnectors: InputConnector[] = [];
obj.connectedTo.forEach((connector: any) => {
if (connector.export !== undefined) {
newInputConnectors.push({ index: connector.export });
} else if (connector.import !== undefined) {
newInputConnectors.push({ index: connector.import });
} else {
if (connector.node.inputIndex !== undefined) {
newInputConnectors.push({ nodeId: connector.node.nodeId, index: connector.node.inputIndex });
} else {
newInputConnectors.push({ nodeId: connector.node.nodeId, index: connector.node.outputIndex });
}
}
});
return newInputConnectors;
});
export class FrontendGraphOutput {
readonly dataType!: FrontendGraphDataType;
@ -148,9 +249,8 @@ export class FrontendGraphOutput {
readonly resolvedType!: string | undefined;
readonly connected!: bigint[];
readonly connectedIndex!: bigint[];
@CreateInputConnectorArray
readonly connectedTo!: InputConnector[];
}
export class FrontendNode {
@ -160,22 +260,26 @@ export class FrontendNode {
readonly id!: bigint;
readonly alias!: string;
readonly reference!: string | undefined;
readonly name!: string;
readonly displayName!: string;
@Type(() => FrontendGraphInput)
readonly primaryInput!: FrontendGraphInput | undefined;
@Type(() => FrontendGraphInput)
readonly exposedInputs!: FrontendGraphInput[];
@Type(() => FrontendGraphOutput)
readonly primaryOutput!: FrontendGraphOutput | undefined;
@Type(() => FrontendGraphOutput)
readonly exposedOutputs!: FrontendGraphOutput[];
@TupleToVec2
readonly position!: XY | undefined;
//TODO: Store field for the width of the left node chain
// TODO: Store field for the width of the left node chain
readonly previewed!: boolean;
@ -188,14 +292,40 @@ export class FrontendNode {
readonly uiOnly!: boolean;
}
const CreateOutputConnector = Transform(({ obj }) => {
if (obj.wireStart.export !== undefined) {
return { index: obj.wireStart.export };
} else if (obj.wireStart.import !== undefined) {
return { index: obj.wireStart.import };
} else {
if (obj.wireStart.node.inputIndex !== undefined) {
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.inputIndex };
} else {
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.outputIndex };
}
}
});
const CreateInputConnector = Transform(({ obj }) => {
if (obj.wireEnd.export !== undefined) {
return { index: obj.wireEnd.export };
} else if (obj.wireEnd.import !== undefined) {
return { index: obj.wireEnd.import };
} else {
if (obj.wireEnd.node.inputIndex !== undefined) {
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.inputIndex };
} else {
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.outputIndex };
}
}
});
export class FrontendNodeWire {
readonly wireStart!: bigint;
@CreateOutputConnector
readonly wireStart!: OutputConnector;
readonly wireStartOutputIndex!: bigint;
readonly wireEnd!: bigint;
readonly wireEndInputIndex!: bigint;
@CreateInputConnector
readonly wireEnd!: InputConnector;
readonly dashed!: boolean;
}
@ -747,6 +877,10 @@ export class LayerPanelEntry {
parentsUnlocked!: boolean;
parentId!: bigint | undefined;
selected!: boolean;
inSelectedNetwork!: boolean;
}
export class DisplayDialogDismiss extends JsMessage {}
@ -1351,7 +1485,12 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup {
}
if (layoutGroup.section) {
const result: WidgetSection = { name: layoutGroup.section.name, visible: layoutGroup.section.visible, id: layoutGroup.section.id, layout: layoutGroup.section.layout.map(createLayoutGroup) };
const result: WidgetSection = {
name: layoutGroup.section.name,
visible: layoutGroup.section.visible,
id: layoutGroup.section.id,
layout: layoutGroup.section.layout.map(createLayoutGroup),
};
return result;
}
@ -1448,7 +1587,10 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerVisitLink,
UpdateActiveDocument,
UpdateBox,
UpdateClickTargets,
UpdateContextMenuInformation,
UpdateInSelectedNetwork,
UpdateImportsExports,
UpdateLayerWidths,
UpdateDialogButtons,
UpdateDialogColumn1,

View File

@ -13,6 +13,7 @@ use editor::consts::FILE_SAVE_SUFFIX;
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use editor::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use editor::messages::portfolio::utility_types::Platform;
use editor::messages::prelude::*;
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
@ -525,7 +526,7 @@ impl EditorHandle {
/// Move a layer to within a folder and placed down at the given index.
/// If the folder is `None`, it is inserted into the document root.
/// If the insert index is `None`, it is inserted at the end of the folder (equivalent to index infinity).
/// If the insert index is `None`, it is inserted at the start of the folder.
#[wasm_bindgen(js_name = moveLayerInTree)]
pub fn move_layer_in_tree(&self, insert_parent_id: Option<u64>, insert_index: Option<usize>) {
let insert_parent_id = insert_parent_id.map(NodeId);
@ -533,7 +534,7 @@ impl EditorHandle {
let message = DocumentMessage::MoveSelectedLayersTo {
parent,
insert_index: insert_index.map(|x| x as isize).unwrap_or(-1),
insert_index: insert_index.unwrap_or_default(),
};
self.dispatch(message);
}
@ -542,7 +543,10 @@ impl EditorHandle {
#[wasm_bindgen(js_name = setLayerName)]
pub fn set_layer_name(&self, id: u64, name: String) {
let layer = LayerNodeIdentifier::new_unchecked(NodeId(id));
let message = GraphOperationMessage::SetName { layer, name };
let message = NodeGraphMessage::SetDisplayName {
node_id: layer.to_node(),
alias: name,
};
self.dispatch(message);
}
@ -564,7 +568,12 @@ impl EditorHandle {
#[wasm_bindgen(js_name = createNode)]
pub fn create_node(&self, node_type: String, x: i32, y: i32) {
let id = NodeId(generate_uuid());
let message = NodeGraphMessage::CreateNode { node_id: Some(id), node_type, x, y };
let message = NodeGraphMessage::CreateNodeFromContextMenu {
node_id: Some(id),
node_type,
x: x / 24,
y: y / 24,
};
self.dispatch(message);
}
@ -575,13 +584,6 @@ impl EditorHandle {
self.dispatch(message);
}
/// Go back a certain number of nested levels
#[wasm_bindgen(js_name = exitNestedNetwork)]
pub fn exit_nested_network(&self, steps_back: usize) {
let message = NodeGraphMessage::ExitNestedNetwork { steps_back };
self.dispatch(message);
}
/// Pastes an image
#[wasm_bindgen(js_name = pasteImage)]
pub fn paste_image(&self, image_data: Vec<u8>, width: u32, height: u32, mouse_x: Option<f64>, mouse_y: Option<f64>) {
@ -602,7 +604,7 @@ impl EditorHandle {
#[wasm_bindgen(js_name = toggleNodeVisibilityLayerPanel)]
pub fn toggle_node_visibility_layer(&self, id: u64) {
let node_id = NodeId(id);
let message = GraphOperationMessage::ToggleVisibility { node_id };
let message = NodeGraphMessage::ToggleVisibility { node_id };
self.dispatch(message);
}
@ -619,9 +621,8 @@ impl EditorHandle {
/// Toggle lock state of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerLock)]
pub fn toggle_layer_lock(&self, id: u64) {
let node_id = NodeId(id);
let message = GraphOperationMessage::ToggleLocked { node_id };
pub fn toggle_layer_lock(&self, node_id: u64) {
let message = NodeGraphMessage::ToggleLocked { node_id: NodeId(node_id) };
self.dispatch(message);
}
@ -633,10 +634,19 @@ impl EditorHandle {
self.dispatch(message);
}
/// Set the active panel to the most recently clicked panel
#[wasm_bindgen(js_name = setActivePanel)]
pub fn set_active_panel(&self, panel: String) {
let message = PortfolioMessage::SetActivePanel { panel: panel.into() };
self.dispatch(message);
}
/// Toggle display type for a layer
#[wasm_bindgen(js_name = setToNodeOrLayer)]
pub fn set_to_node_or_layer(&self, id: u64, is_layer: bool) {
let node_id = NodeId(id);
let message = DocumentMessage::StartTransaction;
self.dispatch(message);
let message = NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer };
self.dispatch(message);
}
@ -680,16 +690,44 @@ impl EditorHandle {
});
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
for node in document.network.nodes.values_mut().filter(|d| d.name == "Artboard") {
if let Some(network) = node.implementation.get_network_mut() {
for node in network.nodes.values_mut() {
if node.name == "To Artboard" {
node.implementation = DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>");
if node.inputs.len() != 6 {
node.inputs.insert(2, NodeInput::value(TaggedValue::IVec2(glam::IVec2::default()), false));
}
for node in document
.network_interface
.network_metadata(&[])
.unwrap()
.persistent_metadata
.node_metadata
.iter()
.filter(|(_, d)| d.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Artboard"))
.map(|(id, _)| *id)
.collect::<Vec<_>>()
{
let Some(document_node) = document.network_interface.network(&[]).unwrap().nodes.get(&node) else {
log::error!("Could not get document node in document network");
return;
};
if let Some(network) = document_node.implementation.get_network() {
let mut nodes_to_upgrade = Vec::new();
for (node_id, _) in network.nodes.iter().collect::<Vec<_>>() {
if document.network_interface.reference(node_id, &[]).is_some_and(|reference| reference == "To Artboard")
&& document
.network_interface
.network(&[])
.unwrap()
.nodes
.get(node_id)
.is_some_and(|document_node| document_node.inputs.len() != 6)
{
nodes_to_upgrade.push(*node_id);
}
}
for node_id in nodes_to_upgrade {
document
.network_interface
.set_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>"));
document
.network_interface
.add_input(&node_id, &[], TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string());
}
}
}
@ -722,60 +760,87 @@ impl EditorHandle {
let mut updated_nodes = HashSet::new();
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
document.metadata.load_structure(&document.network);
for node in document.network.nodes.iter().filter(|(_, d)| d.name == "Merge").map(|(id, _)| *id).collect::<Vec<_>>() {
let layer = LayerNodeIdentifier::new(node, &document.network);
if document.metadata.is_folder(layer) {
document.network_interface.load_structure();
for node in document
.network_interface
.network_metadata(&[])
.unwrap()
.persistent_metadata
.node_metadata
.iter()
.filter(|(_, d)| d.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Merge"))
.map(|(id, _)| *id)
.collect::<Vec<_>>()
{
let layer = LayerNodeIdentifier::new(node, &document.network_interface);
if layer.has_children(document.metadata()) {
continue;
}
let bounds = LayerBounds::new(&document.metadata, layer);
let bounds = LayerBounds::new(document.metadata(), layer);
let mut responses = VecDeque::new();
let mut shape = None;
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), &mut document.network, &mut document.metadata, &mut document.node_graph_handler, &mut responses) {
modify_inputs.modify_existing_inputs("Transform", |inputs, node_id, metadata| {
if !updated_nodes.insert(node_id) {
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, &mut document.network_interface, &mut responses) {
let Some(transform_node_id) = modify_inputs.get_existing_node_id("Transform") else { return };
if !updated_nodes.insert(transform_node_id) {
return;
}
let Some(inputs) = modify_inputs.network_interface.network(&[]).unwrap().nodes.get(&transform_node_id).map(|node| &node.inputs) else {
log::error!("Could not get transform node in document network");
return;
};
let transform = get_current_transform(inputs);
let upstream_transform = modify_inputs.network_interface.document_metadata().upstream_transform(transform_node_id);
let pivot_transform = glam::DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(get_current_normalized_pivot(inputs))));
let transform = get_current_transform(inputs);
let upstream_transform = metadata.upstream_transform(node_id);
let pivot_transform = glam::DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(get_current_normalized_pivot(inputs))));
update_transform(inputs, pivot_transform * transform * pivot_transform.inverse());
});
modify_inputs.modify_existing_inputs("Shape", |inputs, node_id, _metadata| {
if !updated_nodes.insert(node_id) {
return;
}
let path_data = match &inputs[0].as_value() {
Some(TaggedValue::Subpaths(translation)) => translation,
_ => &Vec::new(),
};
let colinear_manipulators = match &inputs[1].as_value() {
Some(TaggedValue::PointIds(translation)) => translation,
_ => &Vec::new(),
};
let mut vector_data = VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
.filter_map(|&point| ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect();
shape = Some((node_id, VectorModification::create_from_vector(&vector_data)));
});
update_transform(&mut document.network_interface, &transform_node_id, pivot_transform * transform * pivot_transform.inverse());
}
if let Some((id, modification)) = shape {
let metadata = document.network.nodes.remove(&id).map(|node| node.metadata).unwrap_or_default();
let node_type = resolve_document_node_type("Path").unwrap();
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, &mut document.network_interface, &mut responses) {
let Some(shape_node_id) = modify_inputs.get_existing_node_id("Shape") else { return };
if !updated_nodes.insert(shape_node_id) {
return;
}
let Some(shape_node) = modify_inputs.network_interface.network(&[]).unwrap().nodes.get(&shape_node_id) else {
log::error!("Could not get shape node in document network");
return;
};
let path_data = match &shape_node.inputs[0].as_value() {
Some(TaggedValue::Subpaths(translation)) => translation,
_ => &Vec::new(),
};
let document_node = node_type.to_document_node_default_inputs([None, Some(NodeInput::value(TaggedValue::VectorModification(modification), false))], metadata);
document.network.nodes.insert(id, document_node);
let colinear_manipulators = match &shape_node.inputs[1].as_value() {
Some(TaggedValue::PointIds(translation)) => translation,
_ => &Vec::new(),
};
let mut vector_data = VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
.filter_map(|&point| ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect();
shape = Some((shape_node_id, VectorModification::create_from_vector(&vector_data)));
}
if let Some((node_id, modification)) = shape {
let node_type = resolve_document_node_type("Path").unwrap();
let document_node = node_type
.node_template_input_override([None, Some(NodeInput::value(TaggedValue::VectorModification(modification), false))])
.document_node;
let node_metadata = document.network_interface.node_metadata(&node_id, &[]).cloned().unwrap_or_default();
document.network_interface.insert_node(
node_id,
NodeTemplate {
document_node,
persistent_node_metadata: node_metadata.persistent_metadata,
},
&[],
);
}
}

View File

@ -39,7 +39,6 @@ fn add_network() -> NodeNetwork {
NodeNetwork {
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [DocumentNode {
name: "Blend Image".into(),
inputs: vec![NodeInput::Inline(InlineRust::new(
format!(
r#"graphene_core::raster::adjustments::BlendNode::new(

View File

@ -21,7 +21,6 @@ use vello::*;
/// Represents a clickable target for the layer
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ClickTarget {
subpath: bezier_rs::Subpath<PointId>,
stroke_width: f64,
@ -38,6 +37,23 @@ impl ClickTarget {
&self.subpath
}
pub fn bounding_box(&self) -> Option<[DVec2; 2]> {
self.bounding_box
}
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)])
}
pub fn apply_transform(&mut self, affine_transform: DAffine2) {
self.subpath.apply_transform(affine_transform);
self.update_bbox();
}
fn update_bbox(&mut self) {
self.bounding_box = self.subpath.bounding_box();
}
/// Does the click target intersect the rectangle
pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool {
// Check if the matrix is not invertible
@ -85,6 +101,20 @@ impl ClickTarget {
let inflated_quad = Quad::from_box(target_bounds);
self.intersect_rectangle(inflated_quad, layer_transform)
}
/// Does the click target intersect the point (not accounting for stroke size)
pub fn intersect_point_no_stroke(&self, point: DVec2) -> bool {
// Check if the point is within the bounding box
if self
.bounding_box
.is_some_and(|bbox| bbox[0].x <= point.x && point.x <= bbox[1].x && bbox[0].y <= point.y && point.y <= bbox[1].y)
{
// Check if the point is within the shape
self.subpath.closed() && self.subpath.contains_point(point)
} else {
false
}
}
}
/// Mutable state used whilst rendering to an SVG
@ -284,7 +314,7 @@ impl GraphicElementRendered for GraphicGroup {
let mut new_click_targets = Vec::new();
element.add_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() {
click_target.subpath.apply_transform(element.transform())
click_target.apply_transform(element.transform())
}
click_targets.extend(new_click_targets);
}

View File

@ -186,10 +186,10 @@ impl<T: Hash> MemoHash<T> {
hasher.finish()
}
pub fn inner_mut<'a>(&'a mut self) -> MemoHashGuard<'a, T> {
pub fn inner_mut(&mut self) -> MemoHashGuard<T> {
MemoHashGuard { inner: self }
}
pub fn into_inner<'a>(self) -> T {
pub fn into_inner(self) -> T {
self.value
}
pub fn hash_code(&self) -> u64 {

View File

@ -485,7 +485,7 @@ use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::hash::Hash;
fn serialize_hashmap<K, V, S>(hashmap: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
pub fn serialize_hashmap<K, V, S>(hashmap: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
where
K: Serialize + Eq + Hash,
V: Serialize,
@ -498,7 +498,7 @@ where
seq.end()
}
fn deserialize_hashmap<'de, K, V, D>(deserializer: D) -> Result<HashMap<K, V>, D::Error>
pub fn deserialize_hashmap<'de, K, V, D>(deserializer: D) -> Result<HashMap<K, V>, D::Error>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,

View File

@ -2,12 +2,13 @@ use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use dyn_any::{DynAny, StaticType};
use glam::IVec2;
use graphene_core::memo::MemoHashGuard;
pub use graphene_core::uuid::generate_uuid;
use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type};
use glam::IVec2;
use std::collections::hash_map::DefaultHasher;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
pub mod value;
@ -38,34 +39,12 @@ fn merge_ids(a: NodeId, b: NodeId) -> NodeId {
NodeId(hasher.finish())
}
#[derive(Clone, Debug, PartialEq, Default, specta::Type, Hash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Metadata about the node including its position in the graph UI
pub struct DocumentNodeMetadata {
pub position: IVec2,
}
impl DocumentNodeMetadata {
pub fn position(position: impl Into<IVec2>) -> Self {
Self { position: position.into() }
}
}
/// Utility function for providing a default boolean value to serde.
#[inline(always)]
fn return_true() -> bool {
true
}
// TODO: Eventually remove this (probably starting late 2024)
fn migrate_layer_to_merge<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
let mut s: String = serde::Deserialize::deserialize(deserializer)?;
if s == "Layer" {
s = "Merge".to_string();
}
Ok(s)
}
// TODO: Eventually remove this (probably starting late 2024)
#[derive(Debug, serde::Deserialize)]
#[serde(untagged)]
@ -124,14 +103,6 @@ where
#[derive(Clone, Debug, PartialEq, Hash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DocumentNode {
/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the node definition's name is displayed to the user in italics.
/// Ensure the click target in the encapsulating network is updated when this is modified by using network.update_click_target(node_id).
#[serde(default)]
pub alias: String,
// TODO: Replace this name with a reference to the [`DocumentNodeDefinition`] node definition to use the name from there instead.
/// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties.
#[serde(deserialize_with = "migrate_layer_to_merge")]
pub name: String,
/// The inputs to a node, which are either:
/// - From other nodes within this graph [`NodeInput::Node`],
/// - A constant value [`NodeInput::Value`],
@ -225,24 +196,11 @@ pub struct DocumentNode {
/// Now, the call from `F` directly reaches the `CacheNode` and the `CacheNode` can decide whether to call `G.eval(input_from_f)`
/// in the event of a cache miss or just return the cached data in the event of a cache hit.
pub manual_composition: Option<Type>,
// TODO: Remove once this references its definition instead (see above TODO).
/// Indicates to the UI if a primary output should be drawn for this node.
/// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output.
#[serde(default = "return_true")]
pub has_primary_output: bool,
// A nested document network or a proto-node identifier.
pub implementation: DocumentNodeImplementation,
/// User chosen state for displaying this as a left-to-right node or bottom-to-top layer. Ensure the click target in the encapsulating network is updated when the node changes to a layer by using network.update_click_target(node_id).
#[serde(default)]
pub is_layer: bool,
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.
#[serde(default = "return_true")]
pub visible: bool,
/// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI.
#[serde(default)]
pub locked: bool,
/// Metadata about the node including its position in the graph UI. Ensure the click target in the encapsulating network is updated when the node moves by using network.update_click_target(node_id).
pub metadata: DocumentNodeMetadata,
/// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed.
/// See [`crate::proto::ProtoNetwork::generate_stable_node_ids`] for details.
/// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph.
@ -279,16 +237,10 @@ pub struct OriginalLocation {
impl Default for DocumentNode {
fn default() -> Self {
Self {
alias: Default::default(),
name: Default::default(),
inputs: Default::default(),
manual_composition: Default::default(),
has_primary_output: true,
implementation: Default::default(),
is_layer: false,
visible: true,
locked: Default::default(),
metadata: DocumentNodeMetadata::default(),
skip_deduplication: Default::default(),
original_location: OriginalLocation::default(),
}
@ -351,11 +303,11 @@ impl DocumentNode {
let first = self.inputs.remove(0);
match first {
NodeInput::Value { tagged_value, .. } => {
assert_eq!(self.inputs.len(), 0, "{}, {:?}", self.name, self.inputs);
assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs);
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
}
NodeInput::Node { node_id, output_index, lambda } => {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node. {:#?}", self.name);
assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node");
let node = if lambda { ProtoNodeInput::NodeLambda(node_id) } else { ProtoNodeInput::Node(node_id) };
(node, ConstructionArgs::Nodes(vec![]))
}
@ -390,48 +342,6 @@ impl DocumentNode {
skip_deduplication: self.skip_deduplication,
}
}
/// Converts all node id inputs to a new id based on a HashMap.
///
/// If the node is not in the hashmap then a default input is found based on the compiled network
pub fn map_ids(mut self, default_inputs: Vec<NodeInput>, new_ids: &HashMap<NodeId, NodeId>) -> Self {
for (input_index, input) in self.inputs.iter_mut().enumerate() {
if let &mut NodeInput::Node { node_id: id, output_index, lambda } = input {
if let Some(&new_id) = new_ids.get(&id) {
*input = NodeInput::Node {
node_id: new_id,
output_index,
lambda,
};
} else {
*input = default_inputs[input_index].clone();
}
} else if let &mut NodeInput::Network { .. } = input {
*input = default_inputs[input_index].clone();
}
}
self
}
pub fn is_artboard(&self) -> bool {
// TODO: Use something more robust than checking against a string.
// TODO: Or, more fundamentally separate the concept of a layer from a node.
self.name == "Artboard"
}
// TODO: Is this redundant with `LayerNodeIdentifier::has_children()`? Consider removing this in favor of that.
/// Determines if a document node acting as a layer has any nested children where its secondary input eventually leads to a layer along horizontal flow.
pub fn layer_has_child_layers(&self, network: &NodeNetwork) -> bool {
if !self.is_layer {
return false;
}
self.inputs.iter().skip(1).any(|input| {
input.as_node().map_or(false, |node_id| {
network.upstream_flow_back_from_nodes(vec![node_id], FlowType::HorizontalFlow).any(|(node, _)| node.is_layer)
})
})
}
}
/// Represents the possible inputs to a node.
@ -510,6 +420,16 @@ impl NodeInput {
NodeInput::Scope(_) => false,
}
}
/// Network node inputs in the document network are not displayed, but still exist in the compiled network
pub fn is_exposed_to_frontend(&self, is_document_network: bool) -> bool {
match self {
NodeInput::Node { .. } => true,
NodeInput::Value { exposed, .. } => *exposed,
NodeInput::Network { .. } => !is_document_network,
NodeInput::Inline(_) => false,
NodeInput::Scope(_) => false,
}
}
pub fn ty(&self) -> Type {
match self {
@ -528,6 +448,13 @@ impl NodeInput {
None
}
}
pub fn as_value_mut(&mut self) -> Option<MemoHashGuard<TaggedValue>> {
if let NodeInput::Value { tagged_value, .. } = self {
Some(tagged_value.inner_mut())
} else {
None
}
}
pub fn as_non_exposed_value(&self) -> Option<&TaggedValue> {
if let NodeInput::Value { tagged_value, exposed: false } = self {
Some(tagged_value)
@ -545,6 +472,41 @@ impl NodeInput {
}
}
#[derive(Clone, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or `Extract`.
pub enum OldDocumentNodeImplementation {
/// This describes a (document) node built out of a subgraph of other (document) nodes.
///
/// A nested [`NodeNetwork`] that is flattened by the [`NodeNetwork::flatten`] function.
Network(OldNodeNetwork),
/// This describes a (document) node implemented as a proto node.
///
/// A proto node identifier which can be found in `node_registry.rs`.
#[serde(alias = "Unresolved")] // TODO: Eventually remove this alias (probably starting late 2024)
ProtoNode(ProtoNodeIdentifier),
/// The Extract variant is a tag which tells the compilation process to do something special. It invokes language-level functionality built for use by the ExtractNode to enable metaprogramming.
/// When the ExtractNode is compiled, it gets replaced by a value node containing a representation of the source code for the function/lambda of the document node that's fed into the ExtractNode
/// (but only that one document node, not upstream nodes).
///
/// This is explained in more detail here: <https://www.youtube.com/watch?v=72KJa3jQClo>
///
/// Currently we use it for GPU execution, where a node has to get "extracted" to its source code representation and stored as a value that can be given to the GpuCompiler node at runtime
/// (to become a compute shader). Future use could involve the addition of an InjectNode to convert the source code form back into an executable node, enabling metaprogramming in the node graph.
/// We would use an assortment of nodes that operate on Graphene source code (just data, no different from any other data flowing through the graph) to make graph transformations.
///
/// We use this for dealing with macros in a syntactic way of modifying the node graph from within the graph itself. Just like we often deal with lambdas to represent a whole group of
/// operations/code/logic, this allows us to basically deal with a lambda at a meta/source-code level, because we need to pass the GPU SPIR-V compiler the source code for a lambda,
/// not the executable logic of a lambda.
///
/// This is analogous to how Rust macros operate at the level of source code, not executable code. When we speak of source code, that represents Graphene's source code in the form of a
/// DocumentNode network, not the text form of Rust's source code. (Analogous to the token stream/AST of a Rust macro.)
///
/// `DocumentNode`s with a `DocumentNodeImplementation::Extract` are converted into a `ClonedNode` that returns the `DocumentNode` specified by the single `NodeInput::Node`. The referenced node
/// (specified by the single `NodeInput::Node`) is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input.
Extract,
}
#[derive(Clone, Debug, PartialEq, Hash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Represents the implementation of a node, which can be a nested [`NodeNetwork`], a proto [`ProtoNodeIdentifier`], or `Extract`.
@ -606,23 +568,6 @@ impl DocumentNodeImplementation {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Root Node is the "default" export for a node network. Used by document metadata, displaying UI-only "Export" node, and for restoring the default preview node.
pub struct RootNode {
pub id: NodeId,
pub output_index: usize,
}
#[derive(PartialEq, Debug, Clone, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Previewing {
/// If there is a node to restore the connection to the export for, then it is stored in the option.
/// Otherwise, nothing gets restored and the primary export is disconnected.
Yes { root_node_to_restore: Option<RootNode> },
#[default]
No,
}
// TODO: Eventually remove this (probably starting late 2024)
#[derive(Debug, serde::Deserialize)]
#[serde(untagged)]
@ -660,6 +605,121 @@ where
Ok(inputs)
}
/// An instance of a [`DocumentNodeDefinition`] that has been instantiated in a [`NodeNetwork`].
/// Currently, when an instance is made, it lives all on its own without any lasting connection to the definition.
/// But we will want to change it in the future so it merely references its definition.
#[derive(Clone, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OldDocumentNode {
/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the node definition's name is displayed to the user in italics.
/// Ensure the click target in the encapsulating network is updated when this is modified by using network.update_click_target(node_id).
#[serde(default)]
pub alias: String,
// TODO: Replace this name with a reference to the [`DocumentNodeDefinition`] node definition to use the name from there instead.
/// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties.
#[serde(deserialize_with = "migrate_layer_to_merge")]
pub name: String,
/// The inputs to a node, which are either:
/// - From other nodes within this graph [`NodeInput::Node`],
/// - A constant value [`NodeInput::Value`],
/// - A [`NodeInput::Network`] which specifies that this input is from outside the graph, which is resolved in the graph flattening step in the case of nested networks.
///
/// In the root network, it is resolved when evaluating the borrow tree.
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).
#[serde(deserialize_with = "deserialize_inputs")]
pub inputs: Vec<NodeInput>,
pub manual_composition: Option<Type>,
// TODO: Remove once this references its definition instead (see above TODO).
/// Indicates to the UI if a primary output should be drawn for this node.
/// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output.
#[serde(default = "return_true")]
pub has_primary_output: bool,
// A nested document network or a proto-node identifier.
pub implementation: OldDocumentNodeImplementation,
/// User chosen state for displaying this as a left-to-right node or bottom-to-top layer. Ensure the click target in the encapsulating network is updated when the node changes to a layer by using network.update_click_target(node_id).
#[serde(default)]
pub is_layer: bool,
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.
#[serde(default = "return_true")]
pub visible: bool,
/// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI.
#[serde(default)]
pub locked: bool,
/// Metadata about the node including its position in the graph UI. Ensure the click target in the encapsulating network is updated when the node moves by using network.update_click_target(node_id).
pub metadata: OldDocumentNodeMetadata,
/// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed.
/// See [`crate::proto::ProtoNetwork::generate_stable_node_ids`] for details.
/// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph.
#[serde(default)]
pub skip_deduplication: bool,
/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.
#[serde(skip)]
pub original_location: OriginalLocation,
}
// TODO: Eventually remove this (probably starting late 2024)
#[derive(Clone, Debug, PartialEq, Default, specta::Type, Hash, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Metadata about the node including its position in the graph UI
pub struct OldDocumentNodeMetadata {
pub position: IVec2,
}
// TODO: Eventually remove this (probably starting late 2024)
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// Root Node is the "default" export for a node network. Used by document metadata, displaying UI-only "Export" node, and for restoring the default preview node.
pub struct OldRootNode {
pub id: NodeId,
pub output_index: usize,
}
// TODO: Eventually remove this (probably starting late 2024)
#[derive(PartialEq, Debug, Clone, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum OldPreviewing {
/// If there is a node to restore the connection to the export for, then it is stored in the option.
/// Otherwise, nothing gets restored and the primary export is disconnected.
Yes { root_node_to_restore: Option<OldRootNode> },
#[default]
No,
}
// TODO: Eventually remove this (probably starting late 2024)
#[derive(Clone, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// A network (subgraph) of nodes containing each [`DocumentNode`] and its ID, as well as list mapping each export to its connected node, or a value if disconnected
pub struct OldNodeNetwork {
/// The list of data outputs that are exported from this network to the parent network.
/// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data.
#[serde(alias = "outputs", deserialize_with = "deserialize_exports")] // TODO: Eventually remove this alias (probably starting late 2024)
pub exports: Vec<NodeInput>,
/// The list of all nodes in this network.
//#[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")]
pub nodes: HashMap<NodeId, OldDocumentNode>,
/// Indicates whether the network is currently rendered with a particular node that is previewed, and if so, which connection should be restored when the preview ends.
#[serde(default)]
pub previewing: OldPreviewing,
/// Temporary fields to store metadata for "Import"/"Export" UI-only nodes, eventually will be replaced with lines leading to edges
#[serde(default = "default_import_metadata")]
pub imports_metadata: (NodeId, IVec2),
#[serde(default = "default_export_metadata")]
pub exports_metadata: (NodeId, IVec2),
/// A network may expose nodes as constants which can by used by other nodes using a `NodeInput::Scope(key)`.
#[serde(default)]
//#[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")]
pub scope_injections: HashMap<String, (NodeId, Type)>,
}
// TODO: Eventually remove this (probably starting late 2024)
fn migrate_layer_to_merge<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<String, D::Error> {
let mut s: String = serde::Deserialize::deserialize(deserializer)?;
if s == "Layer" {
s = "Merge".to_string();
}
Ok(s)
}
// TODO: Eventually remove this (probably starting late 2024)
fn default_import_metadata() -> (NodeId, IVec2) {
(NodeId(generate_uuid()), IVec2::new(-25, -4))
@ -669,7 +729,7 @@ fn default_export_metadata() -> (NodeId, IVec2) {
(NodeId(generate_uuid()), IVec2::new(8, -4))
}
#[derive(Clone, Debug, DynAny)]
#[derive(Clone, Default, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
/// A network (subgraph) of nodes containing each [`DocumentNode`] and its ID, as well as list mapping each export to its connected node, or a value if disconnected
pub struct NodeNetwork {
@ -677,19 +737,14 @@ pub struct NodeNetwork {
/// Each export is a reference to a node within this network, paired with its output index, that is the source of the network's exported data.
#[serde(alias = "outputs", deserialize_with = "deserialize_exports")] // TODO: Eventually remove this alias (probably starting late 2024)
pub exports: Vec<NodeInput>,
/// TODO: Instead of storing import types in each NodeInput::Network connection, the types are stored here. This is similar to how types need to be defined for parameters when creating a function in Rust.
// pub import_types: Vec<Type>,
/// The list of all nodes in this network.
#[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")]
pub nodes: HashMap<NodeId, DocumentNode>,
/// Indicates whether the network is currently rendered with a particular node that is previewed, and if so, which connection should be restored when the preview ends.
#[serde(default)]
pub previewing: Previewing,
/// Temporary fields to store metadata for "Import"/"Export" UI-only nodes, eventually will be replaced with lines leading to edges
#[serde(default = "default_import_metadata")]
pub imports_metadata: (NodeId, IVec2),
#[serde(default = "default_export_metadata")]
pub exports_metadata: (NodeId, IVec2),
/// A network may expose nodes as constants which can by used by other nodes using a `NodeInput::Scope(key)`.
#[serde(default)]
#[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")]
pub scope_injections: HashMap<String, (NodeId, Type)>,
}
@ -702,24 +757,12 @@ impl std::hash::Hash for NodeNetwork {
id.hash(state);
node.hash(state);
}
self.previewing.hash(state);
}
}
impl Default for NodeNetwork {
fn default() -> Self {
NodeNetwork {
exports: Default::default(),
nodes: Default::default(),
previewing: Default::default(),
imports_metadata: default_import_metadata(),
exports_metadata: default_export_metadata(),
scope_injections: Default::default(),
}
}
}
impl PartialEq for NodeNetwork {
fn eq(&self, other: &Self) -> bool {
self.exports == other.exports && self.previewing == other.previewing && self.imports_metadata == other.imports_metadata && self.exports_metadata == other.exports_metadata
self.exports == other.exports
}
}
@ -731,56 +774,6 @@ impl NodeNetwork {
hasher.finish()
}
/// Returns the root node (the node that the solid line is connect to), or None if no nodes are connected to the output
pub fn get_root_node(&self) -> Option<RootNode> {
match self.previewing {
Previewing::Yes { root_node_to_restore } => root_node_to_restore,
Previewing::No => self.exports.first().and_then(|export| {
if let NodeInput::Node { node_id, output_index, .. } = export {
Some(RootNode {
id: *node_id,
output_index: *output_index,
})
} else {
None
}
}),
}
}
/// Sets the root node only if a node is being previewed
pub fn update_root_node(&mut self, node_id: NodeId, output_index: usize) {
if let Previewing::Yes { root_node_to_restore } = self.previewing {
// Only continue previewing if the new root node is not the same as the primary export. If it is the same, end the preview
if let Some(root_node_to_restore) = root_node_to_restore {
if root_node_to_restore.id != node_id {
self.start_previewing(node_id, output_index);
} else {
self.stop_preview();
}
} else {
self.stop_preview();
}
}
}
/// Start previewing with a restore node
pub fn start_previewing(&mut self, previous_node_id: NodeId, output_index: usize) {
self.previewing = Previewing::Yes {
root_node_to_restore: Some(RootNode { id: previous_node_id, output_index }),
};
}
/// Start previewing without a restore node
pub fn start_previewing_without_restore(&mut self) {
self.previewing = Previewing::Yes { root_node_to_restore: None };
}
/// Stops preview, does not reset export
pub fn stop_preview(&mut self) {
self.previewing = Previewing::No;
}
pub fn value_network(node: DocumentNode) -> Self {
Self {
exports: vec![NodeInput::node(NodeId(0), 0)],
@ -790,32 +783,32 @@ impl NodeNetwork {
}
/// A graph with just an input node
pub fn new_network() -> Self {
Self {
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [(
NodeId(0),
DocumentNode {
name: "Input Frame".into(),
manual_composition: Some(concrete!(u32)),
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
metadata: DocumentNodeMetadata { position: (8, 4).into() },
..Default::default()
},
)]
.into_iter()
.collect(),
..Default::default()
}
}
// pub fn new_network() -> Self {
// Self {
// exports: vec![NodeInput::node(NodeId(0), 0)],
// nodes: [(
// NodeId(0),
// DocumentNode {
// name: "Input Frame".into(),
// manual_composition: Some(concrete!(u32)),
// implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
// metadata: DocumentNodeMetadata { position: (8, 4).into() },
// ..Default::default()
// },
// )]
// .into_iter()
// .collect(),
// ..Default::default()
// }
// }
/// Appends a new node to the network after the output node and sets it as the new output
// pub fn push_node_to_document_network(&mut self, mut node: DocumentNode) -> NodeId {
// let id = NodeId(self.nodes.len().try_into().expect("Too many nodes in network"));
// // Set the correct position for the new node
// if node.metadata.position == IVec2::default() {
// if let Some(pos) = self.get_root_node().and_then(|root_node| self.nodes.get(&root_node.id)).map(|n| n.metadata.position) {
// node.metadata.position = pos + IVec2::new(8, 0);
// if node.metadata().position == IVec2::default() {
// if let Some(pos) = self.get_root_node().and_then(|root_node| self.nodes.get(&root_node.id)).map(|n| n.metadata().position) {
// node.metadata().position = pos + IVec2::new(8, 0);
// }
// }
// if !self.exports.is_empty() {
@ -852,71 +845,6 @@ impl NodeNetwork {
network
}
/// Get the network the selected nodes are part of, which is either self or the nested network from nested_path. Used to get nodes selected in the layer panel when viewing a nested network.
pub fn nested_network_for_selected_nodes<'a>(&self, nested_path: &[NodeId], mut selected_nodes: impl Iterator<Item = &'a NodeId>) -> Option<&Self> {
if selected_nodes.any(|node_id| self.nodes.contains_key(node_id) || self.exports_metadata.0 == *node_id || self.imports_metadata.0 == *node_id) {
Some(self)
} else {
self.nested_network(nested_path)
}
}
/// Get the mutable network the selected nodes are part of, which is either self or the nested network from nested_path. Used to modify nodes selected in the layer panel when viewing a nested network.
pub fn nested_network_for_selected_nodes_mut<'a>(&mut self, nested_path: &[NodeId], mut selected_nodes: impl Iterator<Item = &'a NodeId>) -> Option<&mut Self> {
if selected_nodes.any(|node_id| self.nodes.contains_key(node_id)) {
Some(self)
} else {
self.nested_network_mut(nested_path)
}
}
/// Check if the specified node id is connected to the output
pub fn connected_to_output(&self, target_node_id: NodeId) -> bool {
// If the node is the output then return true
if self
.exports
.iter()
.any(|export| if let NodeInput::Node { node_id, .. } = export { *node_id == target_node_id } else { false })
{
return true;
}
if self.exports_metadata.0 == target_node_id {
return true;
}
// Get the outputs
let mut stack = self
.exports
.iter()
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { self.nodes.get(node_id) } else { None })
.collect::<Vec<_>>();
let mut already_visited = HashSet::new();
already_visited.extend(self.exports.iter().filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }));
while let Some(node) = stack.pop() {
for input in &node.inputs {
if let &NodeInput::Node { node_id: ref_id, .. } = input {
// Skip if already viewed
if already_visited.contains(&ref_id) {
continue;
}
// If the target node is used as input then return true
if ref_id == target_node_id {
return true;
}
// Add the referenced node to the stack
let Some(ref_node) = self.nodes.get(&ref_id) else {
continue;
};
already_visited.insert(ref_id);
stack.push(ref_node);
}
}
}
false
}
/// Is the node being used directly as an output?
pub fn outputs_contain(&self, node_id_to_check: NodeId) -> bool {
self.exports
@ -924,20 +852,6 @@ impl NodeNetwork {
.any(|output| if let NodeInput::Node { node_id, .. } = output { *node_id == node_id_to_check } else { false })
}
/// Gives an iterator to all nodes connected to the given nodes (inclusive) by all inputs (primary or primary + secondary depending on `only_follow_primary` choice), traversing backwards upstream starting from the given node's inputs.
pub fn upstream_flow_back_from_nodes(&self, node_ids: Vec<NodeId>, flow_type: FlowType) -> impl Iterator<Item = (&DocumentNode, NodeId)> {
FlowIter {
stack: node_ids,
network: self,
flow_type,
}
}
/// In the network `X -> Y -> Z`, `is_node_upstream_of_another_by_primary_flow(Z, X)` returns true.
pub fn is_node_upstream_of_another_by_horizontal_flow(&self, node: NodeId, potentially_upstream_node: NodeId) -> bool {
self.upstream_flow_back_from_nodes(vec![node], FlowType::HorizontalFlow).any(|(_, id)| id == potentially_upstream_node)
}
/// Check there are no cycles in the graph (this should never happen).
pub fn is_acyclic(&self) -> bool {
let mut dependencies: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
@ -964,54 +878,6 @@ impl NodeNetwork {
}
}
#[derive(PartialEq)]
pub enum FlowType {
/// Iterate over all upstream nodes from every input (the primary and all secondary).
UpstreamFlow,
/// Iterate over nodes connected to the primary input.
PrimaryFlow,
/// Iterate over the secondary input for layer nodes and primary input for non layer nodes.
HorizontalFlow,
}
/// Iterate over upstream nodes. The behavior changes based on the `flow_type` that's set.
/// - [`FlowType::UpstreamFlow`]: iterates over all upstream nodes from every input (the primary and all secondary).
/// - [`FlowType::PrimaryFlow`]: iterates along the horizontal inputs of nodes, so in the case of a node chain `a -> b -> c`, this would yield `c, b, a` if we started from `c`.
/// - [`FlowType::HorizontalFlow`]: iterates over the secondary input for layer nodes and primary input for non layer nodes.
struct FlowIter<'a> {
stack: Vec<NodeId>,
network: &'a NodeNetwork,
flow_type: FlowType,
}
impl<'a> Iterator for FlowIter<'a> {
type Item = (&'a DocumentNode, NodeId);
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut node_id = self.stack.pop()?;
// Special handling for iterating from ROOT_PARENT in load_structure`
if node_id == NodeId(u64::MAX) {
if let Some(root_node) = self.network.get_root_node() {
node_id = root_node.id
} else {
return None;
}
}
if let Some(document_node) = self.network.nodes.get(&node_id) {
let skip = if self.flow_type == FlowType::HorizontalFlow && document_node.is_layer { 1 } else { 0 };
let take = if self.flow_type == FlowType::UpstreamFlow { usize::MAX } else { 1 };
let inputs = document_node.inputs.iter().skip(skip).take(take);
let node_ids = inputs.filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None });
self.stack.extend(node_ids);
return Some((document_node, node_id));
}
}
}
}
/// Functions for compiling the network
impl NodeNetwork {
/// Replace all references in the graph of a node ID with a new node ID defined by the function `f`.
@ -1021,11 +887,6 @@ impl NodeNetwork {
*node_id = f(*node_id)
}
});
if let Previewing::Yes { root_node_to_restore } = &mut self.previewing {
if let Some(root_node_to_restore) = root_node_to_restore.as_mut() {
root_node_to_restore.id = f(root_node_to_restore.id);
}
}
self.scope_injections.values_mut().for_each(|(id, _ty)| *id = f(*id));
let nodes = std::mem::take(&mut self.nodes);
self.nodes = nodes
@ -1037,20 +898,6 @@ impl NodeNetwork {
.collect();
}
/// Collect a hashmap of nodes with a list of the nodes that use it as input
pub fn collect_outwards_wires(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut outwards_wires: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (current_node_id, node) in &self.nodes {
for input in &node.inputs {
if let NodeInput::Node { node_id, .. } = input {
let outward_wires_entry = outwards_wires.entry(*node_id).or_default();
outward_wires_entry.push(*current_node_id);
}
}
}
outwards_wires
}
/// Populate the [`DocumentNode::path`], which stores the location of the document node to allow for matching the resulting proto nodes to the document node for the purposes of typing and finding monitor nodes.
pub fn generate_node_paths(&mut self, prefix: &[NodeId]) {
for (node_id, node) in &mut self.nodes {
@ -1189,7 +1036,6 @@ impl NodeNetwork {
nested_network.nodes.insert(
merged_node_id,
DocumentNode {
name: "Value".into(),
inputs: vec![NodeInput::Value { tagged_value, exposed }],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()),
original_location,
@ -1220,7 +1066,6 @@ impl NodeNetwork {
self.nodes.insert(
merged_node_id,
DocumentNode {
name: "Value".into(),
inputs: vec![NodeInput::Value { tagged_value, exposed }],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()),
original_location,
@ -1256,13 +1101,6 @@ impl NodeNetwork {
// Match the document node input and the inputs of the inner network
for (nested_node_id, mut nested_node) in inner_network.nodes.into_iter() {
if nested_node.name == "To Artboard" {
let label_index = 1;
let label = if !node.alias.is_empty() { node.alias.clone() } else { node.name.clone() };
let label_input = NodeInput::value(TaggedValue::String(label), false);
nested_node.inputs[label_index] = label_input;
}
for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() {
if let NodeInput::Network { import_index, .. } = nested_input {
let parent_input = node.inputs.get(*import_index).unwrap_or_else(|| panic!("Import index {} should always exist", import_index));
@ -1529,7 +1367,6 @@ mod test {
(
NodeId(0),
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
..Default::default()
@ -1538,7 +1375,6 @@ mod test {
(
NodeId(1),
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()),
..Default::default()
@ -1555,13 +1391,12 @@ mod test {
fn map_ids() {
let mut network = add_network();
network.map_ids(|id| NodeId(id.0 + 1));
let mut mapped_add = NodeNetwork {
let mapped_add = NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
(
NodeId(1),
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
..Default::default()
@ -1570,7 +1405,6 @@ mod test {
(
NodeId(2),
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()),
..Default::default()
@ -1581,17 +1415,12 @@ mod test {
.collect(),
..Default::default()
};
network.exports_metadata.0 = NodeId(0);
network.imports_metadata.0 = NodeId(0);
mapped_add.exports_metadata.0 = NodeId(0);
mapped_add.imports_metadata.0 = NodeId(0);
assert_eq!(network, mapped_add);
}
#[test]
fn extract_node() {
let id_node = DocumentNode {
name: "Id".into(),
inputs: vec![],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
@ -1602,7 +1431,6 @@ mod test {
nodes: [
id_node.clone(),
DocumentNode {
name: "Extract".into(),
inputs: vec![NodeInput::lambda(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Extract,
..Default::default()
@ -1628,7 +1456,6 @@ mod test {
nodes: [(
NodeId(1),
DocumentNode {
name: "Inc".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(TaggedValue::U32(2), false)],
implementation: DocumentNodeImplementation::Network(add_network()),
..Default::default()
@ -1640,11 +1467,7 @@ mod test {
};
network.generate_node_paths(&[]);
network.flatten_with_fns(NodeId(1), |self_id, inner_id| NodeId(self_id.0 * 10 + inner_id.0), gen_node_id);
network.exports_metadata.0 = NodeId(0);
network.imports_metadata.0 = NodeId(0);
let mut flat_network = flat_network();
flat_network.imports_metadata.0 = NodeId(0);
flat_network.exports_metadata.0 = NodeId(0);
let flat_network = flat_network();
println!("{flat_network:#?}");
println!("{network:#?}");
@ -1654,7 +1477,6 @@ mod test {
#[test]
fn resolve_proto_node_add() {
let document_node = DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
..Default::default()
@ -1744,7 +1566,6 @@ mod test {
(
NodeId(10),
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::node(NodeId(14), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::structural::ConsNode".into()),
original_location: OriginalLocation {
@ -1760,7 +1581,6 @@ mod test {
(
NodeId(14),
DocumentNode {
name: "Value".into(),
inputs: vec![NodeInput::value(TaggedValue::U32(2), false)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::ClonedNode".into()),
original_location: OriginalLocation {
@ -1776,7 +1596,6 @@ mod test {
(
NodeId(11),
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(10), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::AddPairNode".into()),
original_location: OriginalLocation {
@ -1803,7 +1622,6 @@ mod test {
(
NodeId(1),
DocumentNode {
name: "Identity 1".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
@ -1812,7 +1630,6 @@ mod test {
(
NodeId(2),
DocumentNode {
name: "Identity 2".into(),
inputs: vec![NodeInput::network(concrete!(u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
@ -1832,7 +1649,6 @@ mod test {
(
NodeId(1),
DocumentNode {
name: "Nested network".into(),
inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)],
implementation: DocumentNodeImplementation::Network(two_node_identity()),
..Default::default()
@ -1841,7 +1657,6 @@ mod test {
(
NodeId(2),
DocumentNode {
name: "Result".into(),
inputs: vec![result_node_input],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()

View File

@ -92,35 +92,29 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
network.generate_node_paths(&[]);
let inner_network = DocumentNode {
name: "Scope".to_string(),
implementation: DocumentNodeImplementation::Network(network),
inputs: vec![NodeInput::node(NodeId(0), 1)],
metadata: DocumentNodeMetadata::position((-10, 0)),
..Default::default()
};
let render_node = graph_craft::document::DocumentNode {
name: "Output".into(),
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 1)],
implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
DocumentNode {
name: "RenderNode".to_string(),
inputs: vec![
NodeInput::network(concrete!(WasmEditorApi), 1),
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0),
@ -136,7 +130,6 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
.collect(),
..Default::default()
}),
metadata: DocumentNodeMetadata::position((-3, 0)),
..Default::default()
};
@ -145,7 +138,6 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
inner_network,
render_node,
DocumentNode {
name: "Editor Api".into(),
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],
..Default::default()
@ -156,7 +148,6 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(),
scope_injections: [("editor-api".to_string(), (NodeId(2), concrete!(&WasmEditorApi)))].into_iter().collect(),
..Default::default()
}
}

View File

@ -97,10 +97,10 @@ async fn map_gpu<'a: 'input>(image: ImageFrame<Color>, node: DocumentNode, edito
};
// TODO: The cache should be based on the network topology not the node name
let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key(&node.name) {
self.cache.lock().as_ref().unwrap().get(&node.name).unwrap().clone()
let compute_pass_descriptor = if self.cache.lock().as_ref().unwrap().contains_key("placeholder") {
self.cache.lock().as_ref().unwrap().get("placeholder").unwrap().clone()
} else {
let name = node.name.to_string();
let name = "placeholder".to_string();
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, &image, executor, quantization).await else {
log::error!("Error creating compute pass descriptor in 'map_gpu()");
return ImageFrame::empty();
@ -177,19 +177,16 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: [
DocumentNode {
name: "Slice".into(),
inputs: vec![NodeInput::Inline(InlineRust::new("i1[(_global_index.y * i0 + _global_index.x) as usize]".into(), concrete![Color]))],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
..Default::default()
},
DocumentNode {
name: "Quantization".into(),
inputs: vec![NodeInput::network(concrete!(quantization::Quantization), 1)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
},
DocumentNode {
name: "Width".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
@ -210,13 +207,11 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
},*/
#[cfg(feature = "quantization")]
DocumentNode {
name: "Dequantize".into(),
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::quantization::DeQuantizeNode"),
..Default::default()
},
DocumentNode {
name: "MapNode".into(),
#[cfg(feature = "quantization")]
inputs: vec![NodeInput::node(NodeId(3), 0)],
#[cfg(not(feature = "quantization"))]
@ -226,7 +221,6 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
},
#[cfg(feature = "quantization")]
DocumentNode {
name: "Quantize".into(),
inputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::quantization::QuantizeNode"),
..Default::default()
@ -436,7 +430,6 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
let network = NodeNetwork {
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [DocumentNode {
name: "BlendOp".into(),
inputs: vec![NodeInput::Inline(InlineRust::new(
format!(
r#"graphene_core::raster::adjustments::BlendNode::new(

View File

@ -20,7 +20,6 @@ mod tests {
(
NodeId(0),
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(&u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::structural::ConsNode<_, _>")),
..Default::default()
@ -29,7 +28,6 @@ mod tests {
(
NodeId(1),
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::AddPairNode")),
..Default::default()
@ -47,7 +45,6 @@ mod tests {
nodes: [(
NodeId(0),
DocumentNode {
name: "Inc".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::value(graph_craft::document::value::TaggedValue::U32(1u32), false)],
implementation: DocumentNodeImplementation::Network(add_network()),
..Default::default()
@ -82,7 +79,6 @@ mod tests {
(
NodeId(0),
DocumentNode {
name: "id".into(),
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
@ -92,7 +88,6 @@ mod tests {
(
NodeId(1),
DocumentNode {
name: "Add".into(),
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::AddNode<_>")),
..Default::default()