Polish user-created subgraph nodes: imports in the Properties panel; reorder/delete/rename imports/exports (#2105)

* Remove imports/exports

* WIP: Autogenerated properties

* WIP: Input based properties

* WIP: Hashmap based input overrides

* Migrate noise pattern node to input properties

* Reorder exports

* Continue migrating properties

* WIP: Improve reorder exports

* Automatically populate all input properties for sub networks

* Complete reorder import and export

* Add widget override to node macro

* Migrate assign colors to input based properties

* WIP: Full node property override

* Node based properties override for proto nodes

* Migrate all node properties to be input based

* Rename imports/exports

* improve UI

* Protonode input valid implementations

* Valid type list

* Small formatting fixes

* Polishing small issues

* Document upgrade

* fix tests

* Upgrade noise pattern node

* remove console log

* Fix upgrade script for Noise Pattern

* Improve the Properties panel representation for graphical data

* Re-export demo art

* Code review

* code review improvements

* Cleanup for node properties overrides

* Reexport demo art

* Fix clippy lints

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Adam Gerhant
2025-01-20 21:13:14 -08:00
committed by GitHub
parent ad68b1e5c8
commit eec0ef761c
38 changed files with 3660 additions and 2006 deletions

View File

@@ -146,6 +146,14 @@ pub enum FrontendMessage {
UpdateGraphViewOverlay {
open: bool,
},
UpdateImportReorderIndex {
#[serde(rename = "importIndex")]
index: Option<usize>,
},
UpdateExportReorderIndex {
#[serde(rename = "exportIndex")]
index: Option<usize>,
},
UpdateLayerWidths {
#[serde(rename = "layerWidths")]
layer_widths: HashMap<NodeId, u32>,

View File

@@ -194,7 +194,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
DocumentMessage::PropertiesPanel(message) => {
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
network_interface: &self.network_interface,
network_interface: &mut self.network_interface,
selection_network_path: &self.selection_network_path,
document_name: self.name.as_str(),
executor,
@@ -391,6 +391,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(DocumentMessage::ZoomCanvasToFitAll);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
DocumentMessage::Escape => {
if self.node_graph_handler.drag_start.is_some() {
@@ -1007,7 +1008,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::UpdateLayerPanel);
responses.add(NodeGraphMessage::UpdateInSelectedNetwork)
responses.add(NodeGraphMessage::UpdateInSelectedNetwork);
}
DocumentMessage::SetBlendModeForSelectedLayers { blend_mode } => {
for layer in self.network_interface.selected_nodes(&[]).unwrap().selected_layers_except_artboards(&self.network_interface) {
@@ -1018,6 +1019,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.graph_fade_artwork_percentage = percentage;
responses.add(FrontendMessage::UpdateGraphFadeArtwork { percentage });
}
DocumentMessage::SetNodePinned { node_id, pinned } => {
responses.add(DocumentMessage::StartTransaction);
responses.add(NodeGraphMessage::SetPinned { node_id, pinned });
@@ -1224,21 +1226,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
.navigation_handler
.calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz);
self.network_interface.set_transform(transform, &self.breadcrumb_network_path);
let imports = self.network_interface.frontend_imports(&self.breadcrumb_network_path).unwrap_or_default();
let exports = self.network_interface.frontend_exports(&self.breadcrumb_network_path).unwrap_or_default();
let add_import = self.network_interface.frontend_import_modify(&self.breadcrumb_network_path);
let add_export = self.network_interface.frontend_export_modify(&self.breadcrumb_network_path);
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
responses.add(NodeGraphMessage::UpdateEdges);
responses.add(NodeGraphMessage::UpdateBoxSelection);
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
add_import,
add_export,
});
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(FrontendMessage::UpdateNodeGraphTransform {
transform: Transform {
scale: transform.matrix2.x_axis.x,
@@ -1465,26 +1459,22 @@ impl DocumentMessageHandler {
}
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
let document_message_handler = serde_json::from_str::<OldDocumentMessageHandler>(serialized_content)
.map_or_else(
|_| serde_json::from_str::<DocumentMessageHandler>(serialized_content),
|old_message_handler| {
let default_document_message_handler = DocumentMessageHandler {
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
collapsed: old_message_handler.collapsed,
commit_hash: old_message_handler.commit_hash,
document_ptz: old_message_handler.document_ptz,
document_mode: old_message_handler.document_mode,
view_mode: old_message_handler.view_mode,
overlays_visible: old_message_handler.overlays_visible,
rulers_visible: old_message_handler.rulers_visible,
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
snapping_state: old_message_handler.snapping_state,
..Default::default()
};
Ok(default_document_message_handler)
},
)
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
.or_else(|_| {
serde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
collapsed: old_message_handler.collapsed,
commit_hash: old_message_handler.commit_hash,
document_ptz: old_message_handler.document_ptz,
document_mode: old_message_handler.document_mode,
view_mode: old_message_handler.view_mode,
overlays_visible: old_message_handler.overlays_visible,
rulers_visible: old_message_handler.rulers_visible,
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
snapping_state: old_message_handler.snapping_state,
..Default::default()
})
})
.map_err(|e| EditorError::DocumentDeserialization(e.to_string()))?;
Ok(document_message_handler)
}
@@ -2089,7 +2079,7 @@ impl DocumentMessageHandler {
/// Create a network interface with a single export
fn default_document_network_interface() -> NodeNetworkInterface {
let mut network_interface = NodeNetworkInterface::default();
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "".to_string(), &[]);
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "", &[]);
network_interface
}

View File

@@ -215,7 +215,7 @@ impl<'a> ModifyInputsContext<'a> {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_document_node_type("Image")
.expect("Image node does not exist")
.node_template_input_override([Some(NodeInput::value(TaggedValue::ImageFrame(image_frame), false))]);
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::ImageFrame(image_frame), false))]);
let image_id = NodeId::new();
self.network_interface.insert_node(image_id, image, &[]);
@@ -256,7 +256,11 @@ impl<'a> ModifyInputsContext<'a> {
let mut existing_node_id = None;
for upstream_node in upstream.collect::<Vec<_>>() {
// Check if this is the node we have been searching for.
if self.network_interface.reference(&upstream_node, &[]).is_some_and(|node_reference| node_reference == reference) {
if self
.network_interface
.reference(&upstream_node, &[])
.is_some_and(|node_reference| *node_reference == Some(reference.to_string()))
{
existing_node_id = Some(upstream_node);
break;
}

View File

@@ -1,7 +1,7 @@
use super::utility_types::Direction;
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::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector};
use crate::messages::prelude::*;
use glam::IVec2;
@@ -58,8 +58,7 @@ pub enum NodeGraphMessage {
EnterNestedNetwork,
DuplicateSelectedNodes,
ExposeInput {
node_id: NodeId,
input_index: usize,
input_connector: InputConnector,
new_exposed: bool,
},
InsertNode {
@@ -98,6 +97,20 @@ pub enum NodeGraphMessage {
shift: Key,
},
PrintSelectedNodeCoordinates,
RemoveImport {
import_index: usize,
},
RemoveExport {
export_index: usize,
},
ReorderImport {
start_index: usize,
end_index: usize,
},
ReorderExport {
start_index: usize,
end_index: usize,
},
RunDocumentGraph,
ForceRunDocumentGraph,
SelectedNodesAdd {
@@ -153,6 +166,14 @@ pub enum NodeGraphMessage {
TogglePreviewImpl {
node_id: NodeId,
},
SetImportExportName {
name: String,
index: ImportOrExport,
},
SetImportExportNameImpl {
name: String,
index: ImportOrExport,
},
ToggleSelectedAsLayersOrNodes,
ToggleSelectedLocked,
ToggleLocked {
@@ -180,6 +201,7 @@ pub enum NodeGraphMessage {
},
UpdateEdges,
UpdateBoxSelection,
UpdateImportsExports,
UpdateLayerPanel,
UpdateNewNodeGraph,
UpdateTypes {

View File

@@ -71,6 +71,12 @@ pub struct NodeGraphMessageHandler {
auto_panning: AutoPanning,
/// The node to preview on mouse up if alt-clicked
preview_on_mouse_up: Option<NodeId>,
// The index of the import that is being moved
reordering_import: Option<usize>,
// The index of the export that is being moved
reordering_export: Option<usize>,
// The end index of the moved port
end_index: Option<usize>,
}
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
@@ -99,8 +105,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
}
NodeGraphMessage::AddImport => network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, String::new(), breadcrumb_network_path),
NodeGraphMessage::AddExport => network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, String::new(), breadcrumb_network_path),
NodeGraphMessage::AddImport => {
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", breadcrumb_network_path);
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::AddExport => {
network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, "", breadcrumb_network_path);
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::Init => {
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
@@ -298,35 +310,29 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
{
return;
};
let Some(network) = network_interface.network(selection_network_path) else {
log::error!("Could not get network in EnterNestedNetwork");
return;
};
let Some(node) = network.nodes.get(&node_id) else { return };
if let DocumentNodeImplementation::Network(_) = node.implementation {
if let Some(DocumentNodeImplementation::Network(_)) = network_interface.implementation(&node_id, selection_network_path) {
responses.add(DocumentMessage::EnterNestedNetwork { node_id });
}
}
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
let Some(network) = network_interface.network(selection_network_path) else {
NodeGraphMessage::ExposeInput { input_connector, new_exposed } => {
let InputConnector::Node { node_id, input_index } = input_connector else {
log::error!("Cannot expose/hide export");
return;
};
let Some(node) = network.nodes.get(&node_id) else {
let Some(node) = network_interface.document_node(&node_id, selection_network_path) else {
log::error!("Could not find node {node_id} in NodeGraphMessage::ExposeInput");
return;
};
let Some(mut input) = node.inputs.get(input_index).cloned() else {
log::error!("Could not find input {input_index} in NodeGraphMessage::ExposeInput");
return;
};
if let NodeInput::Value { exposed, .. } = &mut input {
*exposed = new_exposed;
} else {
// TODO: Should network and node inputs be able to be hidden?
log::error!("Could not hide/show input: {:?} since it is not NodeInput::Value", input);
} else if !new_exposed {
// If hiding an input that is not a value, then disconnect it. This will convert it to a value input.
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
responses.add(NodeGraphMessage::ExposeInput { input_connector, new_exposed });
return;
}
@@ -336,6 +342,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
input_connector: InputConnector::node(node_id, input_index),
input,
});
responses.add(PropertiesPanelMessage::Refresh);
responses.add(NodeGraphMessage::SendGraph);
}
@@ -559,23 +566,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
log::error!("Could not get modify import export in PointerDown");
return;
};
if modify_import_export.add_export.intersect_point_no_stroke(node_graph_point) {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::AddExport);
responses.add(NodeGraphMessage::SendGraph);
return;
} else if modify_import_export.add_import.intersect_point_no_stroke(node_graph_point) {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::AddImport);
responses.add(NodeGraphMessage::SendGraph);
return;
}
if network_interface
.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Grip, selection_network_path)
.is_some()
@@ -651,6 +641,37 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
return;
}
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
log::error!("Could not get modify import export in PointerDown");
return;
};
if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::AddExport);
return;
} else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::AddImport);
return;
} else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index });
return;
} else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });
return;
} else if let Some(move_import_index) = modify_import_export.reorder_imports_exports.clicked_output_port_from_point(node_graph_point) {
responses.add(DocumentMessage::StartTransaction);
self.reordering_import = Some(move_import_index);
return;
} else if let Some(move_export_index) = modify_import_export.reorder_imports_exports.clicked_input_port_from_point(node_graph_point) {
responses.add(DocumentMessage::StartTransaction);
self.reordering_export = Some(move_export_index);
return;
}
self.selection_before_pointer_down = network_interface
.selected_nodes(selection_network_path)
.map(|selected_nodes| selected_nodes.selected_nodes().cloned().collect())
@@ -881,6 +902,46 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: true });
} else if self.box_selection_start.is_some() {
responses.add(NodeGraphMessage::UpdateBoxSelection);
} else if self.reordering_import.is_some() {
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
log::error!("Could not get modify import export in PointerUp");
return;
};
// Find the first import that is below the mouse position
self.end_index = Some(
modify_import_export
.reorder_imports_exports
.output_ports()
.find_map(|(index, click_target)| {
let Some(position) = click_target.bounding_box().map(|bbox| (bbox[0].y + bbox[1].y) / 2.) else {
log::error!("Could not get bounding box for import: {index}");
return None;
};
(position > point.y).then_some(*index)
})
.unwrap_or(modify_import_export.reorder_imports_exports.output_ports().count()),
);
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
} else if self.reordering_export.is_some() {
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
log::error!("Could not get modify import export in PointerUp");
return;
};
// Find the first export that is below the mouse position
self.end_index = Some(
modify_import_export
.reorder_imports_exports
.input_ports()
.find_map(|(index, click_target)| {
let Some(position) = click_target.bounding_box().map(|bbox| (bbox[0].y + bbox[1].y) / 2.) else {
log::error!("Could not get bounding box for export: {index}");
return None;
};
(position > point.y).then_some(*index)
})
.unwrap_or(modify_import_export.reorder_imports_exports.input_ports().count()),
);
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
}
}
NodeGraphMessage::PointerUp => {
@@ -1129,15 +1190,34 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
self.select_if_not_dragged = None;
}
// End of reordering an import
else if let (Some(moving_import), Some(end_index)) = (self.reordering_import, self.end_index) {
responses.add(NodeGraphMessage::ReorderImport {
start_index: moving_import,
end_index,
});
responses.add(DocumentMessage::EndTransaction);
}
// End of reordering an export
else if let (Some(moving_export), Some(end_index)) = (self.reordering_export, self.end_index) {
responses.add(NodeGraphMessage::ReorderExport {
start_index: moving_export,
end_index,
});
responses.add(DocumentMessage::EndTransaction);
}
self.drag_start = None;
self.begin_dragging = false;
self.box_selection_start = None;
self.wire_in_progress_from_connector = None;
self.wire_in_progress_to_connector = None;
self.reordering_export = None;
self.reordering_import = None;
responses.add(DocumentMessage::EndTransaction);
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
responses.add(FrontendMessage::UpdateBox { box_selection: None })
responses.add(FrontendMessage::UpdateBox { box_selection: None });
responses.add(FrontendMessage::UpdateImportReorderIndex { index: None });
responses.add(FrontendMessage::UpdateExportReorderIndex { index: None });
}
NodeGraphMessage::PointerOutsideViewport { shift } => {
if self.drag_start.is_some() || self.box_selection_start.is_some() {
@@ -1186,6 +1266,26 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
// }
// }
}
NodeGraphMessage::RemoveImport { import_index: usize } => {
network_interface.remove_import(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
NodeGraphMessage::RemoveExport { export_index: usize } => {
network_interface.remove_export(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
NodeGraphMessage::ReorderImport { start_index, end_index } => {
network_interface.reorder_import(start_index, end_index, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
NodeGraphMessage::ReorderExport { start_index, end_index } => {
network_interface.reorder_export(start_index, end_index, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
NodeGraphMessage::RunDocumentGraph => {
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
}
@@ -1230,16 +1330,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let wires = Self::collect_wires(network_interface, breadcrumb_network_path);
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default();
let add_import = network_interface.frontend_import_modify(breadcrumb_network_path);
let add_export = network_interface.frontend_export_modify(breadcrumb_network_path);
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
add_import,
add_export,
});
responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(FrontendMessage::UpdateNodeGraph { nodes, wires });
responses.add(FrontendMessage::UpdateLayerWidths {
layer_widths,
@@ -1253,16 +1344,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if graph_view_overlay_open {
network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path);
// Send the new edges to the frontend
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default();
let add_import = network_interface.frontend_import_modify(breadcrumb_network_path);
let add_export = network_interface.frontend_export_modify(breadcrumb_network_path);
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
add_import,
add_export,
});
responses.add(NodeGraphMessage::UpdateImportsExports);
}
}
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
@@ -1272,7 +1354,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
input,
});
responses.add(PropertiesPanelMessage::Refresh);
if (!network_interface.reference(&node_id, selection_network_path).is_some_and(|reference| reference == "Imaginate") || input_index == 0)
if (!network_interface
.reference(&node_id, selection_network_path)
.is_some_and(|reference| *reference == Some("Imaginate".to_string()))
|| input_index == 0)
&& network_interface.connected_to_output(&node_id, selection_network_path)
{
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -1377,6 +1462,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
network_interface.set_display_name(&node_id, alias, selection_network_path);
}
NodeGraphMessage::SetImportExportName { name, index } => {
responses.add(DocumentMessage::StartTransaction);
responses.add(NodeGraphMessage::SetImportExportNameImpl { name, index });
responses.add(DocumentMessage::EndTransaction);
responses.add(NodeGraphMessage::UpdateImportsExports);
}
NodeGraphMessage::SetImportExportNameImpl { name, index } => network_interface.set_import_export_name(name, index, breadcrumb_network_path),
NodeGraphMessage::TogglePreview { node_id } => {
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
@@ -1427,13 +1519,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let node_ids = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();
// If any of the selected nodes are pinned, unpin them all. Otherwise, pin them all.
let pinned = !node_ids.iter().all(|node_id| {
if let Some(node) = network_interface.node_metadata(node_id, breadcrumb_network_path) {
node.persistent_metadata.pinned
} else {
false
}
});
let pinned = !node_ids.iter().all(|node_id| network_interface.is_pinned(node_id, breadcrumb_network_path));
responses.add(DocumentMessage::AddTransaction);
for node_id in &node_ids {
@@ -1442,9 +1528,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
}
NodeGraphMessage::ToggleSelectedVisibility => {
let Some(network) = network_interface.network(selection_network_path) else {
return;
};
let Some(selected_nodes) = network_interface.selected_nodes(selection_network_path) else {
log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedLocked");
return;
@@ -1452,7 +1535,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let node_ids = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();
// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
let visible = !node_ids.iter().all(|node_id| network.nodes.get(node_id).is_some_and(|node| node.visible));
let visible = !node_ids.iter().all(|node_id| network_interface.is_visible(node_id, selection_network_path));
responses.add(DocumentMessage::AddTransaction);
for node_id in &node_ids {
@@ -1461,16 +1544,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
}
NodeGraphMessage::ToggleVisibility { node_id } => {
let Some(network) = network_interface.network(selection_network_path) else {
return;
};
let Some(node) = network.nodes.get(&node_id) else {
log::error!("Cannot get node {node_id} in NodeGraphMessage::ToggleVisibility");
return;
};
let visible = !node.visible;
let visible = !network_interface.is_visible(&node_id, selection_network_path);
responses.add(DocumentMessage::AddTransaction);
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
@@ -1552,6 +1626,32 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(FrontendMessage::UpdateBox { box_selection })
}
}
NodeGraphMessage::UpdateImportsExports => {
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default();
let add_import = network_interface
.frontend_import_export_modify(
|modify_import_export_click_target| modify_import_export_click_target.add_import_export.output_ports().collect::<Vec<_>>(),
breadcrumb_network_path,
)
.into_iter()
.next();
let add_export = network_interface
.frontend_import_export_modify(
|modify_import_export_click_target| modify_import_export_click_target.add_import_export.input_ports().collect::<Vec<_>>(),
breadcrumb_network_path,
)
.into_iter()
.next();
responses.add(FrontendMessage::UpdateImportsExports {
imports,
exports,
add_import,
add_export,
});
}
NodeGraphMessage::UpdateLayerPanel => {
Self::update_layer_panel(network_interface, selection_network_path, collapsed, responses);
}
@@ -1671,14 +1771,7 @@ impl NodeGraphMessageHandler {
let has_selection = selected_nodes.has_selected_nodes();
let selection_includes_layers = network_interface.selected_nodes(&[]).unwrap().selected_layers(network_interface.document_metadata()).count() > 0;
let selection_all_locked = network_interface.selected_nodes(&[]).unwrap().selected_unlocked_layers(network_interface).count() == 0;
let selection_all_visible = selected_nodes.selected_nodes().all(|id| {
if let Some(node) = network.nodes.get(id) {
node.visible
} else {
error!("Could not get node {id} in update_selection_action_buttons");
true
}
});
let selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path));
let mut widgets = vec![
PopoverButton::new()
@@ -1839,10 +1932,6 @@ impl NodeGraphMessageHandler {
/// Collate the properties panel sections for a node graph
pub fn collate_properties(context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
// If the selected nodes are in the document network, use the document network. Otherwise, use the nested network
let Some(network) = context.network_interface.network(context.selection_network_path) else {
warn!("No network in collate_properties");
return Vec::new();
};
let Some(selected_nodes) = context.network_interface.selected_nodes(context.selection_network_path) else {
warn!("No selected nodes in collate_properties");
return Vec::new();
@@ -1868,25 +1957,13 @@ impl NodeGraphMessageHandler {
match layers.len() {
// If no layers are selected, show properties for all selected nodes
0 => {
let selected_nodes = nodes
.iter()
.filter_map(|node_id| {
network.nodes.get(node_id).map(|node| {
let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) {
node.persistent_metadata.pinned
} else {
error!("Could not get node {node_id} in collate_properties");
false
};
node_properties::generate_node_properties(node, *node_id, pinned, context)
})
})
.collect::<Vec<_>>();
let selected_nodes = nodes.iter().map(|node_id| node_properties::generate_node_properties(*node_id, context)).collect::<Vec<_>>();
if !selected_nodes.is_empty() {
return selected_nodes;
}
// TODO: Display properties for encapsulating node when no nodes are selected in a nested network
// This may require store a separate path for the properties panel
let mut properties = vec![LayoutGroup::Row {
widgets: vec![
Separator::new(SeparatorType::Related).widget_holder(),
@@ -1900,20 +1977,20 @@ impl NodeGraphMessageHandler {
],
}];
let Some(network) = context.network_interface.network(context.selection_network_path) else {
warn!("No network in collate_properties");
return Vec::new();
};
// And if no nodes are selected, show properties for all pinned nodes
let pinned_node_properties = network
.nodes
.keys()
.cloned()
.collect::<Vec<_>>()
.iter()
.filter_map(|(node_id, node)| {
let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) {
node.persistent_metadata.pinned
} else {
error!("Could not get node {node_id} in collate_properties");
false
};
if pinned {
Some(node_properties::generate_node_properties(node, *node_id, pinned, context))
.filter_map(|node_id| {
if context.network_interface.is_pinned(node_id, context.selection_network_path) {
Some(node_properties::generate_node_properties(*node_id, context))
} else {
None
}
@@ -1983,17 +2060,9 @@ impl NodeGraphMessageHandler {
!context.network_interface.is_layer(node_id, context.selection_network_path)
}
})
.filter_map(|(_, node_id)| network.nodes.get(&node_id).map(|node| (node, node_id)))
.map(|(node, node_id)| {
let pinned = if let Some(node) = context.network_interface.node_metadata(&node_id, context.selection_network_path) {
node.persistent_metadata.pinned
} else {
error!("Could not get node {node_id} in collate_properties");
false
};
node_properties::generate_node_properties(node, node_id, pinned, context)
})
.collect::<Vec<_>>()
.iter()
.map(|(_, node_id)| node_properties::generate_node_properties(*node_id, context))
.collect::<Vec<_>>();
layer_properties.extend(node_properties);
@@ -2110,8 +2179,9 @@ impl NodeGraphMessageHandler {
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
let mut inputs = inputs.into_iter().map(|input| {
input.map(|input| FrontendGraphInput {
data_type: FrontendGraphDataType::with_type(&input.ty),
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
resolved_type: Some(format!("{:?} from {:?}", &input.ty, input.type_source)),
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
name: input.name.unwrap_or_else(|| input.ty.nested_type().to_string()),
connected_to: input.output_connector,
})
@@ -2122,8 +2192,8 @@ impl NodeGraphMessageHandler {
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
let primary_output_type = output_types.first().cloned().flatten();
let frontend_data_type = if let Some((output_type, _)) = &primary_output_type {
FrontendGraphDataType::with_type(output_type)
let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type {
FrontendGraphDataType::displayed_type(output_type, type_source)
} else {
FrontendGraphDataType::General
};
@@ -2144,8 +2214,8 @@ impl NodeGraphMessageHandler {
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
continue;
}
let frontend_data_type = if let Some((output_type, _)) = &exposed_output {
FrontendGraphDataType::with_type(output_type)
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output {
FrontendGraphDataType::displayed_type(output_type, type_source)
} else {
FrontendGraphDataType::General
};
@@ -2203,7 +2273,7 @@ impl NodeGraphMessageHandler {
.node_metadata(&node_id, breadcrumb_network_path)
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),
can_be_layer: can_be_layer_lookup.contains(&node_id),
reference: network_interface.reference(&node_id, breadcrumb_network_path),
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
display_name: network_interface.frontend_display_name(&node_id, breadcrumb_network_path),
primary_input,
exposed_inputs,
@@ -2273,7 +2343,7 @@ impl NodeGraphMessageHandler {
|| (
// Check if the last node in the chain has an exposed left input
network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|
network_interface.network(&[]).unwrap().nodes.get(&node_id).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
if network_interface.is_layer(&node_id, &[]) {
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some())
} else {
@@ -2284,7 +2354,7 @@ impl NodeGraphMessageHandler {
let parents_visible = layer.ancestors(network_interface.document_metadata()).filter(|&ancestor| ancestor != layer).all(|layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
network_interface.network(&[]).unwrap().nodes.get(&layer.to_node()).map(|node| node.visible).unwrap_or_default()
network_interface.document_node(&layer.to_node(), &[]).map(|node| node.visible).unwrap_or_default()
} else {
true
}
@@ -2389,6 +2459,7 @@ struct InputLookup {
name: Option<String>,
ty: Type,
type_source: TypeSource,
valid_types: Vec<Type>,
output_connector: Option<OutputConnector>,
}
@@ -2412,8 +2483,10 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
}
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
let name = network_interface.input_name(&node_id, index, breadcrumb_network_path);
let name = network_interface
.input_name(&node_id, index, breadcrumb_network_path)
.filter(|s| !s.is_empty())
.map(|name| name.to_string());
// Get the output connector that feeds into this input (done here as well for simplicity)
let connector = OutputConnector::from_input(input);
@@ -2430,13 +2503,21 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
for (index, value) in value.iter_mut().enumerate() {
// Skip not exposed inputs for efficiency
let Some(value) = value else { continue };
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
let (ty, type_source) = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path);
value.ty = ty;
value.type_source = type_source;
}
}
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
for (index, value) in value.iter_mut().enumerate() {
// Skip not exposed inputs for efficiency
let Some(value) = value else { continue };
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
value.valid_types = network_interface.valid_input_types(&InputConnector::node(node_id, index), breadcrumb_network_path);
}
}
frontend_inputs_lookup
}
@@ -2462,6 +2543,9 @@ impl Default for NodeGraphMessageHandler {
deselect_on_pointer_up: None,
auto_panning: Default::default(),
preview_on_mouse_up: None,
reordering_export: None,
reordering_import: None,
end_index: None,
}
}
}

View File

@@ -2,7 +2,7 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeId;
use graphene_core::Type;
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector, TypeSource};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
@@ -11,12 +11,12 @@ pub enum FrontendGraphDataType {
Raster,
VectorData,
Number,
Graphic,
Group,
Artboard,
}
impl FrontendGraphDataType {
pub fn with_type(input: &Type) -> Self {
fn with_type(input: &Type) -> Self {
match TaggedValue::from_type_or_none(input) {
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
@@ -30,11 +30,18 @@ impl FrontendGraphDataType {
| TaggedValue::F64Array4(_)
| TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_) => Self::Number,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Graphic,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group,
TaggedValue::ArtboardGroup(_) => Self::Artboard,
_ => Self::General,
}
}
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
match type_source {
TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General,
_ => Self::with_type(input),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -44,6 +51,8 @@ pub struct FrontendGraphInput {
pub name: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
#[serde(rename = "validTypes")]
pub valid_types: Vec<String>,
#[serde(rename = "connectedTo")]
pub connected_to: Option<OutputConnector>,
}
@@ -180,6 +189,8 @@ pub struct FrontendClickTargets {
pub all_nodes_bounding_box: String,
#[serde(rename = "importExportsBoundingBox")]
pub import_exports_bounding_box: String,
#[serde(rename = "modifyImportExport")]
pub modify_import_export: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]

View File

@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::node_graph_executor::NodeGraphExecutor;
pub struct PropertiesPanelMessageHandlerData<'a> {
pub network_interface: &'a NodeNetworkInterface,
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub document_name: &'a str,
pub executor: &'a mut NodeGraphExecutor,

View File

@@ -384,7 +384,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
document_serialized_content,
} => {
// It can be helpful to temporarily set `upgrade_from_before_editable_subgraphs` to true if it's desired to upgrade a piece of artwork to use fresh copies of all nodes
let upgrade_from_before_editable_subgraphs = document_serialized_content.contains("node_output_index");
let replace_implementations_from_definition = document_serialized_content.contains("node_output_index");
let upgrade_vector_manipulation_format = document_serialized_content.contains("ManipulatorGroupIds") && !document_name.contains("__DO_NOT_UPGRADE__");
let document_name = document_name.replace("__DO_NOT_UPGRADE__", "");
@@ -409,7 +409,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
// TODO: Eventually remove this document upgrade code
// Upgrade all old nodes to support editable subgraphs introduced in #1750
if upgrade_from_before_editable_subgraphs {
if replace_implementations_from_definition {
// 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
@@ -614,6 +614,31 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
.network_interface
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
}
if reference == "Image" && inputs_count == 1 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let new_image_node = node_definition.default_node_template();
document.network_interface.replace_implementation(node_id, &[], new_image_node.document_node.implementation);
// Insert a new empty input for the image
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", &[*node_id]);
document.network_interface.set_reference(node_id, &[], Some("Image".to_string()));
}
if reference == "Noise Pattern" && inputs_count == 15 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let new_noise_pattern_node = node_definition.default_node_template();
document.network_interface.replace_implementation(node_id, &[], new_noise_pattern_node.document_node.implementation);
let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), &[]);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), &[]);
for (i, input) in old_inputs.iter().enumerate() {
document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), &[]);
}
}
}
// TODO: Eventually remove this document upgrade code

View File

@@ -186,7 +186,7 @@ impl<'a> NodeGraphLayer<'a> {
/// 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_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| reference == node_name))
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
}
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
@@ -194,7 +194,7 @@ impl<'a> NodeGraphLayer<'a> {
self.horizontal_layer_flow()
.skip(1)// Skip self
.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))
.find(|node_id| self.network_interface.reference(node_id,&[]).is_some_and(|reference| *reference == Some(node_name.to_string())))
.and_then(|node_id| self.network_interface.network(&[]).unwrap().nodes.get(&node_id).map(|node| &node.inputs))
}

View File

@@ -277,7 +277,7 @@ impl BrushToolData {
let Some(reference) = document.network_interface.reference(&node_id, &[]) else {
continue;
};
if reference == "Brush" && node_id != layer.to_node() {
if *reference == Some("Brush".to_string()) && node_id != layer.to_node() {
let points_input = node.inputs.get(2)?;
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else {
continue;
@@ -285,7 +285,7 @@ impl BrushToolData {
self.strokes.clone_from(strokes);
return Some(layer);
} else if reference == "Transform" {
} else if *reference == Some("Transform".to_string()) {
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;

View File

@@ -793,7 +793,10 @@ impl Fsm for PenToolFsmState {
(_, PenToolMessage::Redo) => {
tool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1));
tool_data.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses);
(tool_data.point_index == 0).then_some(PenToolFsmState::Ready).unwrap_or(PenToolFsmState::PlacingAnchor)
match tool_data.point_index {
0 => PenToolFsmState::Ready,
_ => PenToolFsmState::PlacingAnchor,
}
}
_ => self,
}