mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-04 02:08:12 +08:00
Add support for adding/removing a graph's primary imports/exports separately from the secondary (#3089)
* Primary Import/Export * Primary Import/Export * Complete import exports * Final changes * Improve line width * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
ce1cd1706b
commit
f1a1275601
Vendored
+4
@@ -33,6 +33,10 @@
|
|||||||
},
|
},
|
||||||
// Rust Analyzer config
|
// Rust Analyzer config
|
||||||
"rust-analyzer.cargo.allTargets": false,
|
"rust-analyzer.cargo.allTargets": false,
|
||||||
|
"rust-analyzer.procMacro.ignored": {
|
||||||
|
"serde_derive": ["Serialize", "Deserialize"],
|
||||||
|
"specta_macros": ["Type"] // Disabled because of: https://github.com/specta-rs/specta/issues/387
|
||||||
|
},
|
||||||
// ESLint config
|
// ESLint config
|
||||||
"eslint.format.enable": true,
|
"eslint.format.enable": true,
|
||||||
"eslint.workingDirectories": ["./frontend", "./website"],
|
"eslint.workingDirectories": ["./frontend", "./website"],
|
||||||
|
|||||||
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
+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
@@ -8,6 +8,7 @@ use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, La
|
|||||||
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::utility_types::HintData;
|
use crate::messages::tool::utility_types::HintData;
|
||||||
|
use glam::IVec2;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::raster::Image;
|
use graphene_std::raster::Image;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
@@ -124,12 +125,19 @@ pub enum FrontendMessage {
|
|||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
},
|
},
|
||||||
UpdateImportsExports {
|
UpdateImportsExports {
|
||||||
imports: Vec<(FrontendGraphOutput, i32, i32)>,
|
/// If the primary import is not visible, then it is None.
|
||||||
exports: Vec<(FrontendGraphInput, i32, i32)>,
|
imports: Vec<Option<FrontendGraphOutput>>,
|
||||||
#[serde(rename = "addImport")]
|
/// If the primary export is not visible, then it is None.
|
||||||
add_import: Option<(i32, i32)>,
|
exports: Vec<Option<FrontendGraphInput>>,
|
||||||
#[serde(rename = "addExport")]
|
/// The primary import location.
|
||||||
add_export: Option<(i32, i32)>,
|
#[serde(rename = "importPosition")]
|
||||||
|
import_position: IVec2,
|
||||||
|
/// The primary export location.
|
||||||
|
#[serde(rename = "exportPosition")]
|
||||||
|
export_position: IVec2,
|
||||||
|
/// The document network does not have an add import or export button.
|
||||||
|
#[serde(rename = "addImportExport")]
|
||||||
|
add_import_export: bool,
|
||||||
},
|
},
|
||||||
UpdateInSelectedNetwork {
|
UpdateInSelectedNetwork {
|
||||||
#[serde(rename = "inSelectedNetwork")]
|
#[serde(rename = "inSelectedNetwork")]
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType
|
|||||||
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
|
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate, OutputConnector};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
||||||
use crate::messages::portfolio::utility_types::PanelType;
|
use crate::messages::portfolio::utility_types::PanelType;
|
||||||
use crate::messages::portfolio::utility_types::PersistentData;
|
use crate::messages::portfolio::utility_types::PersistentData;
|
||||||
@@ -2766,7 +2766,7 @@ impl DocumentMessageHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_layers_panel_bottom_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
pub fn update_layers_panel_bottom_bar_widgets(&mut self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||||
if !layers_panel_open {
|
if !layers_panel_open {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2776,6 +2776,7 @@ impl DocumentMessageHandler {
|
|||||||
let selected_layer = selected_layers.next();
|
let selected_layer = selected_layers.next();
|
||||||
let has_selection = selected_layer.is_some();
|
let has_selection = selected_layer.is_some();
|
||||||
let has_multiple_selection = selected_layers.next().is_some();
|
let has_multiple_selection = selected_layers.next().is_some();
|
||||||
|
for _ in selected_layers {}
|
||||||
|
|
||||||
let widgets = vec![
|
let widgets = vec![
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
@@ -2789,7 +2790,7 @@ impl DocumentMessageHandler {
|
|||||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface);
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface);
|
||||||
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||||
if let Some(node_id) = node_type {
|
if let Some(node_id) = node_type {
|
||||||
let (output_type, _) = self.network_interface.output_type(&node_id, 0, &self.selection_network_path);
|
let (output_type, _) = self.network_interface.output_type(&OutputConnector::node(node_id, 0), &self.selection_network_path);
|
||||||
Some(format!("type:{}", output_type.nested_type()))
|
Some(format!("type:{}", output_type.nested_type()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -382,7 +382,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
||||||
NodeInput::node(NodeId(2), 0),
|
NodeInput::node(NodeId(2), 0),
|
||||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -771,6 +770,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![
|
exports: vec![
|
||||||
|
NodeInput::value(TaggedValue::None, false),
|
||||||
NodeInput::node(NodeId(0), 0),
|
NodeInput::node(NodeId(0), 0),
|
||||||
NodeInput::node(NodeId(1), 0),
|
NodeInput::node(NodeId(1), 0),
|
||||||
NodeInput::node(NodeId(2), 0),
|
NodeInput::node(NodeId(2), 0),
|
||||||
@@ -825,8 +825,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
input_metadata: vec![("Image", "TODO").into()],
|
input_metadata: vec![("Image", "TODO").into()],
|
||||||
output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
|
output_names: vec!["".to_string(), "Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
|
||||||
has_primary_output: false,
|
|
||||||
network_metadata: Some(NodeNetworkMetadata {
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
node_metadata: [
|
node_metadata: [
|
||||||
@@ -883,7 +882,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
|
exports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||||
@@ -905,13 +904,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
inputs: vec![NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), true)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
input_metadata: vec![("Vec2", "TODO").into()],
|
input_metadata: vec![("Vec2", "TODO").into()],
|
||||||
output_names: vec!["X".to_string(), "Y".to_string()],
|
output_names: vec!["".to_string(), "X".to_string(), "Y".to_string()],
|
||||||
has_primary_output: false,
|
|
||||||
network_metadata: Some(NodeNetworkMetadata {
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
node_metadata: [
|
node_metadata: [
|
||||||
|
|||||||
-1
@@ -77,7 +77,6 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
output_names: vec![output_type.to_string()],
|
output_names: vec![output_type.to_string()],
|
||||||
has_primary_output: true,
|
|
||||||
locked: false,
|
locked: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ pub enum NodeGraphMessage {
|
|||||||
},
|
},
|
||||||
AddPathNode,
|
AddPathNode,
|
||||||
AddImport,
|
AddImport,
|
||||||
|
AddPrimaryImport,
|
||||||
|
AddSecondaryImport,
|
||||||
AddExport,
|
AddExport,
|
||||||
|
AddPrimaryExport,
|
||||||
|
AddSecondaryExport,
|
||||||
Init,
|
Init,
|
||||||
SelectedNodesUpdated,
|
SelectedNodesUpdated,
|
||||||
Copy,
|
Copy,
|
||||||
@@ -63,6 +67,12 @@ pub enum NodeGraphMessage {
|
|||||||
set_to_exposed: bool,
|
set_to_exposed: bool,
|
||||||
start_transaction: bool,
|
start_transaction: bool,
|
||||||
},
|
},
|
||||||
|
ExposeEncapsulatingPrimaryInput {
|
||||||
|
exposed: bool,
|
||||||
|
},
|
||||||
|
ExposePrimaryExport {
|
||||||
|
exposed: bool,
|
||||||
|
},
|
||||||
InsertNode {
|
InsertNode {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
node_template: NodeTemplate,
|
node_template: NodeTemplate,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
|
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};
|
||||||
use super::{document_node_definitions, node_properties};
|
use super::{document_node_definitions, node_properties};
|
||||||
use crate::consts::GRID_SIZE;
|
use crate::consts::GRID_SIZE;
|
||||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||||
@@ -127,7 +127,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
|
||||||
}
|
}
|
||||||
NodeGraphMessage::AddPathNode => {
|
NodeGraphMessage::AddPathNode => {
|
||||||
if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
|
if let Some(layer) = make_path_editable_is_allowed(network_interface) {
|
||||||
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
|
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
|
||||||
responses.add(EventMessage::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
@@ -136,10 +136,44 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path);
|
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::AddPrimaryImport => {
|
||||||
|
if network_interface.number_of_imports(breadcrumb_network_path) == 0 {
|
||||||
|
responses.add(NodeGraphMessage::AddImport);
|
||||||
|
} else {
|
||||||
|
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NodeGraphMessage::AddSecondaryImport => {
|
||||||
|
// If necessary, add a hidden primary import before the secondary import
|
||||||
|
if network_interface.number_of_imports(breadcrumb_network_path) == 0 {
|
||||||
|
responses.add(NodeGraphMessage::AddImport);
|
||||||
|
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the secondary import
|
||||||
|
responses.add(NodeGraphMessage::AddImport);
|
||||||
|
}
|
||||||
NodeGraphMessage::AddExport => {
|
NodeGraphMessage::AddExport => {
|
||||||
network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, "", breadcrumb_network_path);
|
network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, "", breadcrumb_network_path);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::AddPrimaryExport => {
|
||||||
|
if network_interface.number_of_exports(breadcrumb_network_path) == 0 {
|
||||||
|
responses.add(NodeGraphMessage::AddExport);
|
||||||
|
} else {
|
||||||
|
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NodeGraphMessage::AddSecondaryExport => {
|
||||||
|
// If necessary, add a hidden primary import before the secondary import
|
||||||
|
if network_interface.number_of_exports(breadcrumb_network_path) == 0 {
|
||||||
|
responses.add(NodeGraphMessage::AddExport);
|
||||||
|
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the secondary export
|
||||||
|
responses.add(NodeGraphMessage::AddExport);
|
||||||
|
}
|
||||||
NodeGraphMessage::Init => {
|
NodeGraphMessage::Init => {
|
||||||
responses.add(BroadcastMessage::SubscribeEvent {
|
responses.add(BroadcastMessage::SubscribeEvent {
|
||||||
on: EventMessage::SelectionChanged,
|
on: EventMessage::SelectionChanged,
|
||||||
@@ -401,6 +435,77 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
|
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed } => {
|
||||||
|
let Some((node_id, network_path)) = breadcrumb_network_path.split_last() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let encapsulating_connector = InputConnector::node(*node_id, 0);
|
||||||
|
if !exposed {
|
||||||
|
network_interface.disconnect_input(&encapsulating_connector, network_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(mut input) = network_interface.input_from_connector(&encapsulating_connector, network_path).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let NodeInput::Value { exposed: old_exposed, .. } = &mut input {
|
||||||
|
*old_exposed = exposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface.set_input(&encapsulating_connector, input, network_path);
|
||||||
|
|
||||||
|
let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path) else {
|
||||||
|
log::error!("Could not get outward wires in remove_import");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(downstream_connections) = outward_wires.get(&OutputConnector::Import(0)).cloned() else {
|
||||||
|
log::error!("Could not get outward wires for import in remove_import");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Disconnect all connections in the encapsulating network
|
||||||
|
for downstream_connection in &downstream_connections {
|
||||||
|
network_interface.disconnect_input(downstream_connection, breadcrumb_network_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
|
}
|
||||||
|
NodeGraphMessage::ExposePrimaryExport { exposed } => {
|
||||||
|
let export_connector: InputConnector = InputConnector::Export(0);
|
||||||
|
if !exposed {
|
||||||
|
network_interface.disconnect_input(&export_connector, breadcrumb_network_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(mut input) = network_interface.input_from_connector(&export_connector, breadcrumb_network_path).cloned() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let NodeInput::Value { exposed: old_exposed, .. } = &mut input {
|
||||||
|
*old_exposed = exposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
network_interface.set_input(&export_connector, input, breadcrumb_network_path);
|
||||||
|
|
||||||
|
// Disconnect all connections in the encapsulating network
|
||||||
|
if let Some((encapsulating_node, encapsulating_path)) = breadcrumb_network_path.split_last() {
|
||||||
|
let Some(outward_wires) = network_interface.outward_wires(encapsulating_path) else {
|
||||||
|
log::error!("Could not get outward wires in remove_import");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*encapsulating_node, 0)).cloned() else {
|
||||||
|
log::error!("Could not get outward wires for import in remove_import");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for downstream_connection in &downstream_connections {
|
||||||
|
network_interface.disconnect_input(downstream_connection, encapsulating_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
|
}
|
||||||
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
||||||
network_interface.insert_node(node_id, node_template, selection_network_path);
|
network_interface.insert_node(node_id, node_template, selection_network_path);
|
||||||
}
|
}
|
||||||
@@ -706,21 +811,21 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() {
|
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(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::AddExport);
|
if remove_import_index == 0 {
|
||||||
return;
|
responses.add(NodeGraphMessage::ExposeEncapsulatingPrimaryInput { exposed: false })
|
||||||
} else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() {
|
} else {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index });
|
||||||
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;
|
return;
|
||||||
} else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) {
|
} 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(DocumentMessage::AddTransaction);
|
||||||
responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });
|
if remove_export_index == 0 {
|
||||||
|
responses.add(NodeGraphMessage::ExposePrimaryExport { exposed: false })
|
||||||
|
} else {
|
||||||
|
responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index });
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else if let Some(move_import_index) = modify_import_export.reorder_imports_exports.clicked_output_port_from_point(node_graph_point) {
|
} 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);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
@@ -756,10 +861,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Alt-click sets the clicked node as previewed
|
// Alt-click sets the clicked node as previewed
|
||||||
if alt_click {
|
if alt_click && let Some(clicked_node) = clicked_id {
|
||||||
if let Some(clicked_node) = clicked_id {
|
self.preview_on_mouse_up = Some(clicked_node);
|
||||||
self.preview_on_mouse_up = Some(clicked_node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin moving an existing wire
|
// Begin moving an existing wire
|
||||||
@@ -785,14 +888,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
self.initial_disconnecting = false;
|
self.initial_disconnecting = false;
|
||||||
|
|
||||||
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
||||||
if let Some((output_type, source)) = clicked_output
|
let (output_type, source) = &network_interface.output_type(&clicked_output, breadcrumb_network_path);
|
||||||
.node_id()
|
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(output_type, source);
|
||||||
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
|
|
||||||
{
|
|
||||||
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
|
|
||||||
} else {
|
|
||||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.update_node_graph_hints(responses);
|
self.update_node_graph_hints(responses);
|
||||||
return;
|
return;
|
||||||
@@ -1023,7 +1120,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
};
|
};
|
||||||
(position > point.y).then_some(*index)
|
(position > point.y).then_some(*index)
|
||||||
})
|
})
|
||||||
.unwrap_or(modify_import_export.reorder_imports_exports.output_ports().count()),
|
.filter(|end_index| *end_index > 0) // An import cannot be reordered to be the primary
|
||||||
|
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.output_ports().count() + 1),
|
||||||
);
|
);
|
||||||
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
||||||
} else if self.reordering_export.is_some() {
|
} else if self.reordering_export.is_some() {
|
||||||
@@ -1043,7 +1141,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
};
|
};
|
||||||
(position > point.y).then_some(*index)
|
(position > point.y).then_some(*index)
|
||||||
})
|
})
|
||||||
.unwrap_or(modify_import_export.reorder_imports_exports.input_ports().count()),
|
.filter(|end_index| *end_index > 0) // An export cannot be reordered to be the primary
|
||||||
|
.unwrap_or_else(|| modify_import_export.reorder_imports_exports.input_ports().count() + 1),
|
||||||
);
|
);
|
||||||
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
|
responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index });
|
||||||
}
|
}
|
||||||
@@ -1098,27 +1197,27 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
} else if output_connector.is_some() && input_connector.is_none() && !self.initial_disconnecting {
|
} else if !self.initial_disconnecting
|
||||||
|
&& input_connector.is_none()
|
||||||
|
&& let Some(output_connector) = output_connector
|
||||||
|
{
|
||||||
// If the add node menu is already open, we don't want to open it again
|
// If the add node menu is already open, we don't want to open it again
|
||||||
if self.context_menu.is_some() {
|
if self.context_menu.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the output types from the network interface
|
||||||
|
let (output_type, type_source) = network_interface.output_type(&output_connector, selection_network_path);
|
||||||
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||||
warn!("No network_metadata");
|
warn!("No network_metadata");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// Get the compatible type from the output connector
|
|
||||||
let compatible_type = output_connector.and_then(|output_connector| {
|
|
||||||
output_connector.node_id().and_then(|node_id| {
|
|
||||||
// Get the output types from the network interface
|
|
||||||
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
|
|
||||||
|
|
||||||
match type_source {
|
let compatible_type = match type_source {
|
||||||
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
||||||
_ => Some(format!("type:{}", output_type.nested_type())),
|
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||||
}
|
};
|
||||||
})
|
|
||||||
});
|
|
||||||
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||||
let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
|
let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
|
||||||
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x;
|
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x;
|
||||||
@@ -1442,11 +1541,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
NodeGraphMessage::RemoveImport { import_index: usize } => {
|
NodeGraphMessage::RemoveImport { import_index: usize } => {
|
||||||
network_interface.remove_import(usize, selection_network_path);
|
network_interface.remove_import(usize, selection_network_path);
|
||||||
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::RemoveExport { export_index: usize } => {
|
NodeGraphMessage::RemoveExport { export_index: usize } => {
|
||||||
network_interface.remove_export(usize, selection_network_path);
|
network_interface.remove_export(usize, selection_network_path);
|
||||||
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
@@ -1821,7 +1922,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
|
|
||||||
let shift = ipp.keyboard.get(Key::Shift as usize);
|
let shift = ipp.keyboard.get(Key::Shift as usize);
|
||||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||||
log::error!("Could not get selected nodes in PointerMove");
|
log::error!("Could not get selected nodes in UpdateBoxSelection");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>();
|
let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>();
|
||||||
@@ -1850,29 +1951,25 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::UpdateImportsExports => {
|
NodeGraphMessage::UpdateImportsExports => {
|
||||||
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
|
let imports = network_interface.frontend_imports(breadcrumb_network_path);
|
||||||
let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default();
|
let exports = network_interface.frontend_exports(breadcrumb_network_path);
|
||||||
let add_import = network_interface
|
|
||||||
.frontend_import_export_modify(
|
let Some((import_position, export_position)) = network_interface.import_export_position(breadcrumb_network_path) else {
|
||||||
|modify_import_export_click_target| modify_import_export_click_target.add_import_export.output_ports().collect::<Vec<_>>(),
|
log::error!("Could not get import export positions");
|
||||||
breadcrumb_network_path,
|
return;
|
||||||
)
|
};
|
||||||
.into_iter()
|
|
||||||
.next();
|
// Do not show the add import or add export button in the document network;
|
||||||
let add_export = network_interface
|
let add_import_export = !breadcrumb_network_path.is_empty();
|
||||||
.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(NodeGraphMessage::UpdateVisibleNodes);
|
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||||
responses.add(NodeGraphMessage::SendWires);
|
responses.add(NodeGraphMessage::SendWires);
|
||||||
responses.add(FrontendMessage::UpdateImportsExports {
|
responses.add(FrontendMessage::UpdateImportsExports {
|
||||||
imports,
|
imports,
|
||||||
exports,
|
exports,
|
||||||
add_import,
|
import_position,
|
||||||
add_export,
|
export_position,
|
||||||
|
add_import_export,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1990,11 +2087,6 @@ impl NodeGraphMessageHandler {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
|
||||||
warn!("No network in update_selection_action_buttons");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(breadcrumb_network_path) else {
|
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(breadcrumb_network_path) else {
|
||||||
warn!("No selected nodes in update_selection_action_buttons");
|
warn!("No selected nodes in update_selection_action_buttons");
|
||||||
return;
|
return;
|
||||||
@@ -2008,6 +2100,7 @@ impl NodeGraphMessageHandler {
|
|||||||
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
||||||
let selected_layer = selected_layers.next();
|
let selected_layer = selected_layers.next();
|
||||||
let has_multiple_selection = selected_layers.next().is_some();
|
let has_multiple_selection = selected_layers.next().is_some();
|
||||||
|
for _ in selected_layers {}
|
||||||
|
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
@@ -2020,7 +2113,7 @@ impl NodeGraphMessageHandler {
|
|||||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
||||||
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||||
if let Some(node_id) = node_type {
|
if let Some(node_id) = node_type {
|
||||||
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
let (output_type, _) = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
|
||||||
Some(format!("type:{}", output_type.nested_type()))
|
Some(format!("type:{}", output_type.nested_type()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -2106,6 +2199,11 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
let mut selection = selected_nodes.selected_nodes();
|
let mut selection = selected_nodes.selected_nodes();
|
||||||
let (selection, no_other_selections) = (selection.next(), selection.count() == 0);
|
let (selection, no_other_selections) = (selection.next(), selection.count() == 0);
|
||||||
|
|
||||||
|
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||||
|
warn!("No network in update_selection_action_buttons");
|
||||||
|
return;
|
||||||
|
};
|
||||||
let previewing = if matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) {
|
let previewing = if matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) {
|
||||||
network.exports.iter().find_map(|export| {
|
network.exports.iter().find_map(|export| {
|
||||||
let NodeInput::Node { node_id, .. } = export else { return None };
|
let NodeInput::Node { node_id, .. } = export else { return None };
|
||||||
@@ -2338,7 +2436,7 @@ impl NodeGraphMessageHandler {
|
|||||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, context.network_interface);
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, context.network_interface);
|
||||||
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||||
if let Some(node_id) = node_type {
|
if let Some(node_id) = node_type {
|
||||||
let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]);
|
let (output_type, _) = context.network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
|
||||||
Some(format!("type:{}", output_type.nested_type()))
|
Some(format!("type:{}", output_type.nested_type()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -2426,110 +2524,48 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
||||||
let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path).cloned() else {
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
let mut can_be_layer_lookup = HashSet::new();
|
|
||||||
let mut position_lookup = HashMap::new();
|
|
||||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||||
log::error!("Could not get nested network when collecting nodes");
|
log::error!("Could not get nested network when collecting nodes");
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
for node_id in network.nodes.keys().cloned().collect::<Vec<_>>() {
|
|
||||||
if network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path) {
|
|
||||||
can_be_layer_lookup.insert(node_id);
|
|
||||||
}
|
|
||||||
if let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) {
|
|
||||||
position_lookup.insert(node_id, position);
|
|
||||||
} else {
|
|
||||||
log::error!("Could not get position for node {node_id}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
|
|
||||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
|
||||||
log::error!("Could not get nested network when collecting nodes");
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
|
||||||
log::error!("Could not get network_metadata when collecting nodes");
|
|
||||||
return Vec::new();
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
for (&node_id, node) in &network.nodes {
|
for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::<Vec<_>>() {
|
||||||
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
|
let node_id_path = [breadcrumb_network_path, &[node_id]].concat();
|
||||||
|
|
||||||
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
let primary_input_connector = InputConnector::node(node_id, 0);
|
||||||
|
|
||||||
let mut inputs = inputs.into_iter().map(|input| {
|
let primary_input = if network_interface
|
||||||
input.map(|input| FrontendGraphInput {
|
.input_from_connector(&primary_input_connector, breadcrumb_network_path)
|
||||||
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
|
.is_some_and(|input| input.is_exposed())
|
||||||
resolved_type: format!("{:?}", &input.ty),
|
{
|
||||||
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
|
network_interface.frontend_input_from_connector(&primary_input_connector, breadcrumb_network_path)
|
||||||
name: input.input_name,
|
|
||||||
description: input.input_description,
|
|
||||||
connected_to: input.output_connector,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let primary_input = inputs.next().flatten();
|
|
||||||
let exposed_inputs = inputs.flatten().collect();
|
|
||||||
|
|
||||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
|
||||||
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
|
||||||
|
|
||||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
|
|
||||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
|
||||||
Some(FrontendGraphOutput {
|
|
||||||
data_type: frontend_data_type,
|
|
||||||
name: "Output 1".to_string(),
|
|
||||||
description: String::new(),
|
|
||||||
resolved_type: format!("{output_type:?}"),
|
|
||||||
connected_to,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let exposed_inputs = (1..network_interface.number_of_inputs(&node_id, breadcrumb_network_path))
|
||||||
|
.filter_map(|input_index| network_interface.frontend_input_from_connector(&InputConnector::node(node_id, input_index), breadcrumb_network_path))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let mut exposed_outputs = Vec::new();
|
let primary_output = network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, 0), breadcrumb_network_path);
|
||||||
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
|
|
||||||
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
|
||||||
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
|
||||||
|
|
||||||
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
|
let exposed_outputs = (1..network_interface.number_of_outputs(&node_id, breadcrumb_network_path))
|
||||||
log::error!("Could not get node_metadata when getting output for {node_id}");
|
.filter_map(|output_index| network_interface.frontend_output_from_connector(&OutputConnector::node(node_id, output_index), breadcrumb_network_path))
|
||||||
continue;
|
.collect();
|
||||||
};
|
let (primary_output_connected_to_layer, primary_input_connected_to_layer) = if network_interface.is_layer(&node_id, breadcrumb_network_path) {
|
||||||
let output_name = node_metadata
|
(
|
||||||
.persistent_metadata
|
network_interface.primary_output_connected_to_layer(&node_id, breadcrumb_network_path),
|
||||||
.output_names
|
network_interface.primary_input_connected_to_layer(&node_id, breadcrumb_network_path),
|
||||||
.get(output_index)
|
)
|
||||||
.cloned()
|
} else {
|
||||||
.filter(|output_name| !output_name.is_empty())
|
(false, false)
|
||||||
.unwrap_or_else(|| output_type.nested_type().to_string());
|
|
||||||
|
|
||||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
|
|
||||||
exposed_outputs.push(FrontendGraphOutput {
|
|
||||||
data_type,
|
|
||||||
name: output_name,
|
|
||||||
description: String::new(),
|
|
||||||
resolved_type: format!("{output_type:?}"),
|
|
||||||
connected_to,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
|
||||||
log::error!("Could not get nested network when collecting nodes");
|
|
||||||
return Vec::new();
|
|
||||||
};
|
};
|
||||||
let is_export = network.exports.first().is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id));
|
|
||||||
|
let is_export = network_interface
|
||||||
|
.input_from_connector(&InputConnector::Export(0), breadcrumb_network_path)
|
||||||
|
.is_some_and(|export| export.as_node().is_some_and(|export_node_id| node_id == export_node_id));
|
||||||
let is_root_node = network_interface.root_node(breadcrumb_network_path).is_some_and(|root_node| root_node.node_id == node_id);
|
let is_root_node = network_interface.root_node(breadcrumb_network_path).is_some_and(|root_node| root_node.node_id == node_id);
|
||||||
|
|
||||||
let Some(position) = position_lookup.get(&node_id).map(|pos| (pos.x, pos.y)) else {
|
let Some(position) = network_interface.position(&node_id, breadcrumb_network_path) else {
|
||||||
log::error!("Could not get position for node: {node_id}");
|
log::error!("Could not get position for node: {node_id}");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -2555,19 +2591,20 @@ impl NodeGraphMessageHandler {
|
|||||||
is_layer: network_interface
|
is_layer: network_interface
|
||||||
.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: network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path),
|
||||||
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
|
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
|
||||||
display_name: network_interface.display_name(&node_id, breadcrumb_network_path),
|
display_name: network_interface.display_name(&node_id, breadcrumb_network_path),
|
||||||
primary_input,
|
primary_input,
|
||||||
exposed_inputs,
|
exposed_inputs,
|
||||||
primary_output,
|
primary_output,
|
||||||
exposed_outputs,
|
exposed_outputs,
|
||||||
|
primary_output_connected_to_layer,
|
||||||
|
primary_input_connected_to_layer,
|
||||||
position,
|
position,
|
||||||
previewed,
|
previewed,
|
||||||
visible: node.visible,
|
visible,
|
||||||
locked,
|
locked,
|
||||||
errors,
|
errors,
|
||||||
ui_only: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2717,73 +2754,6 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct InputLookup {
|
|
||||||
input_name: String,
|
|
||||||
input_description: String,
|
|
||||||
ty: Type,
|
|
||||||
type_source: TypeSource,
|
|
||||||
valid_types: Vec<Type>,
|
|
||||||
output_connector: Option<OutputConnector>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type FrontendInputsLookup = HashMap<NodeId, Vec<Option<InputLookup>>>;
|
|
||||||
|
|
||||||
/// Create a lookup hashmap that can be used to create the frontend inputs. This is needed because `input_type` requires a mutable `network_interface`.
|
|
||||||
fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: &mut NodeNetworkInterface) -> FrontendInputsLookup {
|
|
||||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
|
||||||
return Default::default();
|
|
||||||
};
|
|
||||||
let mut frontend_inputs_lookup = HashMap::new();
|
|
||||||
for (node_id, index, output_connector, is_exposed) in network
|
|
||||||
.nodes
|
|
||||||
.iter()
|
|
||||||
.flat_map(|(node_id, node)| {
|
|
||||||
node.inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
{
|
|
||||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
|
||||||
let lookup = if !is_exposed {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
|
|
||||||
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
|
|
||||||
Some(InputLookup {
|
|
||||||
input_name,
|
|
||||||
input_description,
|
|
||||||
output_connector,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
|
|
||||||
}
|
|
||||||
|
|
||||||
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`)
|
|
||||||
let (ty, type_source) = network_interface.input_type(&InputConnector::node(node_id, index), breadcrumb_network_path);
|
|
||||||
value.ty = ty;
|
|
||||||
value.type_source = type_source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
|
||||||
for (index, value) in value.iter_mut().enumerate() {
|
|
||||||
// Skip not exposed inputs for efficiency
|
|
||||||
let Some(value) = value else { continue };
|
|
||||||
// Resolve the type (done in a separate loop because it requires a mutable reference to the `network_interface`)
|
|
||||||
value.valid_types = network_interface.valid_input_types(&InputConnector::node(node_id, index), breadcrumb_network_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frontend_inputs_lookup
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for NodeGraphMessageHandler {
|
impl Default for NodeGraphMessageHandler {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector, TypeSource};
|
use crate::messages::portfolio::document::utility_types::network_interface::TypeSource;
|
||||||
|
use glam::IVec2;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::Type;
|
use graphene_std::Type;
|
||||||
@@ -60,7 +61,8 @@ pub struct FrontendGraphInput {
|
|||||||
#[serde(rename = "validTypes")]
|
#[serde(rename = "validTypes")]
|
||||||
pub valid_types: Vec<String>,
|
pub valid_types: Vec<String>,
|
||||||
#[serde(rename = "connectedTo")]
|
#[serde(rename = "connectedTo")]
|
||||||
pub connected_to: Option<OutputConnector>,
|
/// Either "nothing", "import index {index}", or "{node name} output {output_index}".
|
||||||
|
pub connected_to: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
@@ -68,11 +70,13 @@ pub struct FrontendGraphOutput {
|
|||||||
#[serde(rename = "dataType")]
|
#[serde(rename = "dataType")]
|
||||||
pub data_type: FrontendGraphDataType,
|
pub data_type: FrontendGraphDataType,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
|
||||||
#[serde(rename = "resolvedType")]
|
#[serde(rename = "resolvedType")]
|
||||||
pub resolved_type: String,
|
pub resolved_type: String,
|
||||||
|
pub description: String,
|
||||||
|
/// If connected to an export, it is "export index {index}".
|
||||||
|
/// If connected to a node, it is "{node name} input {input_index}".
|
||||||
#[serde(rename = "connectedTo")]
|
#[serde(rename = "connectedTo")]
|
||||||
pub connected_to: Vec<InputConnector>,
|
pub connected_to: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
@@ -93,13 +97,15 @@ pub struct FrontendNode {
|
|||||||
pub primary_output: Option<FrontendGraphOutput>,
|
pub primary_output: Option<FrontendGraphOutput>,
|
||||||
#[serde(rename = "exposedOutputs")]
|
#[serde(rename = "exposedOutputs")]
|
||||||
pub exposed_outputs: Vec<FrontendGraphOutput>,
|
pub exposed_outputs: Vec<FrontendGraphOutput>,
|
||||||
pub position: (i32, i32),
|
#[serde(rename = "primaryOutputConnectedToLayer")]
|
||||||
|
pub primary_output_connected_to_layer: bool,
|
||||||
|
#[serde(rename = "primaryInputConnectedToLayer")]
|
||||||
|
pub primary_input_connected_to_layer: bool,
|
||||||
|
pub position: IVec2,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub locked: bool,
|
pub locked: bool,
|
||||||
pub previewed: bool,
|
pub previewed: bool,
|
||||||
pub errors: Option<String>,
|
pub errors: Option<String>,
|
||||||
#[serde(rename = "uiOnly")]
|
|
||||||
pub ui_only: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+136
@@ -0,0 +1,136 @@
|
|||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, InputPersistentMetadata, NodeNetworkMetadata, NodeTypePersistentMetadata};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct DocumentNodePersistentMetadataInputNames {
|
||||||
|
pub reference: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub display_name: String,
|
||||||
|
pub input_names: Vec<String>,
|
||||||
|
pub output_names: Vec<String>,
|
||||||
|
pub has_primary_output: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub locked: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pinned: bool,
|
||||||
|
pub node_type_metadata: NodeTypePersistentMetadata,
|
||||||
|
pub network_metadata: Option<NodeNetworkMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DocumentNodePersistentMetadataInputNames> for DocumentNodePersistentMetadata {
|
||||||
|
fn from(old: DocumentNodePersistentMetadataInputNames) -> Self {
|
||||||
|
DocumentNodePersistentMetadata {
|
||||||
|
input_metadata: Vec::new(),
|
||||||
|
..old.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct DocumentNodePersistentMetadataPropertiesRow {
|
||||||
|
pub reference: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub display_name: String,
|
||||||
|
pub input_properties: Vec<PropertiesRow>,
|
||||||
|
pub output_names: Vec<String>,
|
||||||
|
pub has_primary_output: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub locked: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pinned: bool,
|
||||||
|
pub node_type_metadata: NodeTypePersistentMetadata,
|
||||||
|
pub network_metadata: Option<NodeNetworkMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct PropertiesRow {
|
||||||
|
pub input_data: HashMap<String, Value>,
|
||||||
|
pub widget_override: Option<String>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub input_name: String,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub input_description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersistentMetadata {
|
||||||
|
fn from(old: DocumentNodePersistentMetadataPropertiesRow) -> Self {
|
||||||
|
let mut input_metadata = Vec::new();
|
||||||
|
for properties_row in old.input_properties {
|
||||||
|
input_metadata.push(InputMetadata {
|
||||||
|
persistent_metadata: InputPersistentMetadata {
|
||||||
|
input_data: properties_row.input_data,
|
||||||
|
widget_override: properties_row.widget_override,
|
||||||
|
input_name: properties_row.input_name,
|
||||||
|
input_description: properties_row.input_description,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
DocumentNodePersistentMetadata {
|
||||||
|
reference: old.reference,
|
||||||
|
display_name: old.display_name,
|
||||||
|
input_metadata: Vec::new(),
|
||||||
|
output_names: old.output_names,
|
||||||
|
locked: old.locked,
|
||||||
|
pinned: old.pinned,
|
||||||
|
node_type_metadata: old.node_type_metadata,
|
||||||
|
network_metadata: old.network_metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct DocumentNodePersistentMetadataHasPrimaryOutput {
|
||||||
|
pub reference: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub display_name: String,
|
||||||
|
pub input_metadata: Vec<InputMetadata>,
|
||||||
|
pub output_names: Vec<String>,
|
||||||
|
pub has_primary_output: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub locked: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pinned: bool,
|
||||||
|
pub node_type_metadata: NodeTypePersistentMetadata,
|
||||||
|
pub network_metadata: Option<NodeNetworkMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DocumentNodePersistentMetadataHasPrimaryOutput> for DocumentNodePersistentMetadata {
|
||||||
|
fn from(old: DocumentNodePersistentMetadataHasPrimaryOutput) -> Self {
|
||||||
|
DocumentNodePersistentMetadata {
|
||||||
|
reference: old.reference,
|
||||||
|
display_name: old.display_name,
|
||||||
|
input_metadata: old.input_metadata,
|
||||||
|
output_names: old.output_names,
|
||||||
|
locked: old.locked,
|
||||||
|
pinned: old.pinned,
|
||||||
|
node_type_metadata: old.node_type_metadata,
|
||||||
|
network_metadata: old.network_metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
let value = Value::deserialize(deserializer)?;
|
||||||
|
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataHasPrimaryOutput>(value.clone()) {
|
||||||
|
return Ok(document.into());
|
||||||
|
};
|
||||||
|
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadata>(value.clone()) {
|
||||||
|
return Ok(document);
|
||||||
|
};
|
||||||
|
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataPropertiesRow>(value.clone()) {
|
||||||
|
return Ok(document.into());
|
||||||
|
};
|
||||||
|
match serde_json::from_value::<DocumentNodePersistentMetadataInputNames>(value.clone()) {
|
||||||
|
Ok(document) => Ok(document.into()),
|
||||||
|
Err(e) => Err(serde::de::Error::custom(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,7 +109,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
let metadata = &document.network_interface.document_network_metadata().persistent_metadata;
|
let metadata = &document.network_interface.document_network_metadata().persistent_metadata;
|
||||||
(!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty())
|
(!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty())
|
||||||
};
|
};
|
||||||
self.menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
|
self.menu_bar_message_handler.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.menu_bar_message_handler.process_message(message, responses, ());
|
self.menu_bar_message_handler.process_message(message, responses, ());
|
||||||
@@ -1144,7 +1144,7 @@ impl PortfolioMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque<Message>, to_front: bool) {
|
fn load_document(&mut self, mut new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque<Message>, to_front: bool) {
|
||||||
if to_front {
|
if to_front {
|
||||||
self.document_ids.push_front(document_id);
|
self.document_ids.push_front(document_id);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use super::snapping::{SnapCandidatePoint, SnapData, SnapManager};
|
use super::snapping::{SnapCandidatePoint, SnapData, SnapManager};
|
||||||
use super::transformation_cage::{BoundingBoxManager, SizeSnapData};
|
use super::transformation_cage::{BoundingBoxManager, SizeSnapData};
|
||||||
use crate::consts::ROTATE_INCREMENT;
|
use crate::consts::ROTATE_INCREMENT;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
use crate::messages::portfolio::document::utility_types::network_interface::{NodeNetworkInterface, OutputConnector};
|
||||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_text};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{NodeGraphLayer, get_text};
|
||||||
@@ -568,25 +568,21 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
|
|||||||
(d1 * len1, d2 * len2)
|
(d1 * len1, d2 * len2)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_path_editable_is_allowed(network_interface: &NodeNetworkInterface, metadata: &DocumentMetadata) -> Option<LayerNodeIdentifier> {
|
pub fn make_path_editable_is_allowed(network_interface: &mut NodeNetworkInterface) -> Option<LayerNodeIdentifier> {
|
||||||
// Must have exactly one layer selected
|
// Must have exactly one layer selected
|
||||||
let selected_nodes = network_interface.selected_nodes();
|
let selected_nodes = network_interface.selected_nodes();
|
||||||
let mut selected_layers = selected_nodes.selected_layers(metadata);
|
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
||||||
let first_layer = selected_layers.next()?;
|
let first_layer = selected_layers.next()?;
|
||||||
if selected_layers.next().is_some() {
|
if selected_layers.next().is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
for _ in selected_layers {}
|
||||||
|
|
||||||
// Must be a layer of type Table<Vector>
|
// Must be a layer of type Table<Vector>
|
||||||
let compatible_type = NodeGraphLayer::new(first_layer, network_interface)
|
let node_id = NodeGraphLayer::new(first_layer, network_interface).horizontal_layer_flow().nth(1)?;
|
||||||
.horizontal_layer_flow()
|
|
||||||
.nth(1)
|
let (output_type, _) = network_interface.output_type(&OutputConnector::node(node_id, 0), &[]);
|
||||||
.map(|node_id| {
|
if output_type.nested_type() != concrete!(Table<Vector>).nested_type() {
|
||||||
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
|
||||||
output_type.nested_type() == concrete!(Table<Vector>).nested_type()
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
if !compatible_type {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3101,7 +3101,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
colinear,
|
colinear,
|
||||||
};
|
};
|
||||||
|
|
||||||
tool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&document.network_interface, document.metadata()).is_some();
|
tool_data.make_path_editable_is_allowed = make_path_editable_is_allowed(&mut document.network_interface).is_some();
|
||||||
tool_data.update_selection_status(shape_editor, document);
|
tool_data.update_selection_status(shape_editor, document);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { Node } from "@graphite/messages";
|
import type { FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages";
|
||||||
import type { FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages";
|
|
||||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||||
import type { IconName } from "@graphite/utility-functions/icons";
|
import type { IconName } from "@graphite/utility-functions/icons";
|
||||||
|
|
||||||
@@ -178,7 +177,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
|
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
|
||||||
return value.resolvedType ? `Data Type:\n${value.resolvedType}` : `Data Type (Unresolved):\n${value.dataType}`;
|
return `Data Type: ${value.resolvedType}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validTypesText(value: FrontendGraphInput): string {
|
function validTypesText(value: FrontendGraphInput): string {
|
||||||
@@ -189,37 +188,11 @@
|
|||||||
function outputConnectedToText(output: FrontendGraphOutput): string {
|
function outputConnectedToText(output: FrontendGraphOutput): string {
|
||||||
if (output.connectedTo.length === 0) return "Connected to nothing";
|
if (output.connectedTo.length === 0) return "Connected to nothing";
|
||||||
|
|
||||||
return output.connectedTo
|
return `Connected to:\n${output.connectedTo.join("\n")}`;
|
||||||
.map((inputConnector) => {
|
|
||||||
if ((inputConnector as Node).nodeId === undefined) return `Connected to export index ${inputConnector.index}`;
|
|
||||||
return `Connected to ${(inputConnector as Node).nodeId}, connector index ${inputConnector.index}`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function inputConnectedToText(input: FrontendGraphInput): string {
|
function inputConnectedToText(input: FrontendGraphInput): string {
|
||||||
if (input.connectedTo === undefined) return "Connected to nothing";
|
return `Connected to:\n${input.connectedTo}`;
|
||||||
if ((input.connectedTo as Node).nodeId === undefined) return `Connected to import index ${input.connectedTo.index}`;
|
|
||||||
return `Connected to ${(input.connectedTo as Node).nodeId}, connector index ${input.connectedTo.index}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function primaryOutputConnectedToLayer(node: FrontendNode): boolean {
|
|
||||||
let firstConnectedNode = Array.from($nodeGraph.nodes.values()).find((n) =>
|
|
||||||
node.primaryOutput?.connectedTo.some((connector) => {
|
|
||||||
if ((connector as Node).nodeId === undefined) return false;
|
|
||||||
if (connector.index !== 0n) return false;
|
|
||||||
return n.id === (connector as Node).nodeId || false;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return firstConnectedNode?.isLayer || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function primaryInputConnectedToLayer(node: FrontendNode): boolean {
|
|
||||||
const connectedNode = Array.from($nodeGraph.nodes.values()).find((n) => {
|
|
||||||
if ((node.primaryInput?.connectedTo as Node) === undefined) return false;
|
|
||||||
return n.id === (node.primaryInput?.connectedTo as Node).nodeId;
|
|
||||||
});
|
|
||||||
return connectedNode?.isLayer || false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
|
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
|
||||||
@@ -265,16 +238,12 @@
|
|||||||
{
|
{
|
||||||
value: "node",
|
value: "node",
|
||||||
label: "Node",
|
label: "Node",
|
||||||
action: () => {
|
action: () => toggleLayerDisplay(false, contextMenuData.nodeId),
|
||||||
toggleLayerDisplay(false, contextMenuData.nodeId);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "layer",
|
value: "layer",
|
||||||
label: "Layer",
|
label: "Layer",
|
||||||
action: () => {
|
action: () => toggleLayerDisplay(true, contextMenuData.nodeId),
|
||||||
toggleLayerDisplay(true, contextMenuData.nodeId);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
disabled={!canBeToggledBetweenNodeAndLayer(contextMenuData.nodeId)}
|
disabled={!canBeToggledBetweenNodeAndLayer(contextMenuData.nodeId)}
|
||||||
@@ -334,149 +303,181 @@
|
|||||||
|
|
||||||
<!-- Import and Export connectors -->
|
<!-- Import and Export connectors -->
|
||||||
<div class="imports-and-exports" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="imports-and-exports" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||||
{#each $nodeGraph.imports as { outputMetadata, position }, index}
|
{#if $nodeGraph.updateImportsExports}
|
||||||
<svg
|
{#each $nodeGraph.updateImportsExports.imports as frontendOutput, index}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
{#if frontendOutput}
|
||||||
viewBox="0 0 8 8"
|
<svg
|
||||||
class="connector"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
data-connector="output"
|
viewBox="0 0 8 8"
|
||||||
data-datatype={outputMetadata.dataType}
|
class="connector"
|
||||||
style:--data-color={`var(--color-data-${outputMetadata.dataType.toLowerCase()})`}
|
data-connector="output"
|
||||||
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
|
data-datatype={frontendOutput.dataType}
|
||||||
style:--offset-left={position.x / 24}
|
style:--data-color={`var(--color-data-${frontendOutput.dataType.toLowerCase()})`}
|
||||||
style:--offset-top={position.y / 24}
|
style:--data-color-dim={`var(--color-data-${frontendOutput.dataType.toLowerCase()}-dim)`}
|
||||||
>
|
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24}
|
||||||
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
|
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index}
|
||||||
{#if outputMetadata.connectedTo !== undefined}
|
>
|
||||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
|
<title>{`${dataTypeTooltip(frontendOutput)}\n\n${outputConnectedToText(frontendOutput)}`}</title>
|
||||||
{:else}
|
{#if frontendOutput.connectedTo.length > 0}
|
||||||
<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)" />
|
||||||
{/if}
|
{:else}
|
||||||
</svg>
|
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
|
||||||
|
{/if}
|
||||||
|
</svg>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="edit-import-export import"
|
on:pointerenter={() => (hoveringImportIndex = index)}
|
||||||
on:pointerenter={() => (hoveringImportIndex = index)}
|
on:pointerleave={() => (hoveringImportIndex = undefined)}
|
||||||
on:pointerleave={() => (hoveringImportIndex = undefined)}
|
class="edit-import-export import"
|
||||||
style:--offset-left={position.x / 24}
|
class:separator-bottom={index === 0 && $nodeGraph.updateImportsExports.addImportExport}
|
||||||
style:--offset-top={position.y / 24}
|
class:separator-top={index === 1 && $nodeGraph.updateImportsExports.addImportExport}
|
||||||
>
|
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 8) / 24}
|
||||||
{#if editingNameImportIndex == index}
|
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 8) / 24 + index}
|
||||||
<input
|
>
|
||||||
class="import-text-input"
|
{#if editingNameImportIndex == index}
|
||||||
type="text"
|
<input
|
||||||
style:width={importsToEdgeTextInputWidth()}
|
class="import-text-input"
|
||||||
bind:this={inputElement}
|
type="text"
|
||||||
bind:value={editingNameText}
|
style:width={importsToEdgeTextInputWidth()}
|
||||||
on:blur={setEditingImportName}
|
bind:this={inputElement}
|
||||||
on:keydown={(e) => e.key === "Enter" && setEditingImportName(e)}
|
bind:value={editingNameText}
|
||||||
/>
|
on:blur={setEditingImportName}
|
||||||
|
on:keydown={(e) => e.key === "Enter" && setEditingImportName(e)}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<p class="import-text" on:dblclick={() => setEditingImportNameIndex(index, frontendOutput.name)}>
|
||||||
|
{frontendOutput.name}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{#if (hoveringImportIndex === index || editingNameImportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
|
||||||
|
<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 */
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{#if index > 0}
|
||||||
|
<div class="reorder-drag-grip" title="Reorder this export" />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="import-text" on:dblclick={() => setEditingImportNameIndex(index, outputMetadata.name)}>{outputMetadata.name}</p>
|
<div
|
||||||
|
class="plus"
|
||||||
|
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 12) / 24}
|
||||||
|
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 12) / 24}
|
||||||
|
>
|
||||||
|
<IconButton size={24} icon="Add" action={() => editor.handle.addPrimaryImport()} />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if hoveringImportIndex === index || editingNameImportIndex === index}
|
{/each}
|
||||||
<IconButton
|
|
||||||
size={16}
|
{#each $nodeGraph.updateImportsExports.exports as frontendInput, index}
|
||||||
icon={"Remove"}
|
{#if frontendInput}
|
||||||
class="remove-button-import"
|
<svg
|
||||||
data-index={index}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
data-import-text-edge
|
viewBox="0 0 8 8"
|
||||||
action={() => {
|
class="connector"
|
||||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
data-connector="input"
|
||||||
}}
|
data-datatype={frontendInput.dataType}
|
||||||
/>
|
style:--data-color={`var(--color-data-${frontendInput.dataType.toLowerCase()})`}
|
||||||
<div class="reorder-drag-grip" title="Reorder this import"></div>
|
style:--data-color-dim={`var(--color-data-${frontendInput.dataType.toLowerCase()}-dim)`}
|
||||||
{/if}
|
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 8) / 24}
|
||||||
</div>
|
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 8) / 24 + index}
|
||||||
{/each}
|
>
|
||||||
{#if $nodeGraph.reorderImportIndex !== undefined}
|
<title>{`${dataTypeTooltip(frontendInput)}\n\n${inputConnectedToText(frontendInput)}`}</title>
|
||||||
{@const position = {
|
{#if frontendInput.connectedTo !== "nothing"}
|
||||||
x: Number($nodeGraph.imports[0].position.x),
|
<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)" />
|
||||||
y: Number($nodeGraph.imports[0].position.y) + Number($nodeGraph.reorderImportIndex) * 24,
|
{:else}
|
||||||
}}
|
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
|
||||||
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 4) / 24} />
|
{/if}
|
||||||
{/if}
|
</svg>
|
||||||
{#if $nodeGraph.addImport !== undefined}
|
<div
|
||||||
<div class="plus" style:--offset-left={$nodeGraph.addImport.x / 24} style:--offset-top={$nodeGraph.addImport.y / 24}>
|
on:pointerenter={() => (hoveringExportIndex = index)}
|
||||||
<IconButton
|
on:pointerleave={() => (hoveringExportIndex = undefined)}
|
||||||
size={24}
|
class="edit-import-export export"
|
||||||
icon="Add"
|
class:separator-bottom={index === 0 && $nodeGraph.updateImportsExports.addImportExport}
|
||||||
action={() => {
|
class:separator-top={index === 1 && $nodeGraph.updateImportsExports.addImportExport}
|
||||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 8) / 24}
|
||||||
}}
|
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 8) / 24 + index}
|
||||||
/>
|
>
|
||||||
</div>
|
{#if (hoveringExportIndex === index || editingNameExportIndex === index) && $nodeGraph.updateImportsExports.addImportExport}
|
||||||
{/if}
|
{#if index > 0}
|
||||||
{#each $nodeGraph.exports as { inputMetadata, position }, index}
|
<div class="reorder-drag-grip" title="Reorder this export" />
|
||||||
<svg
|
{/if}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<IconButton
|
||||||
viewBox="0 0 8 8"
|
size={16}
|
||||||
class="connector"
|
icon={"Remove"}
|
||||||
data-connector="input"
|
class="remove-button-export"
|
||||||
data-datatype={inputMetadata.dataType}
|
data-index={index}
|
||||||
style:--data-color={`var(--color-data-${inputMetadata.dataType.toLowerCase()})`}
|
data-export-text-edge
|
||||||
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
|
action={() => {
|
||||||
style:--offset-left={position.x / 24}
|
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
||||||
style:--offset-top={position.y / 24}
|
}}
|
||||||
>
|
/>
|
||||||
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
|
{/if}
|
||||||
{#if inputMetadata.connectedTo !== undefined}
|
{#if editingNameExportIndex === index}
|
||||||
<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)" />
|
<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, frontendInput.name)}>
|
||||||
|
{frontendInput.name}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
|
<div
|
||||||
|
class="plus"
|
||||||
|
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 12) / 24}
|
||||||
|
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 12) / 24}
|
||||||
|
>
|
||||||
|
<IconButton size={24} icon="Add" action={() => editor.handle.addPrimaryExport()} />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</svg>
|
{/each}
|
||||||
<div
|
|
||||||
class="edit-import-export export"
|
{#if $nodeGraph.updateImportsExports.addImportExport == true}
|
||||||
on:pointerenter={() => (hoveringExportIndex = index)}
|
<div
|
||||||
on:pointerleave={() => (hoveringExportIndex = undefined)}
|
class="plus"
|
||||||
style:--offset-left={position.x / 24}
|
style:--offset-left={($nodeGraph.updateImportsExports.importPosition.x - 12) / 24}
|
||||||
style:--offset-top={position.y / 24}
|
style:--offset-top={($nodeGraph.updateImportsExports.importPosition.y - 12) / 24 + $nodeGraph.updateImportsExports.imports.length}
|
||||||
>
|
>
|
||||||
{#if hoveringExportIndex === index || editingNameExportIndex === index}
|
<IconButton size={24} icon="Add" action={() => editor.handle.addSecondaryImport()} />
|
||||||
<div class="reorder-drag-grip" title="Reorder this export"></div>
|
</div>
|
||||||
<IconButton
|
<div
|
||||||
size={16}
|
class="plus"
|
||||||
icon={"Remove"}
|
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition.x - 12) / 24}
|
||||||
class="remove-button-export"
|
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition.y - 12) / 24 + $nodeGraph.updateImportsExports.exports.length}
|
||||||
data-index={index}
|
>
|
||||||
data-export-text-edge
|
<IconButton size={24} icon={"Add"} action={() => editor.handle.addSecondaryExport()} />
|
||||||
action={() => {
|
</div>
|
||||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
{/if}
|
||||||
}}
|
|
||||||
/>
|
{#if $nodeGraph.reorderImportIndex !== undefined}
|
||||||
{/if}
|
{@const position = {
|
||||||
{#if editingNameExportIndex === index}
|
x: Number($nodeGraph.updateImportsExports.importPosition.x),
|
||||||
<input
|
y: Number($nodeGraph.updateImportsExports.importPosition.y) + Number($nodeGraph.reorderImportIndex) * 24,
|
||||||
type="text"
|
}}
|
||||||
style:width={exportsToEdgeTextInputWidth()}
|
<div class="reorder-bar" style:--offset-left={(position.x - 48) / 24} style:--offset-top={(position.y - 12) / 24} />
|
||||||
bind:this={inputElement}
|
{/if}
|
||||||
bind:value={editingNameText}
|
|
||||||
on:blur={setEditingExportName}
|
{#if $nodeGraph.reorderExportIndex !== undefined}
|
||||||
on:keydown={(e) => e.key === "Enter" && setEditingExportName(e)}
|
{@const position = {
|
||||||
/>
|
x: Number($nodeGraph.updateImportsExports.exportPosition.x),
|
||||||
{:else}
|
y: Number($nodeGraph.updateImportsExports.exportPosition.y) + Number($nodeGraph.reorderExportIndex) * 24,
|
||||||
<p class="export-text" on:dblclick={() => setEditingExportNameIndex(index, inputMetadata.name)}>{inputMetadata.name}</p>
|
}}
|
||||||
{/if}
|
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 12) / 24} />
|
||||||
</div>
|
{/if}
|
||||||
{/each}
|
|
||||||
{#if $nodeGraph.reorderExportIndex !== undefined}
|
|
||||||
{@const position = {
|
|
||||||
x: Number($nodeGraph.exports[0].position.x),
|
|
||||||
y: Number($nodeGraph.exports[0].position.y) + Number($nodeGraph.reorderExportIndex) * 24,
|
|
||||||
}}
|
|
||||||
<div class="reorder-bar" style:--offset-left={position.x / 24} style:--offset-top={(position.y - 4) / 24} />
|
|
||||||
{/if}
|
|
||||||
{#if $nodeGraph.addExport !== undefined}
|
|
||||||
<div class="plus" style:--offset-left={$nodeGraph.addExport.x / 24} style:--offset-top={$nodeGraph.addExport.y / 24}>
|
|
||||||
<IconButton
|
|
||||||
size={24}
|
|
||||||
icon={"Add"}
|
|
||||||
action={() => {
|
|
||||||
/* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -530,7 +531,7 @@
|
|||||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||||
{#if node.primaryOutput.connectedTo.length > 0}
|
{#if node.primaryOutput.connectedTo.length > 0}
|
||||||
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
|
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
|
||||||
{#if primaryOutputConnectedToLayer(node)}
|
{#if node.primaryOutputConnectedToLayer}
|
||||||
<path d="M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z" fill="var(--data-color-dim)" />
|
<path d="M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z" fill="var(--data-color-dim)" />
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
@@ -551,9 +552,9 @@
|
|||||||
{#if node.primaryInput}
|
{#if node.primaryInput}
|
||||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||||
{/if}
|
{/if}
|
||||||
{#if node.primaryInput?.connectedTo !== undefined}
|
{#if node.primaryInput?.connectedTo !== "nothing"}
|
||||||
<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)" />
|
||||||
{#if primaryInputConnectedToLayer(node)}
|
{#if node.primaryInputConnectedToLayer}
|
||||||
<path d="M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z" fill="var(--data-color-dim)" />
|
<path d="M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z" fill="var(--data-color-dim)" />
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
@@ -937,6 +938,24 @@
|
|||||||
margin-top: -5px;
|
margin-top: -5px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
||||||
|
&.separator-bottom::after,
|
||||||
|
&.separator-top::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background: var(--color-8-uppergray);
|
||||||
|
height: 1px;
|
||||||
|
left: -4px;
|
||||||
|
right: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.separator-bottom::after {
|
||||||
|
bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.separator-top::before {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.import {
|
&.import {
|
||||||
right: calc(100% - var(--offset-left) * 24px);
|
right: calc(100% - var(--offset-left) * 24px);
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-64
@@ -12,12 +12,6 @@ export class JsMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
|
const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] }));
|
||||||
const ImportsToVec2Array = Transform(({ obj: { imports } }: { obj: { imports: [FrontendGraphOutput, number, number][] } }) =>
|
|
||||||
imports.map(([outputMetadata, x, y]) => ({ outputMetadata, position: { x, y } })),
|
|
||||||
);
|
|
||||||
const ExportsToVec2Array = Transform(({ obj: { exports } }: { obj: { exports: [FrontendGraphInput, number, number][] } }) =>
|
|
||||||
exports.map(([inputMetadata, x, y]) => ({ inputMetadata, position: { x, y } })),
|
|
||||||
);
|
|
||||||
|
|
||||||
// const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) }));
|
// const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) }));
|
||||||
|
|
||||||
@@ -58,17 +52,17 @@ export class UpdateContextMenuInformation extends JsMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateImportsExports extends JsMessage {
|
export class UpdateImportsExports extends JsMessage {
|
||||||
@ImportsToVec2Array
|
readonly imports!: (FrontendGraphOutput | undefined)[];
|
||||||
readonly imports!: { outputMetadata: FrontendGraphOutput; position: XY }[];
|
|
||||||
|
|
||||||
@ExportsToVec2Array
|
readonly exports!: (FrontendGraphInput | undefined)[];
|
||||||
readonly exports!: { inputMetadata: FrontendGraphInput; position: XY }[];
|
|
||||||
|
|
||||||
@TupleToVec2
|
@TupleToVec2
|
||||||
readonly addImport!: XY | undefined;
|
readonly importPosition!: XY;
|
||||||
|
|
||||||
@TupleToVec2
|
@TupleToVec2
|
||||||
readonly addExport!: XY | undefined;
|
readonly exportPosition!: XY;
|
||||||
|
|
||||||
|
readonly addImportExport!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateInSelectedNetwork extends JsMessage {
|
export class UpdateInSelectedNetwork extends JsMessage {
|
||||||
@@ -194,29 +188,6 @@ export type ContextMenuInformation = {
|
|||||||
|
|
||||||
export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color";
|
export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color";
|
||||||
|
|
||||||
export class Node {
|
|
||||||
readonly index!: bigint;
|
|
||||||
// Omitted if this Node is an Import or Export to/from the node network
|
|
||||||
readonly nodeId?: bigint;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreateOutputConnectorOptional = Transform(({ obj }) => {
|
|
||||||
if (obj.connectedTo == undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (obj.connectedTo?.export !== undefined) {
|
|
||||||
return { index: obj.connectedTo?.export };
|
|
||||||
} else if (obj.connectedTo?.import !== undefined) {
|
|
||||||
return { index: obj.connectedTo?.import };
|
|
||||||
} else {
|
|
||||||
if (obj.connectedTo?.node.inputIndex !== undefined) {
|
|
||||||
return { nodeId: obj.connectedTo?.node.nodeId, index: obj.connectedTo?.node.inputIndex };
|
|
||||||
} else {
|
|
||||||
return { nodeId: obj.connectedTo?.node.nodeId, index: obj.connectedTo?.node.outputIndex };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export class FrontendGraphInput {
|
export class FrontendGraphInput {
|
||||||
readonly dataType!: FrontendGraphDataType;
|
readonly dataType!: FrontendGraphDataType;
|
||||||
|
|
||||||
@@ -228,28 +199,9 @@ export class FrontendGraphInput {
|
|||||||
|
|
||||||
readonly validTypes!: string[];
|
readonly validTypes!: string[];
|
||||||
|
|
||||||
@CreateOutputConnectorOptional
|
readonly connectedTo!: string;
|
||||||
connectedTo!: Node | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateInputConnectorArray = Transform(({ obj }) => {
|
|
||||||
const newInputConnectors: Node[] = [];
|
|
||||||
obj.connectedTo.forEach((connector: any) => {
|
|
||||||
if (connector.export !== undefined) {
|
|
||||||
newInputConnectors.push({ index: connector.export });
|
|
||||||
} else if (connector.import !== undefined) {
|
|
||||||
newInputConnectors.push({ index: connector.import });
|
|
||||||
} else {
|
|
||||||
if (connector.node.inputIndex !== undefined) {
|
|
||||||
newInputConnectors.push({ nodeId: connector.node.nodeId, index: connector.node.inputIndex });
|
|
||||||
} else {
|
|
||||||
newInputConnectors.push({ nodeId: connector.node.nodeId, index: connector.node.outputIndex });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newInputConnectors;
|
|
||||||
});
|
|
||||||
|
|
||||||
export class FrontendGraphOutput {
|
export class FrontendGraphOutput {
|
||||||
readonly dataType!: FrontendGraphDataType;
|
readonly dataType!: FrontendGraphDataType;
|
||||||
|
|
||||||
@@ -259,8 +211,7 @@ export class FrontendGraphOutput {
|
|||||||
|
|
||||||
readonly resolvedType!: string;
|
readonly resolvedType!: string;
|
||||||
|
|
||||||
@CreateInputConnectorArray
|
readonly connectedTo!: string[];
|
||||||
connectedTo!: Node[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FrontendNode {
|
export class FrontendNode {
|
||||||
@@ -274,20 +225,20 @@ export class FrontendNode {
|
|||||||
|
|
||||||
readonly displayName!: string;
|
readonly displayName!: string;
|
||||||
|
|
||||||
@Type(() => FrontendGraphInput)
|
|
||||||
readonly primaryInput!: FrontendGraphInput | undefined;
|
readonly primaryInput!: FrontendGraphInput | undefined;
|
||||||
|
|
||||||
@Type(() => FrontendGraphInput)
|
|
||||||
readonly exposedInputs!: FrontendGraphInput[];
|
readonly exposedInputs!: FrontendGraphInput[];
|
||||||
|
|
||||||
@Type(() => FrontendGraphOutput)
|
|
||||||
readonly primaryOutput!: FrontendGraphOutput | undefined;
|
readonly primaryOutput!: FrontendGraphOutput | undefined;
|
||||||
|
|
||||||
@Type(() => FrontendGraphOutput)
|
|
||||||
readonly exposedOutputs!: FrontendGraphOutput[];
|
readonly exposedOutputs!: FrontendGraphOutput[];
|
||||||
|
|
||||||
|
readonly primaryInputConnectedToLayer!: boolean;
|
||||||
|
|
||||||
|
readonly primaryOutputConnectedToLayer!: boolean;
|
||||||
|
|
||||||
@TupleToVec2
|
@TupleToVec2
|
||||||
readonly position!: XY | undefined;
|
readonly position!: XY;
|
||||||
|
|
||||||
// TODO: Store field for the width of the left node chain
|
// TODO: Store field for the width of the left node chain
|
||||||
|
|
||||||
@@ -298,8 +249,6 @@ export class FrontendNode {
|
|||||||
readonly unlocked!: boolean;
|
readonly unlocked!: boolean;
|
||||||
|
|
||||||
readonly errors!: string | undefined;
|
readonly errors!: string | undefined;
|
||||||
|
|
||||||
readonly uiOnly!: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FrontendNodeType {
|
export class FrontendNodeType {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
import { type Editor } from "@graphite/editor";
|
import { type Editor } from "@graphite/editor";
|
||||||
import type { FrontendGraphOutput, FrontendGraphInput } from "@graphite/messages";
|
|
||||||
import {
|
import {
|
||||||
type Box,
|
type Box,
|
||||||
type FrontendClickTargets,
|
type FrontendClickTargets,
|
||||||
@@ -37,10 +36,7 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
layerWidths: new Map<bigint, number>(),
|
layerWidths: new Map<bigint, number>(),
|
||||||
chainWidths: new Map<bigint, number>(),
|
chainWidths: new Map<bigint, number>(),
|
||||||
hasLeftInputWire: new Map<bigint, boolean>(),
|
hasLeftInputWire: new Map<bigint, boolean>(),
|
||||||
imports: [] as { outputMetadata: FrontendGraphOutput; position: { x: number; y: number } }[],
|
updateImportsExports: undefined as UpdateImportsExports | undefined,
|
||||||
exports: [] as { inputMetadata: FrontendGraphInput; position: { x: number; y: number } }[],
|
|
||||||
addImport: undefined as { x: number; y: number } | undefined,
|
|
||||||
addExport: undefined as { x: number; y: number } | undefined,
|
|
||||||
nodes: new Map<bigint, FrontendNode>(),
|
nodes: new Map<bigint, FrontendNode>(),
|
||||||
visibleNodes: new Set<bigint>(),
|
visibleNodes: new Set<bigint>(),
|
||||||
/// The index is the exposed input index. The exports have a first key value of u32::MAX.
|
/// The index is the exposed input index. The exports have a first key value of u32::MAX.
|
||||||
@@ -96,10 +92,7 @@ export function createNodeGraphState(editor: Editor) {
|
|||||||
});
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateImportsExports, (updateImportsExports) => {
|
editor.subscriptions.subscribeJsMessage(UpdateImportsExports, (updateImportsExports) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.imports = updateImportsExports.imports;
|
state.updateImportsExports = updateImportsExports;
|
||||||
state.exports = updateImportsExports.exports;
|
|
||||||
state.addImport = updateImportsExports.addImport;
|
|
||||||
state.addExport = updateImportsExports.addExport;
|
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -314,6 +314,30 @@ impl EditorHandle {
|
|||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = addPrimaryImport)]
|
||||||
|
pub fn add_primary_import(&self) {
|
||||||
|
self.dispatch(DocumentMessage::AddTransaction);
|
||||||
|
self.dispatch(NodeGraphMessage::AddPrimaryImport);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = addSecondaryImport)]
|
||||||
|
pub fn add_secondary_import(&self) {
|
||||||
|
self.dispatch(DocumentMessage::AddTransaction);
|
||||||
|
self.dispatch(NodeGraphMessage::AddSecondaryImport);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = addPrimaryExport)]
|
||||||
|
pub fn add_primary_export(&self) {
|
||||||
|
self.dispatch(DocumentMessage::AddTransaction);
|
||||||
|
self.dispatch(NodeGraphMessage::AddPrimaryExport);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = addSecondaryExport)]
|
||||||
|
pub fn add_secondary_export(&self) {
|
||||||
|
self.dispatch(DocumentMessage::AddTransaction);
|
||||||
|
self.dispatch(NodeGraphMessage::AddSecondaryExport);
|
||||||
|
}
|
||||||
|
|
||||||
/// Toggles minimizing or restoring down the application window
|
/// Toggles minimizing or restoring down the application window
|
||||||
#[wasm_bindgen(js_name = appWindowMaximize)]
|
#[wasm_bindgen(js_name = appWindowMaximize)]
|
||||||
pub fn app_window_maximize(&self) {
|
pub fn app_window_maximize(&self) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2
|
|||||||
|
|
||||||
/// The X or Y component of a vec2.
|
/// The X or Y component of a vec2.
|
||||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny, node_macro::ChoiceType, specta::Type, serde::Serialize, serde::Deserialize)]
|
||||||
#[widget(Dropdown)]
|
#[widget(Radio)]
|
||||||
pub enum XY {
|
pub enum XY {
|
||||||
#[default]
|
#[default]
|
||||||
X,
|
X,
|
||||||
|
|||||||
Reference in New Issue
Block a user