mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 12:48:11 +08:00
Polish user-created subgraph nodes: imports in the Properties panel; reorder/delete/rename imports/exports (#2105)
* Remove imports/exports * WIP: Autogenerated properties * WIP: Input based properties * WIP: Hashmap based input overrides * Migrate noise pattern node to input properties * Reorder exports * Continue migrating properties * WIP: Improve reorder exports * Automatically populate all input properties for sub networks * Complete reorder import and export * Add widget override to node macro * Migrate assign colors to input based properties * WIP: Full node property override * Node based properties override for proto nodes * Migrate all node properties to be input based * Rename imports/exports * improve UI * Protonode input valid implementations * Valid type list * Small formatting fixes * Polishing small issues * Document upgrade * fix tests * Upgrade noise pattern node * remove console log * Fix upgrade script for Noise Pattern * Improve the Properties panel representation for graphical data * Re-export demo art * Code review * code review improvements * Cleanup for node properties overrides * Reexport demo art * Fix clippy lints --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
ad68b1e5c8
commit
eec0ef761c
Generated
+172
-178
File diff suppressed because it is too large
Load Diff
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -146,6 +146,14 @@ pub enum FrontendMessage {
|
|||||||
UpdateGraphViewOverlay {
|
UpdateGraphViewOverlay {
|
||||||
open: bool,
|
open: bool,
|
||||||
},
|
},
|
||||||
|
UpdateImportReorderIndex {
|
||||||
|
#[serde(rename = "importIndex")]
|
||||||
|
index: Option<usize>,
|
||||||
|
},
|
||||||
|
UpdateExportReorderIndex {
|
||||||
|
#[serde(rename = "exportIndex")]
|
||||||
|
index: Option<usize>,
|
||||||
|
},
|
||||||
UpdateLayerWidths {
|
UpdateLayerWidths {
|
||||||
#[serde(rename = "layerWidths")]
|
#[serde(rename = "layerWidths")]
|
||||||
layer_widths: HashMap<NodeId, u32>,
|
layer_widths: HashMap<NodeId, u32>,
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
}
|
}
|
||||||
DocumentMessage::PropertiesPanel(message) => {
|
DocumentMessage::PropertiesPanel(message) => {
|
||||||
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
|
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,
|
selection_network_path: &self.selection_network_path,
|
||||||
document_name: self.name.as_str(),
|
document_name: self.name.as_str(),
|
||||||
executor,
|
executor,
|
||||||
@@ -391,6 +391,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
||||||
|
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||||
}
|
}
|
||||||
DocumentMessage::Escape => {
|
DocumentMessage::Escape => {
|
||||||
if self.node_graph_handler.drag_start.is_some() {
|
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(PropertiesPanelMessage::Refresh);
|
||||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||||
responses.add(NodeGraphMessage::UpdateInSelectedNetwork)
|
responses.add(NodeGraphMessage::UpdateInSelectedNetwork);
|
||||||
}
|
}
|
||||||
DocumentMessage::SetBlendModeForSelectedLayers { blend_mode } => {
|
DocumentMessage::SetBlendModeForSelectedLayers { blend_mode } => {
|
||||||
for layer in self.network_interface.selected_nodes(&[]).unwrap().selected_layers_except_artboards(&self.network_interface) {
|
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;
|
self.graph_fade_artwork_percentage = percentage;
|
||||||
responses.add(FrontendMessage::UpdateGraphFadeArtwork { percentage });
|
responses.add(FrontendMessage::UpdateGraphFadeArtwork { percentage });
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentMessage::SetNodePinned { node_id, pinned } => {
|
DocumentMessage::SetNodePinned { node_id, pinned } => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
responses.add(NodeGraphMessage::SetPinned { node_id, pinned });
|
responses.add(NodeGraphMessage::SetPinned { node_id, pinned });
|
||||||
@@ -1224,21 +1226,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
.navigation_handler
|
.navigation_handler
|
||||||
.calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz);
|
.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);
|
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::RenderRulers);
|
||||||
responses.add(DocumentMessage::RenderScrollbars);
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
responses.add(NodeGraphMessage::UpdateEdges);
|
responses.add(NodeGraphMessage::UpdateEdges);
|
||||||
responses.add(NodeGraphMessage::UpdateBoxSelection);
|
responses.add(NodeGraphMessage::UpdateBoxSelection);
|
||||||
responses.add(FrontendMessage::UpdateImportsExports {
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
imports,
|
|
||||||
exports,
|
|
||||||
add_import,
|
|
||||||
add_export,
|
|
||||||
});
|
|
||||||
responses.add(FrontendMessage::UpdateNodeGraphTransform {
|
responses.add(FrontendMessage::UpdateNodeGraphTransform {
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
scale: transform.matrix2.x_axis.x,
|
scale: transform.matrix2.x_axis.x,
|
||||||
@@ -1465,26 +1459,22 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
||||||
let document_message_handler = serde_json::from_str::<OldDocumentMessageHandler>(serialized_content)
|
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
|
||||||
.map_or_else(
|
.or_else(|_| {
|
||||||
|_| serde_json::from_str::<DocumentMessageHandler>(serialized_content),
|
serde_json::from_str::<OldDocumentMessageHandler>(serialized_content).map(|old_message_handler| DocumentMessageHandler {
|
||||||
|old_message_handler| {
|
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
|
||||||
let default_document_message_handler = DocumentMessageHandler {
|
collapsed: old_message_handler.collapsed,
|
||||||
network_interface: NodeNetworkInterface::from_old_network(old_message_handler.network),
|
commit_hash: old_message_handler.commit_hash,
|
||||||
collapsed: old_message_handler.collapsed,
|
document_ptz: old_message_handler.document_ptz,
|
||||||
commit_hash: old_message_handler.commit_hash,
|
document_mode: old_message_handler.document_mode,
|
||||||
document_ptz: old_message_handler.document_ptz,
|
view_mode: old_message_handler.view_mode,
|
||||||
document_mode: old_message_handler.document_mode,
|
overlays_visible: old_message_handler.overlays_visible,
|
||||||
view_mode: old_message_handler.view_mode,
|
rulers_visible: old_message_handler.rulers_visible,
|
||||||
overlays_visible: old_message_handler.overlays_visible,
|
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
|
||||||
rulers_visible: old_message_handler.rulers_visible,
|
snapping_state: old_message_handler.snapping_state,
|
||||||
graph_view_overlay_open: old_message_handler.graph_view_overlay_open,
|
..Default::default()
|
||||||
snapping_state: old_message_handler.snapping_state,
|
})
|
||||||
..Default::default()
|
})
|
||||||
};
|
|
||||||
Ok(default_document_message_handler)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|e| EditorError::DocumentDeserialization(e.to_string()))?;
|
.map_err(|e| EditorError::DocumentDeserialization(e.to_string()))?;
|
||||||
Ok(document_message_handler)
|
Ok(document_message_handler)
|
||||||
}
|
}
|
||||||
@@ -2089,7 +2079,7 @@ impl DocumentMessageHandler {
|
|||||||
/// Create a network interface with a single export
|
/// Create a network interface with a single export
|
||||||
fn default_document_network_interface() -> NodeNetworkInterface {
|
fn default_document_network_interface() -> NodeNetworkInterface {
|
||||||
let mut network_interface = NodeNetworkInterface::default();
|
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
|
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 transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
|
||||||
let image = resolve_document_node_type("Image")
|
let image = resolve_document_node_type("Image")
|
||||||
.expect("Image node does not exist")
|
.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();
|
let image_id = NodeId::new();
|
||||||
self.network_interface.insert_node(image_id, image, &[]);
|
self.network_interface.insert_node(image_id, image, &[]);
|
||||||
@@ -256,7 +256,11 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
let mut existing_node_id = None;
|
let mut existing_node_id = None;
|
||||||
for upstream_node in upstream.collect::<Vec<_>>() {
|
for upstream_node in upstream.collect::<Vec<_>>() {
|
||||||
// Check if this is the node we have been searching for.
|
// 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);
|
existing_node_id = Some(upstream_node);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
use super::utility_types::Direction;
|
use super::utility_types::Direction;
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
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::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 crate::messages::prelude::*;
|
||||||
|
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
@@ -58,8 +58,7 @@ pub enum NodeGraphMessage {
|
|||||||
EnterNestedNetwork,
|
EnterNestedNetwork,
|
||||||
DuplicateSelectedNodes,
|
DuplicateSelectedNodes,
|
||||||
ExposeInput {
|
ExposeInput {
|
||||||
node_id: NodeId,
|
input_connector: InputConnector,
|
||||||
input_index: usize,
|
|
||||||
new_exposed: bool,
|
new_exposed: bool,
|
||||||
},
|
},
|
||||||
InsertNode {
|
InsertNode {
|
||||||
@@ -98,6 +97,20 @@ pub enum NodeGraphMessage {
|
|||||||
shift: Key,
|
shift: Key,
|
||||||
},
|
},
|
||||||
PrintSelectedNodeCoordinates,
|
PrintSelectedNodeCoordinates,
|
||||||
|
RemoveImport {
|
||||||
|
import_index: usize,
|
||||||
|
},
|
||||||
|
RemoveExport {
|
||||||
|
export_index: usize,
|
||||||
|
},
|
||||||
|
ReorderImport {
|
||||||
|
start_index: usize,
|
||||||
|
end_index: usize,
|
||||||
|
},
|
||||||
|
ReorderExport {
|
||||||
|
start_index: usize,
|
||||||
|
end_index: usize,
|
||||||
|
},
|
||||||
RunDocumentGraph,
|
RunDocumentGraph,
|
||||||
ForceRunDocumentGraph,
|
ForceRunDocumentGraph,
|
||||||
SelectedNodesAdd {
|
SelectedNodesAdd {
|
||||||
@@ -153,6 +166,14 @@ pub enum NodeGraphMessage {
|
|||||||
TogglePreviewImpl {
|
TogglePreviewImpl {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
},
|
},
|
||||||
|
SetImportExportName {
|
||||||
|
name: String,
|
||||||
|
index: ImportOrExport,
|
||||||
|
},
|
||||||
|
SetImportExportNameImpl {
|
||||||
|
name: String,
|
||||||
|
index: ImportOrExport,
|
||||||
|
},
|
||||||
ToggleSelectedAsLayersOrNodes,
|
ToggleSelectedAsLayersOrNodes,
|
||||||
ToggleSelectedLocked,
|
ToggleSelectedLocked,
|
||||||
ToggleLocked {
|
ToggleLocked {
|
||||||
@@ -180,6 +201,7 @@ pub enum NodeGraphMessage {
|
|||||||
},
|
},
|
||||||
UpdateEdges,
|
UpdateEdges,
|
||||||
UpdateBoxSelection,
|
UpdateBoxSelection,
|
||||||
|
UpdateImportsExports,
|
||||||
UpdateLayerPanel,
|
UpdateLayerPanel,
|
||||||
UpdateNewNodeGraph,
|
UpdateNewNodeGraph,
|
||||||
UpdateTypes {
|
UpdateTypes {
|
||||||
|
|||||||
@@ -71,6 +71,12 @@ pub struct NodeGraphMessageHandler {
|
|||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
/// The node to preview on mouse up if alt-clicked
|
/// The node to preview on mouse up if alt-clicked
|
||||||
preview_on_mouse_up: Option<NodeId>,
|
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.
|
/// 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] });
|
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::AddImport => {
|
||||||
NodeGraphMessage::AddExport => network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, String::new(), breadcrumb_network_path),
|
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 => {
|
NodeGraphMessage::Init => {
|
||||||
responses.add(BroadcastMessage::SubscribeEvent {
|
responses.add(BroadcastMessage::SubscribeEvent {
|
||||||
on: BroadcastEvent::SelectionChanged,
|
on: BroadcastEvent::SelectionChanged,
|
||||||
@@ -298,35 +310,29 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(network) = network_interface.network(selection_network_path) else {
|
if let Some(DocumentNodeImplementation::Network(_)) = network_interface.implementation(&node_id, selection_network_path) {
|
||||||
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 {
|
|
||||||
responses.add(DocumentMessage::EnterNestedNetwork { node_id });
|
responses.add(DocumentMessage::EnterNestedNetwork { node_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
NodeGraphMessage::ExposeInput { input_connector, new_exposed } => {
|
||||||
let Some(network) = network_interface.network(selection_network_path) else {
|
let InputConnector::Node { node_id, input_index } = input_connector else {
|
||||||
|
log::error!("Cannot expose/hide export");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let Some(node) = network_interface.document_node(&node_id, selection_network_path) else {
|
||||||
let Some(node) = network.nodes.get(&node_id) else {
|
|
||||||
log::error!("Could not find node {node_id} in NodeGraphMessage::ExposeInput");
|
log::error!("Could not find node {node_id} in NodeGraphMessage::ExposeInput");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(mut input) = node.inputs.get(input_index).cloned() else {
|
let Some(mut input) = node.inputs.get(input_index).cloned() else {
|
||||||
log::error!("Could not find input {input_index} in NodeGraphMessage::ExposeInput");
|
log::error!("Could not find input {input_index} in NodeGraphMessage::ExposeInput");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let NodeInput::Value { exposed, .. } = &mut input {
|
if let NodeInput::Value { exposed, .. } = &mut input {
|
||||||
*exposed = new_exposed;
|
*exposed = new_exposed;
|
||||||
} else {
|
} else if !new_exposed {
|
||||||
// TODO: Should network and node inputs be able to be hidden?
|
// If hiding an input that is not a value, then disconnect it. This will convert it to a value input.
|
||||||
log::error!("Could not hide/show input: {:?} since it is not NodeInput::Value", input);
|
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
|
||||||
|
responses.add(NodeGraphMessage::ExposeInput { input_connector, new_exposed });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,6 +342,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
input_connector: InputConnector::node(node_id, input_index),
|
input_connector: InputConnector::node(node_id, input_index),
|
||||||
input,
|
input,
|
||||||
});
|
});
|
||||||
|
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
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 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
|
if network_interface
|
||||||
.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Grip, selection_network_path)
|
.layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Grip, selection_network_path)
|
||||||
.is_some()
|
.is_some()
|
||||||
@@ -651,6 +641,37 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
return;
|
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
|
self.selection_before_pointer_down = network_interface
|
||||||
.selected_nodes(selection_network_path)
|
.selected_nodes(selection_network_path)
|
||||||
.map(|selected_nodes| selected_nodes.selected_nodes().cloned().collect())
|
.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 });
|
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: true });
|
||||||
} else if self.box_selection_start.is_some() {
|
} else if self.box_selection_start.is_some() {
|
||||||
responses.add(NodeGraphMessage::UpdateBoxSelection);
|
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 => {
|
NodeGraphMessage::PointerUp => {
|
||||||
@@ -1129,15 +1190,34 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
}
|
}
|
||||||
self.select_if_not_dragged = None;
|
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.drag_start = None;
|
||||||
self.begin_dragging = false;
|
self.begin_dragging = false;
|
||||||
self.box_selection_start = None;
|
self.box_selection_start = None;
|
||||||
self.wire_in_progress_from_connector = None;
|
self.wire_in_progress_from_connector = None;
|
||||||
self.wire_in_progress_to_connector = None;
|
self.wire_in_progress_to_connector = None;
|
||||||
|
self.reordering_export = None;
|
||||||
|
self.reordering_import = None;
|
||||||
responses.add(DocumentMessage::EndTransaction);
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
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 } => {
|
NodeGraphMessage::PointerOutsideViewport { shift } => {
|
||||||
if self.drag_start.is_some() || self.box_selection_start.is_some() {
|
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 => {
|
NodeGraphMessage::RunDocumentGraph => {
|
||||||
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
|
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 wires = Self::collect_wires(network_interface, breadcrumb_network_path);
|
||||||
let nodes = self.collect_nodes(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 (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();
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
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(FrontendMessage::UpdateNodeGraph { nodes, wires });
|
responses.add(FrontendMessage::UpdateNodeGraph { nodes, wires });
|
||||||
responses.add(FrontendMessage::UpdateLayerWidths {
|
responses.add(FrontendMessage::UpdateLayerWidths {
|
||||||
layer_widths,
|
layer_widths,
|
||||||
@@ -1253,16 +1344,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
if graph_view_overlay_open {
|
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);
|
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
|
// Send the new edges to the frontend
|
||||||
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
|
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
|
||||||
@@ -1272,7 +1354,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
input,
|
input,
|
||||||
});
|
});
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
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)
|
&& network_interface.connected_to_output(&node_id, selection_network_path)
|
||||||
{
|
{
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
@@ -1377,6 +1462,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
|
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
|
||||||
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
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 } => {
|
NodeGraphMessage::TogglePreview { node_id } => {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::TogglePreviewImpl { node_id });
|
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<_>>();
|
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.
|
// If any of the selected nodes are pinned, unpin them all. Otherwise, pin them all.
|
||||||
let pinned = !node_ids.iter().all(|node_id| {
|
let pinned = !node_ids.iter().all(|node_id| network_interface.is_pinned(node_id, breadcrumb_network_path));
|
||||||
if let Some(node) = network_interface.node_metadata(node_id, breadcrumb_network_path) {
|
|
||||||
node.persistent_metadata.pinned
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
for node_id in &node_ids {
|
for node_id in &node_ids {
|
||||||
@@ -1442,9 +1528,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleSelectedVisibility => {
|
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 {
|
let Some(selected_nodes) = network_interface.selected_nodes(selection_network_path) else {
|
||||||
log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedLocked");
|
log::error!("Could not get selected nodes in NodeGraphMessage::ToggleSelectedLocked");
|
||||||
return;
|
return;
|
||||||
@@ -1452,7 +1535,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
let node_ids = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();
|
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.
|
// 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);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
for node_id in &node_ids {
|
for node_id in &node_ids {
|
||||||
@@ -1461,16 +1544,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
|
responses.add(NodeGraphMessage::SetLockedOrVisibilitySideEffects { node_ids });
|
||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleVisibility { node_id } => {
|
NodeGraphMessage::ToggleVisibility { node_id } => {
|
||||||
let Some(network) = network_interface.network(selection_network_path) else {
|
let visible = !network_interface.is_visible(&node_id, selection_network_path);
|
||||||
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;
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
|
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 })
|
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 => {
|
NodeGraphMessage::UpdateLayerPanel => {
|
||||||
Self::update_layer_panel(network_interface, selection_network_path, collapsed, responses);
|
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 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_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_locked = network_interface.selected_nodes(&[]).unwrap().selected_unlocked_layers(network_interface).count() == 0;
|
||||||
let selection_all_visible = selected_nodes.selected_nodes().all(|id| {
|
let selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path));
|
||||||
if let Some(node) = network.nodes.get(id) {
|
|
||||||
node.visible
|
|
||||||
} else {
|
|
||||||
error!("Could not get node {id} in update_selection_action_buttons");
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
@@ -1839,10 +1932,6 @@ impl NodeGraphMessageHandler {
|
|||||||
/// Collate the properties panel sections for a node graph
|
/// Collate the properties panel sections for a node graph
|
||||||
pub fn collate_properties(context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
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
|
// 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 {
|
let Some(selected_nodes) = context.network_interface.selected_nodes(context.selection_network_path) else {
|
||||||
warn!("No selected nodes in collate_properties");
|
warn!("No selected nodes in collate_properties");
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@@ -1868,25 +1957,13 @@ impl NodeGraphMessageHandler {
|
|||||||
match layers.len() {
|
match layers.len() {
|
||||||
// If no layers are selected, show properties for all selected nodes
|
// If no layers are selected, show properties for all selected nodes
|
||||||
0 => {
|
0 => {
|
||||||
let selected_nodes = nodes
|
let selected_nodes = nodes.iter().map(|node_id| node_properties::generate_node_properties(*node_id, context)).collect::<Vec<_>>();
|
||||||
.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<_>>();
|
|
||||||
if !selected_nodes.is_empty() {
|
if !selected_nodes.is_empty() {
|
||||||
return selected_nodes;
|
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 {
|
let mut properties = vec![LayoutGroup::Row {
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
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
|
// And if no nodes are selected, show properties for all pinned nodes
|
||||||
let pinned_node_properties = network
|
let pinned_node_properties = network
|
||||||
.nodes
|
.nodes
|
||||||
|
.keys()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(node_id, node)| {
|
.filter_map(|node_id| {
|
||||||
let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) {
|
if context.network_interface.is_pinned(node_id, context.selection_network_path) {
|
||||||
node.persistent_metadata.pinned
|
Some(node_properties::generate_node_properties(*node_id, context))
|
||||||
} 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))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -1983,17 +2060,9 @@ impl NodeGraphMessageHandler {
|
|||||||
!context.network_interface.is_layer(node_id, context.selection_network_path)
|
!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)))
|
.collect::<Vec<_>>()
|
||||||
.map(|(node, node_id)| {
|
.iter()
|
||||||
let pinned = if let Some(node) = context.network_interface.node_metadata(&node_id, context.selection_network_path) {
|
.map(|(_, node_id)| node_properties::generate_node_properties(*node_id, context))
|
||||||
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<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
layer_properties.extend(node_properties);
|
layer_properties.extend(node_properties);
|
||||||
@@ -2110,8 +2179,9 @@ impl NodeGraphMessageHandler {
|
|||||||
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
||||||
let mut inputs = inputs.into_iter().map(|input| {
|
let mut inputs = inputs.into_iter().map(|input| {
|
||||||
input.map(|input| FrontendGraphInput {
|
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)),
|
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()),
|
name: input.name.unwrap_or_else(|| input.ty.nested_type().to_string()),
|
||||||
connected_to: input.output_connector,
|
connected_to: input.output_connector,
|
||||||
})
|
})
|
||||||
@@ -2122,8 +2192,8 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
|
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
|
||||||
let primary_output_type = output_types.first().cloned().flatten();
|
let primary_output_type = output_types.first().cloned().flatten();
|
||||||
let frontend_data_type = if let Some((output_type, _)) = &primary_output_type {
|
let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type {
|
||||||
FrontendGraphDataType::with_type(output_type)
|
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||||
} else {
|
} else {
|
||||||
FrontendGraphDataType::General
|
FrontendGraphDataType::General
|
||||||
};
|
};
|
||||||
@@ -2144,8 +2214,8 @@ impl NodeGraphMessageHandler {
|
|||||||
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let frontend_data_type = if let Some((output_type, _)) = &exposed_output {
|
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output {
|
||||||
FrontendGraphDataType::with_type(output_type)
|
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||||
} else {
|
} else {
|
||||||
FrontendGraphDataType::General
|
FrontendGraphDataType::General
|
||||||
};
|
};
|
||||||
@@ -2203,7 +2273,7 @@ impl NodeGraphMessageHandler {
|
|||||||
.node_metadata(&node_id, breadcrumb_network_path)
|
.node_metadata(&node_id, breadcrumb_network_path)
|
||||||
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),
|
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),
|
||||||
can_be_layer: can_be_layer_lookup.contains(&node_id),
|
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),
|
display_name: network_interface.frontend_display_name(&node_id, breadcrumb_network_path),
|
||||||
primary_input,
|
primary_input,
|
||||||
exposed_inputs,
|
exposed_inputs,
|
||||||
@@ -2273,7 +2343,7 @@ impl NodeGraphMessageHandler {
|
|||||||
|| (
|
|| (
|
||||||
// Check if the last node in the chain has an exposed left input
|
// 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.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, &[]) {
|
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())
|
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||||
} else {
|
} else {
|
||||||
@@ -2284,7 +2354,7 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
let parents_visible = layer.ancestors(network_interface.document_metadata()).filter(|&ancestor| ancestor != layer).all(|layer| {
|
let parents_visible = layer.ancestors(network_interface.document_metadata()).filter(|&ancestor| ancestor != layer).all(|layer| {
|
||||||
if layer != LayerNodeIdentifier::ROOT_PARENT {
|
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 {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -2389,6 +2459,7 @@ struct InputLookup {
|
|||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
ty: Type,
|
ty: Type,
|
||||||
type_source: TypeSource,
|
type_source: TypeSource,
|
||||||
|
valid_types: Vec<Type>,
|
||||||
output_connector: Option<OutputConnector>,
|
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`)
|
// 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)
|
// Get the output connector that feeds into this input (done here as well for simplicity)
|
||||||
let connector = OutputConnector::from_input(input);
|
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() {
|
for (index, value) in value.iter_mut().enumerate() {
|
||||||
// Skip not exposed inputs for efficiency
|
// Skip not exposed inputs for efficiency
|
||||||
let Some(value) = value else { continue };
|
let Some(value) = value else { continue };
|
||||||
|
|
||||||
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
|
// 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);
|
let (ty, type_source) = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path);
|
||||||
value.ty = ty;
|
value.ty = ty;
|
||||||
value.type_source = type_source;
|
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
|
frontend_inputs_lookup
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2462,6 +2543,9 @@ impl Default for NodeGraphMessageHandler {
|
|||||||
deselect_on_pointer_up: None,
|
deselect_on_pointer_up: None,
|
||||||
auto_panning: Default::default(),
|
auto_panning: Default::default(),
|
||||||
preview_on_mouse_up: None,
|
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 graph_craft::document::NodeId;
|
||||||
use graphene_core::Type;
|
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)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub enum FrontendGraphDataType {
|
pub enum FrontendGraphDataType {
|
||||||
@@ -11,12 +11,12 @@ pub enum FrontendGraphDataType {
|
|||||||
Raster,
|
Raster,
|
||||||
VectorData,
|
VectorData,
|
||||||
Number,
|
Number,
|
||||||
Graphic,
|
Group,
|
||||||
Artboard,
|
Artboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrontendGraphDataType {
|
impl FrontendGraphDataType {
|
||||||
pub fn with_type(input: &Type) -> Self {
|
fn with_type(input: &Type) -> Self {
|
||||||
match TaggedValue::from_type_or_none(input) {
|
match TaggedValue::from_type_or_none(input) {
|
||||||
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
|
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
|
||||||
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||||
@@ -30,11 +30,18 @@ impl FrontendGraphDataType {
|
|||||||
| TaggedValue::F64Array4(_)
|
| TaggedValue::F64Array4(_)
|
||||||
| TaggedValue::VecF64(_)
|
| TaggedValue::VecF64(_)
|
||||||
| TaggedValue::VecDVec2(_) => Self::Number,
|
| TaggedValue::VecDVec2(_) => Self::Number,
|
||||||
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Graphic,
|
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group,
|
||||||
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
||||||
_ => Self::General,
|
_ => 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)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
@@ -44,6 +51,8 @@ pub struct FrontendGraphInput {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(rename = "resolvedType")]
|
#[serde(rename = "resolvedType")]
|
||||||
pub resolved_type: Option<String>,
|
pub resolved_type: Option<String>,
|
||||||
|
#[serde(rename = "validTypes")]
|
||||||
|
pub valid_types: Vec<String>,
|
||||||
#[serde(rename = "connectedTo")]
|
#[serde(rename = "connectedTo")]
|
||||||
pub connected_to: Option<OutputConnector>,
|
pub connected_to: Option<OutputConnector>,
|
||||||
}
|
}
|
||||||
@@ -180,6 +189,8 @@ pub struct FrontendClickTargets {
|
|||||||
pub all_nodes_bounding_box: String,
|
pub all_nodes_bounding_box: String,
|
||||||
#[serde(rename = "importExportsBoundingBox")]
|
#[serde(rename = "importExportsBoundingBox")]
|
||||||
pub import_exports_bounding_box: String,
|
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)]
|
#[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;
|
use crate::node_graph_executor::NodeGraphExecutor;
|
||||||
|
|
||||||
pub struct PropertiesPanelMessageHandlerData<'a> {
|
pub struct PropertiesPanelMessageHandlerData<'a> {
|
||||||
pub network_interface: &'a NodeNetworkInterface,
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
pub selection_network_path: &'a [NodeId],
|
pub selection_network_path: &'a [NodeId],
|
||||||
pub document_name: &'a str,
|
pub document_name: &'a str,
|
||||||
pub executor: &'a mut NodeGraphExecutor,
|
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,
|
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
|
// 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 upgrade_vector_manipulation_format = document_serialized_content.contains("ManipulatorGroupIds") && !document_name.contains("__DO_NOT_UPGRADE__");
|
||||||
let document_name = document_name.replace("__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
|
// TODO: Eventually remove this document upgrade code
|
||||||
// Upgrade all old nodes to support editable subgraphs introduced in #1750
|
// 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.
|
// 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
|
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
|
||||||
for node_id in &document
|
for node_id in &document
|
||||||
@@ -614,6 +614,31 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||||||
.network_interface
|
.network_interface
|
||||||
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
|
.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
|
// 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
|
/// 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> {
|
pub fn upstream_node_id_from_name(&self, node_name: &str) -> Option<NodeId> {
|
||||||
self.horizontal_layer_flow()
|
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.
|
/// 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()
|
self.horizontal_layer_flow()
|
||||||
.skip(1)// Skip self
|
.skip(1)// Skip self
|
||||||
.take_while(|node_id| !self.network_interface.is_layer(node_id,&[]))
|
.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))
|
.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 {
|
let Some(reference) = document.network_interface.reference(&node_id, &[]) else {
|
||||||
continue;
|
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 points_input = node.inputs.get(2)?;
|
||||||
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else {
|
let Some(TaggedValue::BrushStrokes(strokes)) = points_input.as_value() else {
|
||||||
continue;
|
continue;
|
||||||
@@ -285,7 +285,7 @@ impl BrushToolData {
|
|||||||
self.strokes.clone_from(strokes);
|
self.strokes.clone_from(strokes);
|
||||||
|
|
||||||
return Some(layer);
|
return Some(layer);
|
||||||
} else if reference == "Transform" {
|
} else if *reference == Some("Transform".to_string()) {
|
||||||
let upstream = document.metadata().upstream_transform(node_id);
|
let upstream = document.metadata().upstream_transform(node_id);
|
||||||
let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
|
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;
|
self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
|
||||||
|
|||||||
@@ -793,7 +793,10 @@ impl Fsm for PenToolFsmState {
|
|||||||
(_, PenToolMessage::Redo) => {
|
(_, PenToolMessage::Redo) => {
|
||||||
tool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1));
|
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.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,
|
_ => self,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,8 +123,8 @@
|
|||||||
--color-data-vectordata-dim: #4b778c;
|
--color-data-vectordata-dim: #4b778c;
|
||||||
--color-data-number: #cbbab4;
|
--color-data-number: #cbbab4;
|
||||||
--color-data-number-dim: #87736b;
|
--color-data-number-dim: #87736b;
|
||||||
--color-data-graphic: #6b84e8;
|
--color-data-group: #6b84e8;
|
||||||
--color-data-graphic-dim: #4a557b;
|
--color-data-group-dim: #4a557b;
|
||||||
--color-data-artboard: #70a898;
|
--color-data-artboard: #70a898;
|
||||||
--color-data-artboard-dim: #3a6156;
|
--color-data-artboard-dim: #3a6156;
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,69 @@
|
|||||||
|
|
||||||
$: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths);
|
$: 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 {
|
function calculateGridSpacing(scale: number): number {
|
||||||
const dense = scale * GRID_SIZE;
|
const dense = scale * GRID_SIZE;
|
||||||
let sparse = dense;
|
let sparse = dense;
|
||||||
@@ -267,6 +330,10 @@
|
|||||||
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${value.dataType}`;
|
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 {
|
function outputConnectedToText(output: FrontendGraphOutput): string {
|
||||||
if (output.connectedTo.length === 0) {
|
if (output.connectedTo.length === 0) {
|
||||||
return "Connected to nothing";
|
return "Connected to nothing";
|
||||||
@@ -396,6 +463,9 @@
|
|||||||
{/each}
|
{/each}
|
||||||
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.allNodesBoundingBox} />
|
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.allNodesBoundingBox} />
|
||||||
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.importExportsBoundingBox} />
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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)" />
|
<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}
|
{/if}
|
||||||
</svg>
|
</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}
|
{/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}
|
{#if $nodeGraph.addImport !== undefined}
|
||||||
<div class="plus" style:--offset-left={$nodeGraph.addImport.x / 24} style:--offset-top={$nodeGraph.addImport.y / 24}>
|
<div class="plus" style:--offset-left={$nodeGraph.addImport.x / 24} style:--offset-top={$nodeGraph.addImport.y / 24}>
|
||||||
<IconButton
|
<IconButton
|
||||||
class={"visibility"}
|
|
||||||
data-visibility-button
|
|
||||||
size={24}
|
size={24}
|
||||||
icon={"Add"}
|
icon="Add"
|
||||||
action={() => {
|
action={() => {
|
||||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
/* 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)" />
|
<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}
|
{/if}
|
||||||
</svg>
|
</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}
|
{/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}
|
{#if $nodeGraph.addExport !== undefined}
|
||||||
<div class="plus" style:--offset-left={$nodeGraph.addExport.x / 24} style:--offset-top={$nodeGraph.addExport.y / 24}>
|
<div class="plus" style:--offset-left={$nodeGraph.addExport.x / 24} style:--offset-top={$nodeGraph.addExport.y / 24}>
|
||||||
<IconButton
|
<IconButton
|
||||||
class={"visibility"}
|
|
||||||
data-visibility-button
|
|
||||||
size={24}
|
size={24}
|
||||||
icon={"Add"}
|
icon={"Add"}
|
||||||
action={() => {
|
action={() => {
|
||||||
@@ -566,7 +712,7 @@
|
|||||||
bind:this={inputs[nodeIndex + 1][0]}
|
bind:this={inputs[nodeIndex + 1][0]}
|
||||||
>
|
>
|
||||||
{#if node.primaryInput}
|
{#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}
|
||||||
{#if node.primaryInput?.connectedTo !== undefined}
|
{#if node.primaryInput?.connectedTo !== undefined}
|
||||||
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
|
<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)`}
|
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={inputs[nodeIndex + 1][1]}
|
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}
|
{#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)" />
|
<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}
|
{:else}
|
||||||
@@ -698,7 +844,7 @@
|
|||||||
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={inputs[nodeIndex + 1][0]}
|
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}
|
{#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)" />
|
<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}
|
{:else}
|
||||||
@@ -718,7 +864,7 @@
|
|||||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||||
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
|
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}
|
{#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)" />
|
<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}
|
{:else}
|
||||||
@@ -880,6 +1026,10 @@
|
|||||||
.all-nodes-bounding-box {
|
.all-nodes-bounding-box {
|
||||||
stroke: purple;
|
stroke: purple;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modify-import-export {
|
||||||
|
stroke: orange;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,30 +1068,66 @@
|
|||||||
left: calc(var(--offset-left) * 24px);
|
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 {
|
.plus {
|
||||||
margin-top: -4px;
|
|
||||||
margin-left: -4px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(var(--offset-top) * 24px);
|
top: calc(var(--offset-top) * 24px);
|
||||||
left: calc(var(--offset-left) * 24px);
|
left: calc(var(--offset-left) * 24px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.export-text {
|
.edit-import-export {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 0;
|
display: flex;
|
||||||
margin-left: 20px;
|
align-items: center;
|
||||||
top: calc(var(--offset-top) * 24px);
|
top: calc(var(--offset-top) * 24px);
|
||||||
left: calc(var(--offset-left) * 24px);
|
margin-top: -5px;
|
||||||
}
|
height: 24px;
|
||||||
|
|
||||||
.import-text {
|
&.import {
|
||||||
position: absolute;
|
right: calc(100% - var(--offset-left) * 24px);
|
||||||
text-align: right;
|
}
|
||||||
top: calc(var(--offset-top) * 24px);
|
|
||||||
left: calc(var(--offset-left) * 24px);
|
&.export {
|
||||||
margin-top: 0;
|
left: calc(var(--offset-left) * 24px + 17px);
|
||||||
margin-left: calc(-100px - 2px);
|
}
|
||||||
width: 100px;
|
|
||||||
|
.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;
|
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 LayerWidths = Transform(({ obj }) => obj.layerWidths);
|
||||||
const ChainWidths = Transform(({ obj }) => obj.chainWidths);
|
const ChainWidths = Transform(({ obj }) => obj.chainWidths);
|
||||||
const HasLeftInputWire = Transform(({ obj }) => obj.hasLeftInputWire);
|
const HasLeftInputWire = Transform(({ obj }) => obj.hasLeftInputWire);
|
||||||
@@ -170,6 +178,7 @@ export type FrontendClickTargets = {
|
|||||||
readonly iconClickTargets: string[];
|
readonly iconClickTargets: string[];
|
||||||
readonly allNodesBoundingBox: string;
|
readonly allNodesBoundingBox: string;
|
||||||
readonly importExportsBoundingBox: string;
|
readonly importExportsBoundingBox: string;
|
||||||
|
readonly modifyImportExport: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextMenuInformation = {
|
export type ContextMenuInformation = {
|
||||||
@@ -178,7 +187,7 @@ export type ContextMenuInformation = {
|
|||||||
contextMenuData: "CreateNode" | { nodeId: bigint; currentlyIsNode: boolean };
|
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 {
|
export class Node {
|
||||||
readonly index!: bigint;
|
readonly index!: bigint;
|
||||||
@@ -210,6 +219,8 @@ export class FrontendGraphInput {
|
|||||||
|
|
||||||
readonly resolvedType!: string | undefined;
|
readonly resolvedType!: string | undefined;
|
||||||
|
|
||||||
|
readonly validTypes!: string[];
|
||||||
|
|
||||||
@CreateOutputConnectorOptional
|
@CreateOutputConnectorOptional
|
||||||
connectedTo!: Node | undefined;
|
connectedTo!: Node | undefined;
|
||||||
}
|
}
|
||||||
@@ -1592,6 +1603,8 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateImportsExports,
|
UpdateImportsExports,
|
||||||
UpdateInputHints,
|
UpdateInputHints,
|
||||||
UpdateInSelectedNetwork,
|
UpdateInSelectedNetwork,
|
||||||
|
UpdateExportReorderIndex,
|
||||||
|
UpdateImportReorderIndex,
|
||||||
UpdateLayersPanelControlBarLayout,
|
UpdateLayersPanelControlBarLayout,
|
||||||
UpdateLayerWidths,
|
UpdateLayerWidths,
|
||||||
UpdateMenuBarLayout,
|
UpdateMenuBarLayout,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
UpdateClickTargets,
|
UpdateClickTargets,
|
||||||
UpdateContextMenuInformation,
|
UpdateContextMenuInformation,
|
||||||
UpdateInSelectedNetwork,
|
UpdateInSelectedNetwork,
|
||||||
|
UpdateImportReorderIndex,
|
||||||
|
UpdateExportReorderIndex,
|
||||||
UpdateImportsExports,
|
UpdateImportsExports,
|
||||||
UpdateLayerWidths,
|
UpdateLayerWidths,
|
||||||
UpdateNodeGraph,
|
UpdateNodeGraph,
|
||||||
@@ -47,6 +49,8 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
selected: [] as bigint[],
|
selected: [] as bigint[],
|
||||||
transform: { scale: 1, x: 0, y: 0 },
|
transform: { scale: 1, x: 0, y: 0 },
|
||||||
inSelectedNetwork: true,
|
inSelectedNetwork: true,
|
||||||
|
reorderImportIndex: undefined as number | undefined,
|
||||||
|
reorderExportIndex: undefined as number | undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up message subscriptions on creation
|
// Set up message subscriptions on creation
|
||||||
@@ -76,6 +80,18 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
return state;
|
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) => {
|
editor.subscriptions.subscribeJsMessage(UpdateImportsExports, (updateImportsExports) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.imports = updateImportsExports.imports;
|
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_keyboard::ModifierKeys;
|
||||||
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
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::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::portfolio::utility_types::Platform;
|
||||||
use editor::messages::prelude::*;
|
use editor::messages::prelude::*;
|
||||||
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
|
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 });
|
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)]
|
#[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
||||||
pub fn inject_imaginate_poll_server_status(&self) {
|
pub fn inject_imaginate_poll_server_status(&self) {
|
||||||
self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
||||||
@@ -754,7 +774,10 @@ impl EditorHandle {
|
|||||||
if let Some(network) = document_node.implementation.get_network() {
|
if let Some(network) = document_node.implementation.get_network() {
|
||||||
let mut nodes_to_upgrade = Vec::new();
|
let mut nodes_to_upgrade = Vec::new();
|
||||||
for (node_id, _) in network.nodes.iter().collect::<Vec<_>>() {
|
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
|
&& document
|
||||||
.network_interface
|
.network_interface
|
||||||
.network(&[])
|
.network(&[])
|
||||||
@@ -770,7 +793,7 @@ impl EditorHandle {
|
|||||||
document
|
document
|
||||||
.network_interface
|
.network_interface
|
||||||
.replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ToArtboardNode"));
|
.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"
|
/// 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>(
|
fn math<U: num_traits::float::Float>(
|
||||||
_: (),
|
_: (),
|
||||||
/// The value of "A" when calculating the expression
|
/// The value of "A" when calculating the expression
|
||||||
|
|||||||
@@ -1185,7 +1185,7 @@ impl DomainWarpType {
|
|||||||
// Aims for interoperable compatibility with:
|
// 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=%27mixr%27%20%3D%20Channel%20Mixer
|
||||||
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr
|
// 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>>(
|
async fn channel_mixer<F: 'n + Send, T: Adjust<Color>>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
(),
|
||||||
@@ -1555,7 +1555,7 @@ async fn posterize<F: 'n + Send, T: Adjust<Color>>(
|
|||||||
//
|
//
|
||||||
// Algorithm based on:
|
// Algorithm based on:
|
||||||
// https://geraldbakker.nl/psnumbers/exposure.html
|
// 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>>(
|
async fn exposure<F: 'n + Send, T: Adjust<Color>>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
(),
|
||||||
@@ -1683,7 +1683,13 @@ mod index_node {
|
|||||||
use crate::raster::{Color, ImageFrame};
|
use crate::raster::{Color, ImageFrame};
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[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() {
|
if (index as usize) < input.len() {
|
||||||
input[index as usize].clone()
|
input[index as usize].clone()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,27 +29,39 @@ pub mod types {
|
|||||||
pub type Resolution = glam::UVec2;
|
pub type Resolution = glam::UVec2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translation struct between macro and definition
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NodeMetadata {
|
pub struct NodeMetadata {
|
||||||
pub display_name: &'static str,
|
pub display_name: &'static str,
|
||||||
pub category: Option<&'static str>,
|
pub category: Option<&'static str>,
|
||||||
pub fields: Vec<FieldMetadata>,
|
pub fields: Vec<FieldMetadata>,
|
||||||
pub description: &'static str,
|
pub description: &'static str,
|
||||||
|
pub properties: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translation struct between macro and definition
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FieldMetadata {
|
pub struct FieldMetadata {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub description: &'static str,
|
pub description: &'static str,
|
||||||
pub exposed: bool,
|
pub exposed: bool,
|
||||||
pub value_source: ValueSource,
|
pub widget_override: RegistryWidgetOverride,
|
||||||
|
pub value_source: RegistryValueSource,
|
||||||
pub number_min: Option<f64>,
|
pub number_min: Option<f64>,
|
||||||
pub number_max: Option<f64>,
|
pub number_max: Option<f64>,
|
||||||
pub number_mode_range: Option<(f64, f64)>,
|
pub number_mode_range: Option<(f64, f64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ValueSource {
|
pub enum RegistryWidgetOverride {
|
||||||
|
None,
|
||||||
|
Hidden,
|
||||||
|
String(&'static str),
|
||||||
|
Custom(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum RegistryValueSource {
|
||||||
None,
|
None,
|
||||||
Default(&'static str),
|
Default(&'static str),
|
||||||
Scope(&'static str),
|
Scope(&'static str),
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ fn ellipse<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _prima
|
|||||||
ellipse
|
ellipse
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Shape"))]
|
#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))]
|
||||||
fn rectangle<F: 'n + Send, T: CornerRadius>(
|
fn rectangle<F: 'n + Send, T: CornerRadius>(
|
||||||
#[implementations((), Footprint)] _footprint: F,
|
#[implementations((), Footprint)] _footprint: F,
|
||||||
_primary: (),
|
_primary: (),
|
||||||
|
|||||||
@@ -50,14 +50,15 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
|||||||
Footprint -> GraphicGroup,
|
Footprint -> GraphicGroup,
|
||||||
Footprint -> VectorData,
|
Footprint -> VectorData,
|
||||||
)]
|
)]
|
||||||
|
#[widget(ParsedWidgetOverride::Hidden)]
|
||||||
vector_group: impl Node<F, Output = T>,
|
vector_group: impl Node<F, Output = T>,
|
||||||
#[default(true)] fill: bool,
|
#[default(true)] fill: bool,
|
||||||
stroke: bool,
|
stroke: bool,
|
||||||
gradient: GradientStops,
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
randomize: bool,
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_randomize")] randomize: bool,
|
||||||
seed: SeedValue,
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue,
|
||||||
repeat_every: u32,
|
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32,
|
||||||
) -> T {
|
) -> T {
|
||||||
let mut input = vector_group.eval(footprint).await;
|
let mut input = vector_group.eval(footprint).await;
|
||||||
let length = input.vector_iter_mut().count();
|
let length = input.vector_iter_mut().count();
|
||||||
@@ -89,7 +90,7 @@ async fn assign_colors<F: 'n + Send, T: VectorIterMut>(
|
|||||||
input
|
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>(
|
async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
(),
|
||||||
@@ -161,7 +162,7 @@ async fn fill<F: 'n + Send, FillTy: Into<Fill> + 'n + Send, TargetTy: VectorIter
|
|||||||
target
|
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>(
|
async fn stroke<F: 'n + Send, ColorTy: Into<Option<Color>> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
(),
|
||||||
@@ -430,7 +431,7 @@ async fn bounding_box<F: 'n + Send>(
|
|||||||
result
|
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>(
|
async fn offset_path<F: 'n + Send>(
|
||||||
#[implementations(
|
#[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.
|
/// 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
|
#[cfg_attr(feature = "serde", serde(alias = "outputs", deserialize_with = "deserialize_exports"))] // TODO: Eventually remove this alias document upgrade code
|
||||||
pub exports: Vec<NodeInput>,
|
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>,
|
// pub import_types: Vec<Type>,
|
||||||
/// The list of all nodes in this network.
|
/// The list of all nodes in this network.
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
|||||||
@@ -430,24 +430,11 @@ generate_imaginate_node! {
|
|||||||
tiling: Tiling: bool,
|
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"))]
|
#[node_macro::node(category("Raster: Generator"))]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn noise_pattern(
|
fn noise_pattern(
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
|
_primary: (),
|
||||||
clip: bool,
|
clip: bool,
|
||||||
seed: u32,
|
seed: u32,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
|
|||||||
@@ -55,12 +55,22 @@ async fn draw_image_frame(_: (), image: ImageFrame<graphene_core::raster::SRGBA8
|
|||||||
|
|
||||||
#[node_macro::node(category("Network"))]
|
#[node_macro::node(category("Network"))]
|
||||||
async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> {
|
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"))]
|
#[node_macro::node(category("Raster"))]
|
||||||
fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrame<Color> {
|
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 = image.to_rgba32f();
|
||||||
let image = ImageFrame {
|
let image = ImageFrame {
|
||||||
image: Image {
|
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 => Option<WgpuSurface>]),
|
||||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]),
|
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_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();
|
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||||
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
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::Regular { ty, .. } => ty.clone(),
|
||||||
ParsedField::Node { output_type, input_type, .. } => match parsed.is_async {
|
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>),
|
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>),
|
false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect();
|
.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
|
let value_sources: Vec<_> = fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| match field {
|
.map(|field| match field {
|
||||||
ParsedField::Regular { value_source, .. } => match value_source {
|
ParsedField::Regular { value_source, .. } => match value_source {
|
||||||
ValueSource::Default(data) => quote!(ValueSource::Default(stringify!(#data))),
|
ParsedValueSource::Default(data) => quote!(RegistryValueSource::Default(stringify!(#data))),
|
||||||
ValueSource::Scope(data) => quote!(ValueSource::Scope(#data)),
|
ParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)),
|
||||||
_ => quote!(ValueSource::None),
|
_ => quote!(RegistryValueSource::None),
|
||||||
},
|
},
|
||||||
_ => quote!(ValueSource::None),
|
_ => quote!(RegistryValueSource::None),
|
||||||
})
|
})
|
||||||
.collect();
|
.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 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 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! {
|
Ok(quote! {
|
||||||
/// Underlying implementation for [#struct_name]
|
/// Underlying implementation for [#struct_name]
|
||||||
#[inline]
|
#[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::{Node, NodeIOTypes, concrete, fn_type, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
||||||
use gcore::value::ClonedNode;
|
use gcore::value::ClonedNode;
|
||||||
use gcore::ops::TypeNode;
|
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 gcore::ctor::ctor;
|
||||||
|
|
||||||
// Use the types specified in the implementation
|
// 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,
|
display_name: #display_name,
|
||||||
category: #category,
|
category: #category,
|
||||||
description: #description,
|
description: #description,
|
||||||
|
properties: #properties,
|
||||||
fields: vec![
|
fields: vec![
|
||||||
#(
|
#(
|
||||||
FieldMetadata {
|
FieldMetadata {
|
||||||
name: #input_names,
|
name: #input_names,
|
||||||
|
widget_override: #widget_override,
|
||||||
description: #input_descriptions,
|
description: #input_descriptions,
|
||||||
exposed: #exposed,
|
exposed: #exposed,
|
||||||
value_source: #value_sources,
|
value_source: #value_sources,
|
||||||
|
|||||||
@@ -39,26 +39,69 @@ pub(crate) struct NodeFnAttributes {
|
|||||||
pub(crate) display_name: Option<LitStr>,
|
pub(crate) display_name: Option<LitStr>,
|
||||||
pub(crate) path: Option<Path>,
|
pub(crate) path: Option<Path>,
|
||||||
pub(crate) skip_impl: bool,
|
pub(crate) skip_impl: bool,
|
||||||
|
pub(crate) properties_string: Option<LitStr>,
|
||||||
// Add more attributes as needed
|
// Add more attributes as needed
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub enum ValueSource {
|
pub enum ParsedValueSource {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Default(TokenStream2),
|
Default(TokenStream2),
|
||||||
Scope(LitStr),
|
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)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum ParsedField {
|
pub(crate) enum ParsedField {
|
||||||
Regular {
|
Regular {
|
||||||
pat_ident: PatIdent,
|
pat_ident: PatIdent,
|
||||||
name: Option<LitStr>,
|
name: Option<LitStr>,
|
||||||
description: String,
|
description: String,
|
||||||
|
widget_override: ParsedWidgetOverride,
|
||||||
ty: Type,
|
ty: Type,
|
||||||
exposed: bool,
|
exposed: bool,
|
||||||
value_source: ValueSource,
|
value_source: ParsedValueSource,
|
||||||
number_min: Option<LitFloat>,
|
number_min: Option<LitFloat>,
|
||||||
number_max: Option<LitFloat>,
|
number_max: Option<LitFloat>,
|
||||||
number_mode_range: Option<ExprTuple>,
|
number_mode_range: Option<ExprTuple>,
|
||||||
@@ -68,6 +111,7 @@ pub(crate) enum ParsedField {
|
|||||||
pat_ident: PatIdent,
|
pat_ident: PatIdent,
|
||||||
name: Option<LitStr>,
|
name: Option<LitStr>,
|
||||||
description: String,
|
description: String,
|
||||||
|
widget_override: ParsedWidgetOverride,
|
||||||
input_type: Type,
|
input_type: Type,
|
||||||
output_type: Type,
|
output_type: Type,
|
||||||
implementations: Punctuated<Implementation, Comma>,
|
implementations: Punctuated<Implementation, Comma>,
|
||||||
@@ -126,6 +170,7 @@ impl Parse for NodeFnAttributes {
|
|||||||
let mut display_name = None;
|
let mut display_name = None;
|
||||||
let mut path = None;
|
let mut path = None;
|
||||||
let mut skip_impl = false;
|
let mut skip_impl = false;
|
||||||
|
let mut properties_string = None;
|
||||||
|
|
||||||
let content = input;
|
let content = input;
|
||||||
// let content;
|
// let content;
|
||||||
@@ -165,6 +210,16 @@ impl Parse for NodeFnAttributes {
|
|||||||
}
|
}
|
||||||
skip_impl = true;
|
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(
|
return Err(Error::new_spanned(
|
||||||
meta,
|
meta,
|
||||||
@@ -187,6 +242,7 @@ impl Parse for NodeFnAttributes {
|
|||||||
display_name,
|
display_name,
|
||||||
path,
|
path,
|
||||||
skip_impl,
|
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))))
|
.map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `name` value for argument '{}': {}", ident, e))))
|
||||||
.transpose()?;
|
.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 exposed = extract_attribute(attrs, "expose").is_some();
|
||||||
|
|
||||||
let value_source = match (default_value, scope) {
|
let value_source = match (default_value, scope) {
|
||||||
(Some(_), Some(_)) => return Err(Error::new_spanned(&pat_ident, "Cannot have both `default` and `scope` attributes")),
|
(Some(_), Some(_)) => return Err(Error::new_spanned(&pat_ident, "Cannot have both `default` and `scope` attributes")),
|
||||||
(Some(default_value), _) => ValueSource::Default(default_value),
|
(Some(default_value), _) => ParsedValueSource::Default(default_value),
|
||||||
(_, Some(scope)) => ValueSource::Scope(scope),
|
(_, Some(scope)) => ParsedValueSource::Scope(scope),
|
||||||
_ => ValueSource::None,
|
_ => ParsedValueSource::None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let number_min = extract_attribute(attrs, "min")
|
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
|
let (input_type, output_type) = node_input_type
|
||||||
.zip(node_output_type)
|
.zip(node_output_type)
|
||||||
.ok_or_else(|| Error::new_spanned(&ty, "Invalid Node type. Expected `impl Node<Input, Output = OutputType>`"))?;
|
.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"));
|
return Err(Error::new_spanned(&ty, "No default values for `impl Node` allowed"));
|
||||||
}
|
}
|
||||||
let implementations = extract_attribute(attrs, "implementations")
|
let implementations = extract_attribute(attrs, "implementations")
|
||||||
@@ -417,6 +481,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
|||||||
pat_ident,
|
pat_ident,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
widget_override,
|
||||||
input_type,
|
input_type,
|
||||||
output_type,
|
output_type,
|
||||||
implementations,
|
implementations,
|
||||||
@@ -430,6 +495,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
|||||||
pat_ident,
|
pat_ident,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
widget_override,
|
||||||
exposed,
|
exposed,
|
||||||
number_min,
|
number_min,
|
||||||
number_max,
|
number_max,
|
||||||
@@ -550,11 +616,11 @@ mod tests {
|
|||||||
assert_eq!(p_name, e_name);
|
assert_eq!(p_name, e_name);
|
||||||
assert_eq!(p_exp, e_exp);
|
assert_eq!(p_exp, e_exp);
|
||||||
match (p_default, e_default) {
|
match (p_default, e_default) {
|
||||||
(ValueSource::None, ValueSource::None) => {}
|
(ParsedValueSource::None, ParsedValueSource::None) => {}
|
||||||
(ValueSource::Default(p), ValueSource::Default(e)) => {
|
(ParsedValueSource::Default(p), ParsedValueSource::Default(e)) => {
|
||||||
assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string());
|
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());
|
assert_eq!(p.value(), e.value());
|
||||||
}
|
}
|
||||||
_ => panic!("Mismatched default values"),
|
_ => panic!("Mismatched default values"),
|
||||||
@@ -602,6 +668,7 @@ mod tests {
|
|||||||
display_name: None,
|
display_name: None,
|
||||||
path: Some(parse_quote!(graphene_core::TestNode)),
|
path: Some(parse_quote!(graphene_core::TestNode)),
|
||||||
skip_impl: true,
|
skip_impl: true,
|
||||||
|
properties_string: None,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("add", Span::call_site()),
|
fn_name: Ident::new("add", Span::call_site()),
|
||||||
struct_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"),
|
pat_ident: pat_ident("b"),
|
||||||
name: None,
|
name: None,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
number_min: None,
|
number_min: None,
|
||||||
number_max: None,
|
number_max: None,
|
||||||
number_mode_range: None,
|
number_mode_range: None,
|
||||||
@@ -655,6 +723,7 @@ mod tests {
|
|||||||
display_name: None,
|
display_name: None,
|
||||||
path: None,
|
path: None,
|
||||||
skip_impl: false,
|
skip_impl: false,
|
||||||
|
properties_string: None,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("transform", Span::call_site()),
|
fn_name: Ident::new("transform", Span::call_site()),
|
||||||
struct_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"),
|
pat_ident: pat_ident("transform_target"),
|
||||||
name: None,
|
name: None,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
widget_override: ParsedWidgetOverride::None,
|
||||||
input_type: parse_quote!(Footprint),
|
input_type: parse_quote!(Footprint),
|
||||||
output_type: parse_quote!(T),
|
output_type: parse_quote!(T),
|
||||||
implementations: Punctuated::new(),
|
implementations: Punctuated::new(),
|
||||||
@@ -681,9 +751,10 @@ mod tests {
|
|||||||
pat_ident: pat_ident("translate"),
|
pat_ident: pat_ident("translate"),
|
||||||
name: None,
|
name: None,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: parse_quote!(DVec2),
|
ty: parse_quote!(DVec2),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
number_min: None,
|
number_min: None,
|
||||||
number_max: None,
|
number_max: None,
|
||||||
number_mode_range: None,
|
number_mode_range: None,
|
||||||
@@ -715,6 +786,7 @@ mod tests {
|
|||||||
display_name: None,
|
display_name: None,
|
||||||
path: None,
|
path: None,
|
||||||
skip_impl: false,
|
skip_impl: false,
|
||||||
|
properties_string: None,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("circle", Span::call_site()),
|
fn_name: Ident::new("circle", Span::call_site()),
|
||||||
struct_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"),
|
pat_ident: pat_ident("radius"),
|
||||||
name: None,
|
name: None,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ValueSource::Default(quote!(50.)),
|
value_source: ParsedValueSource::Default(quote!(50.)),
|
||||||
number_min: None,
|
number_min: None,
|
||||||
number_max: None,
|
number_max: None,
|
||||||
number_mode_range: None,
|
number_mode_range: None,
|
||||||
@@ -764,6 +837,7 @@ mod tests {
|
|||||||
display_name: None,
|
display_name: None,
|
||||||
path: None,
|
path: None,
|
||||||
skip_impl: false,
|
skip_impl: false,
|
||||||
|
properties_string: None,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("levels", Span::call_site()),
|
fn_name: Ident::new("levels", Span::call_site()),
|
||||||
struct_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"),
|
pat_ident: pat_ident("shadows"),
|
||||||
name: None,
|
name: None,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
number_min: None,
|
number_min: None,
|
||||||
number_max: None,
|
number_max: None,
|
||||||
number_mode_range: None,
|
number_mode_range: None,
|
||||||
@@ -825,6 +900,7 @@ mod tests {
|
|||||||
display_name: None,
|
display_name: None,
|
||||||
path: Some(parse_quote!(graphene_core::TestNode)),
|
path: Some(parse_quote!(graphene_core::TestNode)),
|
||||||
skip_impl: false,
|
skip_impl: false,
|
||||||
|
properties_string: None,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("add", Span::call_site()),
|
fn_name: Ident::new("add", Span::call_site()),
|
||||||
struct_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"),
|
pat_ident: pat_ident("b"),
|
||||||
name: None,
|
name: None,
|
||||||
description: String::from("b"),
|
description: String::from("b"),
|
||||||
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
number_min: Some(parse_quote!(-500.)),
|
number_min: Some(parse_quote!(-500.)),
|
||||||
number_max: Some(parse_quote!(500.)),
|
number_max: Some(parse_quote!(500.)),
|
||||||
number_mode_range: Some(parse_quote!((0., 100.))),
|
number_mode_range: Some(parse_quote!((0., 100.))),
|
||||||
@@ -874,6 +951,7 @@ mod tests {
|
|||||||
display_name: None,
|
display_name: None,
|
||||||
path: None,
|
path: None,
|
||||||
skip_impl: false,
|
skip_impl: false,
|
||||||
|
properties_string: None,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("load_image", Span::call_site()),
|
fn_name: Ident::new("load_image", Span::call_site()),
|
||||||
struct_name: Ident::new("LoadImage", Span::call_site()),
|
struct_name: Ident::new("LoadImage", Span::call_site()),
|
||||||
@@ -892,8 +970,9 @@ mod tests {
|
|||||||
name: None,
|
name: None,
|
||||||
ty: parse_quote!(String),
|
ty: parse_quote!(String),
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
widget_override: ParsedWidgetOverride::None,
|
||||||
exposed: true,
|
exposed: true,
|
||||||
value_source: ValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
number_min: None,
|
number_min: None,
|
||||||
number_max: None,
|
number_max: None,
|
||||||
number_mode_range: None,
|
number_mode_range: None,
|
||||||
@@ -923,6 +1002,7 @@ mod tests {
|
|||||||
display_name: Some(parse_quote!("CustomNode2")),
|
display_name: Some(parse_quote!("CustomNode2")),
|
||||||
path: None,
|
path: None,
|
||||||
skip_impl: false,
|
skip_impl: false,
|
||||||
|
properties_string: None,
|
||||||
},
|
},
|
||||||
fn_name: Ident::new("custom_node", Span::call_site()),
|
fn_name: Ident::new("custom_node", Span::call_site()),
|
||||||
struct_name: Ident::new("CustomNode", Span::call_site()),
|
struct_name: Ident::new("CustomNode", Span::call_site()),
|
||||||
|
|||||||
Reference in New Issue
Block a user