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:
parent
ad68b1e5c8
commit
eec0ef761c
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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,26 +1459,22 @@ impl DocumentMessageHandler {
|
|||
}
|
||||
|
||||
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
||||
let document_message_handler = serde_json::from_str::<OldDocumentMessageHandler>(serialized_content)
|
||||
.map_or_else(
|
||||
|_| serde_json::from_str::<DocumentMessageHandler>(serialized_content),
|
||||
|old_message_handler| {
|
||||
let default_document_message_handler = DocumentMessageHandler {
|
||||
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
|
||||
collapsed: old_message_handler.collapsed,
|
||||
commit_hash: old_message_handler.commit_hash,
|
||||
document_ptz: old_message_handler.document_ptz,
|
||||
document_mode: old_message_handler.document_mode,
|
||||
view_mode: old_message_handler.view_mode,
|
||||
overlays_visible: old_message_handler.overlays_visible,
|
||||
rulers_visible: old_message_handler.rulers_visible,
|
||||
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
|
||||
snapping_state: old_message_handler.snapping_state,
|
||||
..Default::default()
|
||||
};
|
||||
Ok(default_document_message_handler)
|
||||
},
|
||||
)
|
||||
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
|
||||
.or_else(|_| {
|
||||
serde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {
|
||||
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
|
||||
collapsed: old_message_handler.collapsed,
|
||||
commit_hash: old_message_handler.commit_hash,
|
||||
document_ptz: old_message_handler.document_ptz,
|
||||
document_mode: old_message_handler.document_mode,
|
||||
view_mode: old_message_handler.view_mode,
|
||||
overlays_visible: old_message_handler.overlays_visible,
|
||||
rulers_visible: old_message_handler.rulers_visible,
|
||||
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
|
||||
snapping_state: old_message_handler.snapping_state,
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.map_err(|e| EditorError::DocumentDeserialization(e.to_string()))?;
|
||||
Ok(document_message_handler)
|
||||
}
|
||||
|
|
@ -2089,7 +2079,7 @@ impl DocumentMessageHandler {
|
|||
/// Create a network interface with a single export
|
||||
fn default_document_network_interface() -> NodeNetworkInterface {
|
||||
let mut network_interface = NodeNetworkInterface::default();
|
||||
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "".to_string(), &[]);
|
||||
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "", &[]);
|
||||
network_interface
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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-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;
|
||||
&.import {
|
||||
right: calc(100% - var(--offset-left) * 24px);
|
||||
}
|
||||
|
||||
&.export {
|
||||
left: calc(var(--offset-left) * 24px + 17px);
|
||||
}
|
||||
|
||||
.import-text {
|
||||
text-align: right;
|
||||
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()),
|
||||
|
|
|
|||
Loading…
Reference in New Issue