mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-03 23:28:11 +08:00
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:
co-authored by
Keavon Chambers
parent
ad68b1e5c8
commit
eec0ef761c
Generated
+172
-178
File diff suppressed because it is too large
Load Diff
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -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>,
|
||||
|
||||
@@ -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,11 +1459,9 @@ 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 {
|
||||
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,
|
||||
@@ -1481,10 +1473,8 @@ impl DocumentMessageHandler {
|
||||
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)
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
.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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)]
|
||||
|
||||
@@ -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,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -123,8 +123,8 @@
|
||||
--color-data-vectordata-dim: #4b778c;
|
||||
--color-data-number: #cbbab4;
|
||||
--color-data-number-dim: #87736b;
|
||||
--color-data-graphic: #6b84e8;
|
||||
--color-data-graphic-dim: #4a557b;
|
||||
--color-data-group: #6b84e8;
|
||||
--color-data-group-dim: #4a557b;
|
||||
--color-data-artboard: #70a898;
|
||||
--color-data-artboard-dim: #3a6156;
|
||||
|
||||
|
||||
@@ -47,6 +47,69 @@
|
||||
|
||||
$: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths);
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
let hoveringImportIndex: number | undefined = undefined;
|
||||
let hoveringExportIndex: number | undefined = undefined;
|
||||
|
||||
let editingNameImportIndex: number | undefined = undefined;
|
||||
let editingNameExportIndex: number | undefined = undefined;
|
||||
let editingNameText = "";
|
||||
|
||||
function exportsToEdgeTextInputWidth() {
|
||||
let exportTextDivs = document.querySelectorAll(`[data-export-text-edge]`);
|
||||
let exportTextDiv = Array.from(exportTextDivs).find((div) => {
|
||||
return div.getAttribute("data-index") === String(editingNameExportIndex);
|
||||
});
|
||||
if (!graph || !exportTextDiv) return "50px";
|
||||
let distance = graph.getBoundingClientRect().right - exportTextDiv.getBoundingClientRect().right;
|
||||
return distance - 15 + "px";
|
||||
}
|
||||
|
||||
function importsToEdgeTextInputWidth() {
|
||||
let importTextDivs = document.querySelectorAll(`[data-import-text-edge]`);
|
||||
let importTextDiv = Array.from(importTextDivs).find((div) => {
|
||||
return div.getAttribute("data-index") === String(editingNameImportIndex);
|
||||
});
|
||||
if (!graph || !importTextDiv) return "50px";
|
||||
let distance = importTextDiv.getBoundingClientRect().left - graph.getBoundingClientRect().left;
|
||||
return distance - 15 + "px";
|
||||
}
|
||||
|
||||
function setEditingImportNameIndex(index: number, currentName: string) {
|
||||
focusInput(currentName);
|
||||
editingNameImportIndex = index;
|
||||
}
|
||||
|
||||
function setEditingExportNameIndex(index: number, currentName: string) {
|
||||
focusInput(currentName);
|
||||
editingNameExportIndex = index;
|
||||
}
|
||||
|
||||
function focusInput(currentName: string) {
|
||||
editingNameText = currentName;
|
||||
setTimeout(() => {
|
||||
if (inputElement) {
|
||||
inputElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function setEditingImportName(event: Event) {
|
||||
if (editingNameImportIndex !== undefined) {
|
||||
let text = (event.target as HTMLInputElement)?.value;
|
||||
editor.handle.setImportName(editingNameImportIndex, text);
|
||||
editingNameImportIndex = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function setEditingExportName(event: Event) {
|
||||
if (editingNameExportIndex !== undefined) {
|
||||
let text = (event.target as HTMLInputElement)?.value;
|
||||
editor.handle.setExportName(editingNameExportIndex, text);
|
||||
editingNameExportIndex = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function calculateGridSpacing(scale: number): number {
|
||||
const dense = scale * GRID_SIZE;
|
||||
let sparse = dense;
|
||||
@@ -267,6 +330,10 @@
|
||||
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${value.dataType}`;
|
||||
}
|
||||
|
||||
function validTypesText(value: FrontendGraphInput): string {
|
||||
return `Valid Types:\n${value.validTypes.join(",\n ")}`;
|
||||
}
|
||||
|
||||
function outputConnectedToText(output: FrontendGraphOutput): string {
|
||||
if (output.connectedTo.length === 0) {
|
||||
return "Connected to nothing";
|
||||
@@ -396,6 +463,9 @@
|
||||
{/each}
|
||||
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.allNodesBoundingBox} />
|
||||
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.importExportsBoundingBox} />
|
||||
{#each $nodeGraph.clickTargets.modifyImportExport as pathString}
|
||||
<path class="modify-import-export" d={pathString} />
|
||||
{/each}
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -439,15 +509,54 @@
|
||||
<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>
|
||||
|
||||
<div
|
||||
class="edit-import-export import"
|
||||
on:pointerenter={() => (hoveringImportIndex = index)}
|
||||
on:pointerleave={() => (hoveringImportIndex = undefined)}
|
||||
style:--offset-left={position.x / 24}
|
||||
style:--offset-top={position.y / 24}
|
||||
>
|
||||
{#if editingNameImportIndex == index}
|
||||
<input
|
||||
class="import-text-input"
|
||||
type="text"
|
||||
style:width={importsToEdgeTextInputWidth()}
|
||||
bind:this={inputElement}
|
||||
bind:value={editingNameText}
|
||||
on:blur={setEditingImportName}
|
||||
on:keydown={(e) => e.key === "Enter" && setEditingImportName(e)}
|
||||
/>
|
||||
{:else}
|
||||
<p class="import-text" on:dblclick={() => setEditingImportNameIndex(index, outputMetadata.name)}>{outputMetadata.name}</p>
|
||||
{/if}
|
||||
{#if hoveringImportIndex === index || editingNameImportIndex === index}
|
||||
<IconButton
|
||||
size={16}
|
||||
icon={"Remove"}
|
||||
class="remove-button-import"
|
||||
data-index={index}
|
||||
data-import-text-edge
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
/>
|
||||
<div class="reorder-drag-grip" title="Reorder this import"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if $nodeGraph.reorderImportIndex !== undefined}
|
||||
{@const position = {
|
||||
x: Number($nodeGraph.imports[0].position.x),
|
||||
y: Number($nodeGraph.imports[0].position.y) + Number($nodeGraph.reorderImportIndex) * 24,
|
||||
}}
|
||||
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 4) / 24} />
|
||||
{/if}
|
||||
{#if $nodeGraph.addImport !== undefined}
|
||||
<div class="plus" style:--offset-left={$nodeGraph.addImport.x / 24} style:--offset-top={$nodeGraph.addImport.y / 24}>
|
||||
<IconButton
|
||||
class={"visibility"}
|
||||
data-visibility-button
|
||||
size={24}
|
||||
icon={"Add"}
|
||||
icon="Add"
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
@@ -474,13 +583,50 @@
|
||||
<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>
|
||||
<div
|
||||
class="edit-import-export export"
|
||||
on:pointerenter={() => (hoveringExportIndex = index)}
|
||||
on:pointerleave={() => (hoveringExportIndex = undefined)}
|
||||
style:--offset-left={position.x / 24}
|
||||
style:--offset-top={position.y / 24}
|
||||
>
|
||||
{#if hoveringExportIndex === index || editingNameExportIndex === index}
|
||||
<div class="reorder-drag-grip" title="Reorder this export"></div>
|
||||
<IconButton
|
||||
size={16}
|
||||
icon={"Remove"}
|
||||
class="remove-button-export"
|
||||
data-index={index}
|
||||
data-export-text-edge
|
||||
action={() => {
|
||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{#if editingNameExportIndex === index}
|
||||
<input
|
||||
type="text"
|
||||
style:width={exportsToEdgeTextInputWidth()}
|
||||
bind:this={inputElement}
|
||||
bind:value={editingNameText}
|
||||
on:blur={setEditingExportName}
|
||||
on:keydown={(e) => e.key === "Enter" && setEditingExportName(e)}
|
||||
/>
|
||||
{:else}
|
||||
<p class="export-text" on:dblclick={() => setEditingExportNameIndex(index, inputMetadata.name)}>{inputMetadata.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if $nodeGraph.reorderExportIndex !== undefined}
|
||||
{@const position = {
|
||||
x: Number($nodeGraph.exports[0].position.x),
|
||||
y: Number($nodeGraph.exports[0].position.y) + Number($nodeGraph.reorderExportIndex) * 24,
|
||||
}}
|
||||
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 4) / 24} />
|
||||
{/if}
|
||||
{#if $nodeGraph.addExport !== undefined}
|
||||
<div class="plus" style:--offset-left={$nodeGraph.addExport.x / 24} style:--offset-top={$nodeGraph.addExport.y / 24}>
|
||||
<IconButton
|
||||
class={"visibility"}
|
||||
data-visibility-button
|
||||
size={24}
|
||||
icon={"Add"}
|
||||
action={() => {
|
||||
@@ -566,7 +712,7 @@
|
||||
bind:this={inputs[nodeIndex + 1][0]}
|
||||
>
|
||||
{#if node.primaryInput}
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n${validTypesText(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
{/if}
|
||||
{#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)" />
|
||||
@@ -591,7 +737,7 @@
|
||||
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][1]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(stackDataInput)}\n${inputConnectedToText(stackDataInput)}`}</title>
|
||||
<title>{`${dataTypeTooltip(stackDataInput)}\n${validTypesText(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}
|
||||
@@ -698,7 +844,7 @@
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][0]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n${validTypesText(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}
|
||||
@@ -718,7 +864,7 @@
|
||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n${inputConnectedToText(secondary)}`}</title>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n${validTypesText(secondary)}\n${inputConnectedToText(secondary)}`}</title>
|
||||
{#if secondary.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}
|
||||
@@ -880,6 +1026,10 @@
|
||||
.all-nodes-bounding-box {
|
||||
stroke: purple;
|
||||
}
|
||||
|
||||
.modify-import-export {
|
||||
stroke: orange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,30 +1068,66 @@
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
.reorder-bar {
|
||||
position: absolute;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.plus {
|
||||
margin-top: -4px;
|
||||
margin-left: -4px;
|
||||
position: absolute;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
.export-text {
|
||||
.edit-import-export {
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
top: calc(var(--offset-top) * 24px);
|
||||
left: calc(var(--offset-left) * 24px);
|
||||
margin-top: -5px;
|
||||
height: 24px;
|
||||
|
||||
&.import {
|
||||
right: calc(100% - var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
&.export {
|
||||
left: calc(var(--offset-left) * 24px + 17px);
|
||||
}
|
||||
|
||||
.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;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.export-text {
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.import-text-input {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.remove-button-import {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.remove-button-export {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.reorder-drag-grip {
|
||||
width: 8px;
|
||||
height: 24px;
|
||||
background-position: 2px 8px;
|
||||
border-radius: 2px;
|
||||
margin: -6px 0;
|
||||
background-image: var(--icon-drag-grip-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,14 @@ export class UpdateInSelectedNetwork extends JsMessage {
|
||||
readonly inSelectedNetwork!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateImportReorderIndex extends JsMessage {
|
||||
readonly importIndex!: number | undefined;
|
||||
}
|
||||
|
||||
export class UpdateExportReorderIndex extends JsMessage {
|
||||
readonly exportIndex!: number | undefined;
|
||||
}
|
||||
|
||||
const LayerWidths = Transform(({ obj }) => obj.layerWidths);
|
||||
const ChainWidths = Transform(({ obj }) => obj.chainWidths);
|
||||
const HasLeftInputWire = Transform(({ obj }) => obj.hasLeftInputWire);
|
||||
@@ -170,6 +178,7 @@ export type FrontendClickTargets = {
|
||||
readonly iconClickTargets: string[];
|
||||
readonly allNodesBoundingBox: string;
|
||||
readonly importExportsBoundingBox: string;
|
||||
readonly modifyImportExport: string[];
|
||||
};
|
||||
|
||||
export type ContextMenuInformation = {
|
||||
@@ -178,7 +187,7 @@ export type ContextMenuInformation = {
|
||||
contextMenuData: "CreateNode" | { nodeId: bigint; currentlyIsNode: boolean };
|
||||
};
|
||||
|
||||
export type FrontendGraphDataType = "General" | "Raster" | "VectorData" | "Number" | "Graphic" | "Artboard";
|
||||
export type FrontendGraphDataType = "General" | "Raster" | "VectorData" | "Number" | "Group" | "Artboard";
|
||||
|
||||
export class Node {
|
||||
readonly index!: bigint;
|
||||
@@ -210,6 +219,8 @@ export class FrontendGraphInput {
|
||||
|
||||
readonly resolvedType!: string | undefined;
|
||||
|
||||
readonly validTypes!: string[];
|
||||
|
||||
@CreateOutputConnectorOptional
|
||||
connectedTo!: Node | undefined;
|
||||
}
|
||||
@@ -1592,6 +1603,8 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
UpdateImportsExports,
|
||||
UpdateInputHints,
|
||||
UpdateInSelectedNetwork,
|
||||
UpdateExportReorderIndex,
|
||||
UpdateImportReorderIndex,
|
||||
UpdateLayersPanelControlBarLayout,
|
||||
UpdateLayerWidths,
|
||||
UpdateMenuBarLayout,
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
UpdateClickTargets,
|
||||
UpdateContextMenuInformation,
|
||||
UpdateInSelectedNetwork,
|
||||
UpdateImportReorderIndex,
|
||||
UpdateExportReorderIndex,
|
||||
UpdateImportsExports,
|
||||
UpdateLayerWidths,
|
||||
UpdateNodeGraph,
|
||||
@@ -47,6 +49,8 @@ export function createNodeGraphState(editor: Editor) {
|
||||
selected: [] as bigint[],
|
||||
transform: { scale: 1, x: 0, y: 0 },
|
||||
inSelectedNetwork: true,
|
||||
reorderImportIndex: undefined as number | undefined,
|
||||
reorderExportIndex: undefined as number | undefined,
|
||||
});
|
||||
|
||||
// Set up message subscriptions on creation
|
||||
@@ -76,6 +80,18 @@ export function createNodeGraphState(editor: Editor) {
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateImportReorderIndex, (updateImportReorderIndex) => {
|
||||
update((state) => {
|
||||
state.reorderImportIndex = updateImportReorderIndex.importIndex;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateExportReorderIndex, (updateExportReorderIndex) => {
|
||||
update((state) => {
|
||||
state.reorderExportIndex = updateExportReorderIndex.exportIndex;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateImportsExports, (updateImportsExports) => {
|
||||
update((state) => {
|
||||
state.imports = updateImportsExports.imports;
|
||||
|
||||
@@ -12,7 +12,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::document::utility_types::network_interface::{ImportOrExport, NodeTemplate};
|
||||
use editor::messages::portfolio::utility_types::Platform;
|
||||
use editor::messages::prelude::*;
|
||||
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
|
||||
@@ -697,6 +697,26 @@ impl EditorHandle {
|
||||
self.dispatch(DocumentMessage::SetToNodeOrLayer { node_id: NodeId(id), is_layer });
|
||||
}
|
||||
|
||||
/// Set the name of an import or export
|
||||
#[wasm_bindgen(js_name = setImportName)]
|
||||
pub fn set_import_name(&self, index: usize, name: String) {
|
||||
let message = NodeGraphMessage::SetImportExportName {
|
||||
name,
|
||||
index: ImportOrExport::Import(index),
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Set the name of an export
|
||||
#[wasm_bindgen(js_name = setExportName)]
|
||||
pub fn set_export_name(&self, index: usize, name: String) {
|
||||
let message = NodeGraphMessage::SetImportExportName {
|
||||
name,
|
||||
index: ImportOrExport::Export(index),
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
||||
pub fn inject_imaginate_poll_server_status(&self) {
|
||||
self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
||||
@@ -754,7 +774,10 @@ impl EditorHandle {
|
||||
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")
|
||||
if document
|
||||
.network_interface
|
||||
.reference(node_id, &[])
|
||||
.is_some_and(|reference| *reference == Some("To Artboard".to_string()))
|
||||
&& document
|
||||
.network_interface
|
||||
.network(&[])
|
||||
@@ -770,7 +793,7 @@ impl EditorHandle {
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ToArtboardNode"));
|
||||
document.network_interface.add_import(TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string(), &[node_id]);
|
||||
document.network_interface.add_import(TaggedValue::IVec2(glam::IVec2::default()), false, 2, "", &[node_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl ValueProvider for MathNodeContext {
|
||||
}
|
||||
|
||||
/// Calculates a mathematical expression with input values "A" and "B"
|
||||
#[node_macro::node(category("Math"))]
|
||||
#[node_macro::node(category("General"), properties("math_properties"))]
|
||||
fn math<U: num_traits::float::Float>(
|
||||
_: (),
|
||||
/// The value of "A" when calculating the expression
|
||||
|
||||
@@ -1185,7 +1185,7 @@ impl DomainWarpType {
|
||||
// Aims for interoperable compatibility with:
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer
|
||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))]
|
||||
async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[implementations(
|
||||
(),
|
||||
@@ -1555,7 +1555,7 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
||||
//
|
||||
// Algorithm based on:
|
||||
// https://geraldbakker.nl/psnumbers/exposure.html
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))]
|
||||
async fn exposure<F: 'n + Send, T: Adjust<Color>>(
|
||||
#[implementations(
|
||||
(),
|
||||
@@ -1683,7 +1683,13 @@ mod index_node {
|
||||
use crate::raster::{Color, ImageFrame};
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
pub fn index<T: Default + Clone>(_: (), #[implementations(Vec<ImageFrame<Color>>, Vec<Color>)] input: Vec<T>, index: u32) -> T {
|
||||
pub fn index<T: Default + Clone>(
|
||||
_: (),
|
||||
#[implementations(Vec<ImageFrame<Color>>, Vec<Color>)]
|
||||
#[widget(ParsedWidgetOverride::Hidden)]
|
||||
input: Vec<T>,
|
||||
index: u32,
|
||||
) -> T {
|
||||
if (index as usize) < input.len() {
|
||||
input[index as usize].clone()
|
||||
} else {
|
||||
|
||||
@@ -29,27 +29,39 @@ pub mod types {
|
||||
pub type Resolution = glam::UVec2;
|
||||
}
|
||||
|
||||
// Translation struct between macro and definition
|
||||
#[derive(Clone)]
|
||||
pub struct NodeMetadata {
|
||||
pub display_name: &'static str,
|
||||
pub category: Option<&'static str>,
|
||||
pub fields: Vec<FieldMetadata>,
|
||||
pub description: &'static str,
|
||||
pub properties: Option<&'static str>,
|
||||
}
|
||||
|
||||
// Translation struct between macro and definition
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FieldMetadata {
|
||||
pub name: &'static str,
|
||||
pub description: &'static str,
|
||||
pub exposed: bool,
|
||||
pub value_source: ValueSource,
|
||||
pub widget_override: RegistryWidgetOverride,
|
||||
pub value_source: RegistryValueSource,
|
||||
pub number_min: Option<f64>,
|
||||
pub number_max: Option<f64>,
|
||||
pub number_mode_range: Option<(f64, f64)>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ValueSource {
|
||||
pub enum RegistryWidgetOverride {
|
||||
None,
|
||||
Hidden,
|
||||
String(&'static str),
|
||||
Custom(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RegistryValueSource {
|
||||
None,
|
||||
Default(&'static str),
|
||||
Scope(&'static str),
|
||||
|
||||
@@ -55,7 +55,7 @@ fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _prima
|
||||
ellipse
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
|
||||
fn rectangle<F: 'n + Send, T: CornerRadius>(
|
||||
#[implementations((), Footprint)] _footprint: F,
|
||||
_primary: (),
|
||||
|
||||
@@ -50,14 +50,15 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||
Footprint -> GraphicGroup,
|
||||
Footprint -> VectorData,
|
||||
)]
|
||||
#[widget(ParsedWidgetOverride::Hidden)]
|
||||
vector_group: impl Node<F, Output = T>,
|
||||
#[default(true)] fill: bool,
|
||||
stroke: bool,
|
||||
gradient: GradientStops,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops,
|
||||
reverse: bool,
|
||||
randomize: bool,
|
||||
seed: SeedValue,
|
||||
repeat_every: u32,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_randomize")] randomize: bool,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
|
||||
) -> T {
|
||||
let mut input = vector_group.eval(footprint).await;
|
||||
let length = input.vector_iter_mut().count();
|
||||
@@ -89,7 +90,7 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
||||
input
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||
async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
@@ -161,7 +162,7 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
|
||||
target
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||
async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
@@ -430,7 +431,7 @@ async fn bounding_box<F: 'n + Send>(
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
||||
async fn offset_path<F: 'n + Send>(
|
||||
#[implementations(
|
||||
(),
|
||||
|
||||
@@ -762,7 +762,7 @@ 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.
|
||||
#[cfg_attr(feature = "serde", serde(alias = "outputs", deserialize_with = "deserialize_exports"))] // TODO: Eventually remove this alias document upgrade code
|
||||
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.
|
||||
// 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.
|
||||
#[cfg_attr(
|
||||
|
||||
@@ -430,24 +430,11 @@ generate_imaginate_node! {
|
||||
tiling: Tiling: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageFrameNode<P, Transform> {
|
||||
transform: Transform,
|
||||
_p: PhantomData<P>,
|
||||
}
|
||||
#[node_macro::old_node_fn(ImageFrameNode<_P>)]
|
||||
fn image_frame<_P: Pixel>(image: Image<_P>, transform: DAffine2) -> ImageFrame<_P> {
|
||||
ImageFrame {
|
||||
image,
|
||||
transform,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Generator"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn noise_pattern(
|
||||
footprint: Footprint,
|
||||
_primary: (),
|
||||
clip: bool,
|
||||
seed: u32,
|
||||
scale: f64,
|
||||
|
||||
@@ -55,12 +55,22 @@ async fn draw_image_frame(_: (), image: ImageFrame<graphene_core::raster::SRGBA8
|
||||
|
||||
#[node_macro::node(category("Network"))]
|
||||
async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
|
||||
editor.application_io.as_ref().unwrap().load_resource(url).unwrap().await.unwrap()
|
||||
let Some(api) = editor.application_io.as_ref() else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
let Ok(data) = api.load_resource(url) else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
let Ok(data) = data.await else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame<Color> {
|
||||
let image = image::load_from_memory(data.as_ref()).expect("Failed to decode image");
|
||||
let Some(image) = image::load_from_memory(data.as_ref()).ok() else { return ImageFrame::default() };
|
||||
let image = image.to_rgba32f();
|
||||
let image = ImageFrame {
|
||||
image: Image {
|
||||
|
||||
@@ -361,7 +361,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option<WgpuSurface>]),
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
|
||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image<Color>, params: [&str]),
|
||||
register_node!(graphene_std::raster::ImageFrameNode<_, _>, input: Image<Color>, params: [DAffine2]),
|
||||
];
|
||||
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
||||
|
||||
@@ -84,21 +84,36 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
ParsedField::Regular { ty, .. } => ty.clone(),
|
||||
ParsedField::Node { output_type, input_type, .. } => match parsed.is_async {
|
||||
true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future<Output=#output_type> + #graphene_core::WasmNotSend>),
|
||||
|
||||
false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let widget_override: Vec<_> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let parsed_widget_override = match field {
|
||||
ParsedField::Regular { widget_override, .. } => widget_override,
|
||||
ParsedField::Node { widget_override, .. } => widget_override,
|
||||
};
|
||||
match parsed_widget_override {
|
||||
ParsedWidgetOverride::None => quote!(RegistryWidgetOverride::None),
|
||||
ParsedWidgetOverride::Hidden => quote!(RegistryWidgetOverride::Hidden),
|
||||
ParsedWidgetOverride::String(lit_str) => quote!(RegistryWidgetOverride::String(#lit_str)),
|
||||
ParsedWidgetOverride::Custom(lit_str) => quote!(RegistryWidgetOverride::Custom(#lit_str)),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let value_sources: Vec<_> = fields
|
||||
.iter()
|
||||
.map(|field| match field {
|
||||
ParsedField::Regular { value_source, .. } => match value_source {
|
||||
ValueSource::Default(data) => quote!(ValueSource::Default(stringify!(#data))),
|
||||
ValueSource::Scope(data) => quote!(ValueSource::Scope(#data)),
|
||||
_ => quote!(ValueSource::None),
|
||||
ParsedValueSource::Default(data) => quote!(RegistryValueSource::Default(stringify!(#data))),
|
||||
ParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)),
|
||||
_ => quote!(RegistryValueSource::None),
|
||||
},
|
||||
_ => quote!(ValueSource::None),
|
||||
_ => quote!(RegistryValueSource::None),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -213,6 +228,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
|
||||
let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake));
|
||||
|
||||
let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
|
||||
|
||||
Ok(quote! {
|
||||
/// Underlying implementation for [#struct_name]
|
||||
#[inline]
|
||||
@@ -235,7 +252,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
use gcore::{Node, NodeIOTypes, concrete, fn_type, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
||||
use gcore::value::ClonedNode;
|
||||
use gcore::ops::TypeNode;
|
||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, ValueSource};
|
||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
|
||||
use gcore::ctor::ctor;
|
||||
|
||||
// Use the types specified in the implementation
|
||||
@@ -266,10 +283,12 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
display_name: #display_name,
|
||||
category: #category,
|
||||
description: #description,
|
||||
properties: #properties,
|
||||
fields: vec![
|
||||
#(
|
||||
FieldMetadata {
|
||||
name: #input_names,
|
||||
widget_override: #widget_override,
|
||||
description: #input_descriptions,
|
||||
exposed: #exposed,
|
||||
value_source: #value_sources,
|
||||
|
||||
@@ -39,26 +39,69 @@ pub(crate) struct NodeFnAttributes {
|
||||
pub(crate) display_name: Option<LitStr>,
|
||||
pub(crate) path: Option<Path>,
|
||||
pub(crate) skip_impl: bool,
|
||||
pub(crate) properties_string: Option<LitStr>,
|
||||
// Add more attributes as needed
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub enum ValueSource {
|
||||
pub enum ParsedValueSource {
|
||||
#[default]
|
||||
None,
|
||||
Default(TokenStream2),
|
||||
Scope(LitStr),
|
||||
}
|
||||
|
||||
// #[widget(ParsedWidgetOverride::Hidden)]
|
||||
// #[widget(ParsedWidgetOverride::String = "Some string")]
|
||||
// #[widget(ParsedWidgetOverride::Custom = "Custom string")]
|
||||
#[derive(Debug, Default)]
|
||||
pub enum ParsedWidgetOverride {
|
||||
#[default]
|
||||
None,
|
||||
Hidden,
|
||||
String(LitStr),
|
||||
Custom(LitStr),
|
||||
}
|
||||
|
||||
impl Parse for ParsedWidgetOverride {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
// Parse the full path (e.g., ParsedWidgetOverride::Hidden)
|
||||
let path: Path = input.parse()?;
|
||||
|
||||
// Ensure the path starts with `ParsedWidgetOverride`
|
||||
if path.segments.len() == 2 && path.segments[0].ident == "ParsedWidgetOverride" {
|
||||
let variant = &path.segments[1].ident;
|
||||
|
||||
match variant.to_string().as_str() {
|
||||
"Hidden" => Ok(ParsedWidgetOverride::Hidden),
|
||||
"String" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
let lit: LitStr = input.parse()?;
|
||||
Ok(ParsedWidgetOverride::String(lit))
|
||||
}
|
||||
"Custom" => {
|
||||
input.parse::<syn::Token![=]>()?;
|
||||
let lit: LitStr = input.parse()?;
|
||||
Ok(ParsedWidgetOverride::Custom(lit))
|
||||
}
|
||||
_ => Err(syn::Error::new(variant.span(), "Unknown ParsedWidgetOverride variant")),
|
||||
}
|
||||
} else {
|
||||
Err(syn::Error::new(input.span(), "Expected ParsedWidgetOverride::<variant>"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ParsedField {
|
||||
Regular {
|
||||
pat_ident: PatIdent,
|
||||
name: Option<LitStr>,
|
||||
description: String,
|
||||
widget_override: ParsedWidgetOverride,
|
||||
ty: Type,
|
||||
exposed: bool,
|
||||
value_source: ValueSource,
|
||||
value_source: ParsedValueSource,
|
||||
number_min: Option<LitFloat>,
|
||||
number_max: Option<LitFloat>,
|
||||
number_mode_range: Option<ExprTuple>,
|
||||
@@ -68,6 +111,7 @@ pub(crate) enum ParsedField {
|
||||
pat_ident: PatIdent,
|
||||
name: Option<LitStr>,
|
||||
description: String,
|
||||
widget_override: ParsedWidgetOverride,
|
||||
input_type: Type,
|
||||
output_type: Type,
|
||||
implementations: Punctuated<Implementation, Comma>,
|
||||
@@ -126,6 +170,7 @@ impl Parse for NodeFnAttributes {
|
||||
let mut display_name = None;
|
||||
let mut path = None;
|
||||
let mut skip_impl = false;
|
||||
let mut properties_string = None;
|
||||
|
||||
let content = input;
|
||||
// let content;
|
||||
@@ -165,6 +210,16 @@ impl Parse for NodeFnAttributes {
|
||||
}
|
||||
skip_impl = true;
|
||||
}
|
||||
Meta::List(meta) if meta.path.is_ident("properties") => {
|
||||
if properties_string.is_some() {
|
||||
return Err(Error::new_spanned(path, "Multiple 'properties_string' attributes are not allowed"));
|
||||
}
|
||||
let parsed_properties_string: LitStr = meta
|
||||
.parse_args()
|
||||
.map_err(|_| Error::new_spanned(meta, "Expected a string for 'properties', e.g., name(\"channel_mixer_properties\")"))?;
|
||||
|
||||
properties_string = Some(parsed_properties_string);
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
meta,
|
||||
@@ -187,6 +242,7 @@ impl Parse for NodeFnAttributes {
|
||||
display_name,
|
||||
path,
|
||||
skip_impl,
|
||||
properties_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -343,13 +399,21 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `name` value for argument '{}': {}", ident, e))))
|
||||
.transpose()?;
|
||||
|
||||
let widget_override = extract_attribute(attrs, "widget")
|
||||
.map(|attr| {
|
||||
attr.parse_args()
|
||||
.map_err(|e| Error::new_spanned(attr, format!("Invalid `widget override` value for argument '{}': {}", ident, e)))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
let exposed = extract_attribute(attrs, "expose").is_some();
|
||||
|
||||
let value_source = match (default_value, scope) {
|
||||
(Some(_), Some(_)) => return Err(Error::new_spanned(&pat_ident, "Cannot have both `default` and `scope` attributes")),
|
||||
(Some(default_value), _) => ValueSource::Default(default_value),
|
||||
(_, Some(scope)) => ValueSource::Scope(scope),
|
||||
_ => ValueSource::None,
|
||||
(Some(default_value), _) => ParsedValueSource::Default(default_value),
|
||||
(_, Some(scope)) => ParsedValueSource::Scope(scope),
|
||||
_ => ParsedValueSource::None,
|
||||
};
|
||||
|
||||
let number_min = extract_attribute(attrs, "min")
|
||||
@@ -405,7 +469,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
let (input_type, output_type) = node_input_type
|
||||
.zip(node_output_type)
|
||||
.ok_or_else(|| Error::new_spanned(&ty, "Invalid Node type. Expected `impl Node<Input, Output = OutputType>`"))?;
|
||||
if !matches!(&value_source, ValueSource::None) {
|
||||
if !matches!(&value_source, ParsedValueSource::None) {
|
||||
return Err(Error::new_spanned(&ty, "No default values for `impl Node` allowed"));
|
||||
}
|
||||
let implementations = extract_attribute(attrs, "implementations")
|
||||
@@ -417,6 +481,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
pat_ident,
|
||||
name,
|
||||
description,
|
||||
widget_override,
|
||||
input_type,
|
||||
output_type,
|
||||
implementations,
|
||||
@@ -430,6 +495,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
||||
pat_ident,
|
||||
name,
|
||||
description,
|
||||
widget_override,
|
||||
exposed,
|
||||
number_min,
|
||||
number_max,
|
||||
@@ -550,11 +616,11 @@ mod tests {
|
||||
assert_eq!(p_name, e_name);
|
||||
assert_eq!(p_exp, e_exp);
|
||||
match (p_default, e_default) {
|
||||
(ValueSource::None, ValueSource::None) => {}
|
||||
(ValueSource::Default(p), ValueSource::Default(e)) => {
|
||||
(ParsedValueSource::None, ParsedValueSource::None) => {}
|
||||
(ParsedValueSource::Default(p), ParsedValueSource::Default(e)) => {
|
||||
assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());
|
||||
}
|
||||
(ValueSource::Scope(p), ValueSource::Scope(e)) => {
|
||||
(ParsedValueSource::Scope(p), ParsedValueSource::Scope(e)) => {
|
||||
assert_eq!(p.value(), e.value());
|
||||
}
|
||||
_ => panic!("Mismatched default values"),
|
||||
@@ -602,6 +668,7 @@ mod tests {
|
||||
display_name: None,
|
||||
path: Some(parse_quote!(graphene_core::TestNode)),
|
||||
skip_impl: true,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("add", Span::call_site()),
|
||||
struct_name: Ident::new("Add", Span::call_site()),
|
||||
@@ -619,9 +686,10 @@ mod tests {
|
||||
pat_ident: pat_ident("b"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
@@ -655,6 +723,7 @@ mod tests {
|
||||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("transform", Span::call_site()),
|
||||
struct_name: Ident::new("Transform", Span::call_site()),
|
||||
@@ -673,6 +742,7 @@ mod tests {
|
||||
pat_ident: pat_ident("transform_target"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
input_type: parse_quote!(Footprint),
|
||||
output_type: parse_quote!(T),
|
||||
implementations: Punctuated::new(),
|
||||
@@ -681,9 +751,10 @@ mod tests {
|
||||
pat_ident: pat_ident("translate"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(DVec2),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
@@ -715,6 +786,7 @@ mod tests {
|
||||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("circle", Span::call_site()),
|
||||
struct_name: Ident::new("Circle", Span::call_site()),
|
||||
@@ -732,9 +804,10 @@ mod tests {
|
||||
pat_ident: pat_ident("radius"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::Default(quote!(50.)),
|
||||
value_source: ParsedValueSource::Default(quote!(50.)),
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
@@ -764,6 +837,7 @@ mod tests {
|
||||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("levels", Span::call_site()),
|
||||
struct_name: Ident::new("Levels", Span::call_site()),
|
||||
@@ -781,9 +855,10 @@ mod tests {
|
||||
pat_ident: pat_ident("shadows"),
|
||||
name: None,
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
@@ -825,6 +900,7 @@ mod tests {
|
||||
display_name: None,
|
||||
path: Some(parse_quote!(graphene_core::TestNode)),
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("add", Span::call_site()),
|
||||
struct_name: Ident::new("Add", Span::call_site()),
|
||||
@@ -842,9 +918,10 @@ mod tests {
|
||||
pat_ident: pat_ident("b"),
|
||||
name: None,
|
||||
description: String::from("b"),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
ty: parse_quote!(f64),
|
||||
exposed: false,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: Some(parse_quote!(-500.)),
|
||||
number_max: Some(parse_quote!(500.)),
|
||||
number_mode_range: Some(parse_quote!((0., 100.))),
|
||||
@@ -874,6 +951,7 @@ mod tests {
|
||||
display_name: None,
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("load_image", Span::call_site()),
|
||||
struct_name: Ident::new("LoadImage", Span::call_site()),
|
||||
@@ -892,8 +970,9 @@ mod tests {
|
||||
name: None,
|
||||
ty: parse_quote!(String),
|
||||
description: String::new(),
|
||||
widget_override: ParsedWidgetOverride::None,
|
||||
exposed: true,
|
||||
value_source: ValueSource::None,
|
||||
value_source: ParsedValueSource::None,
|
||||
number_min: None,
|
||||
number_max: None,
|
||||
number_mode_range: None,
|
||||
@@ -923,6 +1002,7 @@ mod tests {
|
||||
display_name: Some(parse_quote!("CustomNode2")),
|
||||
path: None,
|
||||
skip_impl: false,
|
||||
properties_string: None,
|
||||
},
|
||||
fn_name: Ident::new("custom_node", Span::call_site()),
|
||||
struct_name: Ident::new("CustomNode", Span::call_site()),
|
||||
|
||||
Reference in New Issue
Block a user