mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Introduce the NetworkView read cursor with typed errors and convert the network queries onto it (#4376)
* Introduce a NetworkView cursor with typed errors and convert the primitive queries to it * Convert the composite name, selection, and eligibility queries onto NetworkView
This commit is contained in:
committed by
Dennis Kobert
parent
e68756a47b
commit
bb447eab1a
@@ -13,8 +13,10 @@ mod structure;
|
||||
mod types;
|
||||
#[cfg(test)]
|
||||
mod validation;
|
||||
mod view;
|
||||
|
||||
pub use types::*;
|
||||
pub use view::{NetworkError, NetworkView};
|
||||
|
||||
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations};
|
||||
use super::misc::PTZ;
|
||||
|
||||
@@ -110,15 +110,7 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node) = self.document_node(node_id, network_path) else {
|
||||
log::error!("Could not get node {node_id} in is_eligible_to_be_layer");
|
||||
return false;
|
||||
};
|
||||
let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count();
|
||||
let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed());
|
||||
let output_count = self.number_of_outputs(node_id, network_path);
|
||||
|
||||
!self.hidden_primary_output(node_id, network_path) && output_count == 1 && (input_count <= 2) && parameters_hidden
|
||||
self.query(network_path, "is_eligible_to_be_layer", |view| view.is_eligible_to_be_layer(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> {
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
use super::*;
|
||||
|
||||
impl NodeNetworkInterface {
|
||||
/// Runs a query against a resolved [`NetworkView`], logging any error at this message-boundary wrapper and mapping it to None.
|
||||
pub(crate) fn query<'a, 'p, T>(&'a self, network_path: &'p [NodeId], caller: &str, query: impl FnOnce(NetworkView<'a, 'p>) -> Result<T, NetworkError>) -> Option<T> {
|
||||
match self.view(network_path).and_then(query) {
|
||||
Ok(value) => Some(value),
|
||||
Err(error) => {
|
||||
log::error!("{error} in {caller}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public immutable getters for the network interface
|
||||
impl NodeNetworkInterface {
|
||||
/// Gets the network of the root document
|
||||
@@ -25,21 +38,11 @@ impl NodeNetworkInterface {
|
||||
|
||||
/// Get the specified document node in the nested network based on node_id and network_path
|
||||
pub fn document_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNode> {
|
||||
let network = self.nested_network(network_path)?;
|
||||
let Some(node_metadata) = network.nodes.get(node_id) else {
|
||||
log::error!("Could not get document node with id {node_id} in network {network_path:?}");
|
||||
return None;
|
||||
};
|
||||
Some(node_metadata)
|
||||
self.query(network_path, "document_node", |view| view.node(node_id))
|
||||
}
|
||||
|
||||
pub fn node_metadata(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> {
|
||||
let network_metadata = self.network_metadata(network_path)?;
|
||||
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(node_id) else {
|
||||
log::error!("Could not get node metadata for node {node_id} in network {network_path:?}");
|
||||
return None;
|
||||
};
|
||||
Some(node_metadata)
|
||||
self.query(network_path, "node_metadata", |view| view.node_metadata(node_id))
|
||||
}
|
||||
|
||||
pub fn document_network_metadata(&self) -> &NodeNetworkMetadata {
|
||||
@@ -69,57 +72,34 @@ impl NodeNetworkInterface {
|
||||
|
||||
/// Get the selected nodes for the network at the network_path
|
||||
pub fn selected_nodes_in_nested_network(&self, network_path: &[NodeId]) -> Option<SelectedNodes> {
|
||||
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in selected_nodes");
|
||||
return None;
|
||||
};
|
||||
self.query(network_path, "selected_nodes_in_nested_network", |view| Ok(view.selected_nodes()))
|
||||
}
|
||||
|
||||
Some(
|
||||
network_metadata
|
||||
.persistent_metadata
|
||||
.selection_undo_history
|
||||
.back()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.filtered_selected_nodes(|node_id| network_metadata.persistent_metadata.node_metadata.contains_key(node_id)),
|
||||
)
|
||||
/// Runs an encapsulating-node query, staying silent for the document network which has no encapsulating node.
|
||||
fn query_encapsulating<'a, 'p, T>(&'a self, network_path: &'p [NodeId], caller: &str, query: impl FnOnce(NetworkView<'a, 'p>) -> Result<T, NetworkError>) -> Option<T> {
|
||||
match self.view(network_path).and_then(query) {
|
||||
Ok(value) => Some(value),
|
||||
Err(NetworkError::NoEncapsulatingNode) => None,
|
||||
Err(error) => {
|
||||
log::error!("{error} in {caller}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the network which the encapsulating node of the currently viewed network is part of. Will always be None in the document network.
|
||||
pub fn encapsulating_network_metadata(&self, network_path: &[NodeId]) -> Option<&NodeNetworkMetadata> {
|
||||
let mut encapsulating_path = network_path.to_vec();
|
||||
encapsulating_path.pop()?;
|
||||
let Some(parent_metadata) = self.network_metadata(&encapsulating_path) else {
|
||||
log::error!("Could not get parent network in encapsulating_node_metadata");
|
||||
return None;
|
||||
};
|
||||
Some(parent_metadata)
|
||||
self.query_encapsulating(network_path, "encapsulating_network_metadata", |view| view.encapsulating().map(|parent| parent.network_metadata()))
|
||||
}
|
||||
|
||||
/// Get the node which encapsulates the currently viewed network. Will always be None in the document network.
|
||||
pub fn encapsulating_node(&self, network_path: &[NodeId]) -> Option<&DocumentNode> {
|
||||
let mut encapsulating_path = network_path.to_vec();
|
||||
let encapsulating_node_id = encapsulating_path.pop()?;
|
||||
let Some(encapsulating_node) = self.document_node(&encapsulating_node_id, &encapsulating_path) else {
|
||||
log::error!("Could not get encapsulating node in encapsulating_node");
|
||||
return None;
|
||||
};
|
||||
Some(encapsulating_node)
|
||||
self.query_encapsulating(network_path, "encapsulating_node", |view| view.encapsulating_node())
|
||||
}
|
||||
|
||||
/// Get the node metadata for the node which encapsulates the currently viewed network. Will always be None in the document network.
|
||||
pub fn encapsulating_node_metadata(&self, network_path: &[NodeId]) -> Option<&DocumentNodeMetadata> {
|
||||
let mut encapsulating_path = network_path.to_vec();
|
||||
let encapsulating_node_id = encapsulating_path.pop()?;
|
||||
let Some(parent_metadata) = self.network_metadata(&encapsulating_path) else {
|
||||
log::error!("Could not get parent network in encapsulating_node_metadata");
|
||||
return None;
|
||||
};
|
||||
let Some(encapsulating_node_metadata) = parent_metadata.persistent_metadata.node_metadata.get(&encapsulating_node_id) else {
|
||||
log::error!("Could not get encapsulating node metadata in encapsulating_node_metadata");
|
||||
return None;
|
||||
};
|
||||
Some(encapsulating_node_metadata)
|
||||
self.query_encapsulating(network_path, "encapsulating_node_metadata", |view| view.encapsulating_node_metadata())
|
||||
}
|
||||
|
||||
/// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself.
|
||||
@@ -150,128 +130,39 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn chain_width(&self, node_id: &NodeId, network_path: &[NodeId]) -> u32 {
|
||||
if self.number_of_displayed_inputs(node_id, network_path) > 1 {
|
||||
let mut last_chain_node_distance = 0_u32;
|
||||
// Iterate upstream from the layer, and get the number of nodes distance to the last node with Position::Chain
|
||||
for (index, node_id) in self
|
||||
.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalPrimaryOutputFlow)
|
||||
.skip(1)
|
||||
.enumerate()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
// Check if the node is positioned as a chain
|
||||
if self.is_chain(&node_id, network_path) {
|
||||
last_chain_node_distance = (index as u32) + 1;
|
||||
} else {
|
||||
return last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1
|
||||
} else {
|
||||
// Layer with no inputs has no chain
|
||||
0
|
||||
}
|
||||
self.query(network_path, "chain_width", |view| Ok(view.chain_width(node_id))).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Check if the specified node id is connected to the output
|
||||
pub fn connected_to_output(&self, target_node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(network) = self.nested_network(network_path) else {
|
||||
log::error!("Could not get network in connected_to_output");
|
||||
return false;
|
||||
};
|
||||
// If the node is the output then return true
|
||||
if network
|
||||
.exports
|
||||
.iter()
|
||||
.any(|export| if let NodeInput::Node { node_id, .. } = export { node_id == target_node_id } else { false })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the outputs
|
||||
let mut stack = network
|
||||
.exports
|
||||
.iter()
|
||||
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { network.nodes.get(node_id) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let mut already_visited = HashSet::new();
|
||||
already_visited.extend(
|
||||
network
|
||||
.exports
|
||||
.iter()
|
||||
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }),
|
||||
);
|
||||
|
||||
while let Some(node) = stack.pop() {
|
||||
for input in &node.inputs {
|
||||
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
||||
// Skip if already viewed
|
||||
if already_visited.contains(&ref_id) {
|
||||
continue;
|
||||
}
|
||||
// If the target node is used as input then return true
|
||||
if ref_id == *target_node_id {
|
||||
return true;
|
||||
}
|
||||
// Add the referenced node to the stack
|
||||
let Some(ref_node) = network.nodes.get(&ref_id) else {
|
||||
continue;
|
||||
};
|
||||
already_visited.insert(ref_id);
|
||||
stack.push(ref_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
self.query(network_path, "connected_to_output", |view| Ok(view.connected_to_output(target_node_id))).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn number_of_imports(&self, network_path: &[NodeId]) -> usize {
|
||||
// TODO: Use network.import_types.len()
|
||||
if let Some(encapsulating_node) = self.encapsulating_node(network_path) {
|
||||
encapsulating_node.inputs.len()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
self.view(network_path).map(|view| view.number_of_imports()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn number_of_exports(&self, network_path: &[NodeId]) -> usize {
|
||||
if let Some(network) = self.nested_network(network_path) { network.exports.len() } else { 0 }
|
||||
self.view(network_path).map(|view| view.number_of_exports()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn number_of_displayed_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
|
||||
let Some(node) = self.document_node(node_id, network_path) else {
|
||||
log::error!("Could not get node {node_id} in number_of_displayed_inputs");
|
||||
return 0;
|
||||
};
|
||||
node.inputs.iter().filter(|input| input.is_exposed()).count()
|
||||
self.query(network_path, "number_of_displayed_inputs", |view| view.number_of_displayed_inputs(node_id))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn number_of_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
|
||||
let Some(node) = self.document_node(node_id, network_path) else {
|
||||
log::error!("Could not get node {node_id} in number_of_inputs");
|
||||
return 0;
|
||||
};
|
||||
node.inputs.len()
|
||||
self.query(network_path, "number_of_inputs", |view| view.number_of_inputs(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Whether the node has an exposed input at index 0 to accept the horizontal flow from upstream.
|
||||
/// A node without one (e.g. a generator) can only be the most-upstream node in a chain.
|
||||
pub fn has_primary_input(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
self.input_from_connector(&InputConnector::node(*node_id, 0), network_path).is_some_and(|input| input.is_exposed())
|
||||
self.view(network_path).and_then(|view| view.has_primary_input(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn number_of_outputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize {
|
||||
let Some(implementation) = self.implementation(node_id, network_path) else {
|
||||
log::error!("Could not get node {node_id} in number_of_outputs");
|
||||
return 0;
|
||||
};
|
||||
match &implementation {
|
||||
DocumentNodeImplementation::ProtoNode(_) => 1,
|
||||
DocumentNodeImplementation::Network(nested_network) => nested_network.exports.len(),
|
||||
DocumentNodeImplementation::Extract => 1,
|
||||
}
|
||||
self.query(network_path, "number_of_outputs", |view| view.number_of_outputs(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Creates a copy for each node by disconnecting nodes which are not connected to other copied nodes.
|
||||
@@ -350,19 +241,7 @@ impl NodeNetworkInterface {
|
||||
|
||||
/// Create a node template from an existing node.
|
||||
pub fn create_node_template(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeTemplate> {
|
||||
let Some(node) = self.document_node(node_id, network_path) else {
|
||||
log::error!("Could not get node {node_id} in create_node_template");
|
||||
return None;
|
||||
};
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path).cloned() else {
|
||||
log::error!("Could not get node_metadata in create_node_template");
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(NodeTemplate {
|
||||
persistent_node_metadata: node_metadata.persistent_metadata,
|
||||
document_node: node.clone(),
|
||||
})
|
||||
self.query(network_path, "create_node_template", |view| view.create_node_template(node_id))
|
||||
}
|
||||
|
||||
/// Converts all node id inputs to a new id based on a HashMap.
|
||||
@@ -388,19 +267,14 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> {
|
||||
let Some(network) = self.nested_network(network_path) else {
|
||||
log::error!("Could not get network in input_from_connector");
|
||||
return None;
|
||||
};
|
||||
match input_connector {
|
||||
InputConnector::Node { node_id, input_index } => {
|
||||
let Some(node) = network.nodes.get(node_id) else {
|
||||
log::error!("Could not get node {node_id} in input_from_connector");
|
||||
return None;
|
||||
};
|
||||
node.inputs.get(*input_index)
|
||||
match self.view(network_path).and_then(|view| view.input(input_connector)) {
|
||||
Ok(input) => Some(input),
|
||||
// An out-of-range input index is an expected lookup miss, not an error worth logging
|
||||
Err(NetworkError::InputNotFound { .. }) => None,
|
||||
Err(error) => {
|
||||
log::error!("{error} in input_from_connector");
|
||||
None
|
||||
}
|
||||
InputConnector::Export(export_index) => network.exports.get(*export_index),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -806,86 +680,40 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
pub fn previewing(&self, network_path: &[NodeId]) -> Previewing {
|
||||
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in previewing");
|
||||
return Previewing::No;
|
||||
};
|
||||
network_metadata.persistent_metadata.previewing
|
||||
self.query(network_path, "previewing", |view| Ok(view.previewing())).unwrap_or(Previewing::No)
|
||||
}
|
||||
|
||||
/// Returns the root node (the node that the solid line is connect to), or None if no nodes are connected to the output
|
||||
pub fn root_node(&self, network_path: &[NodeId]) -> Option<RootNode> {
|
||||
let Some(network) = self.nested_network(network_path) else {
|
||||
log::error!("Could not get network in root_node");
|
||||
return None;
|
||||
};
|
||||
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in root_node");
|
||||
return None;
|
||||
};
|
||||
match &network_metadata.persistent_metadata.previewing {
|
||||
Previewing::Yes { root_node_to_restore } => *root_node_to_restore,
|
||||
Previewing::No => network.exports.first().and_then(|export| {
|
||||
if let NodeInput::Node { node_id, output_index, .. } = export {
|
||||
Some(RootNode {
|
||||
node_id: *node_id,
|
||||
output_index: *output_index,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
}
|
||||
self.query(network_path, "root_node", |view| Ok(view.root_node())).flatten()
|
||||
}
|
||||
|
||||
pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<DefinitionIdentifier> {
|
||||
let Some(document_node) = self.document_node(node_id, network_path) else {
|
||||
log::error!("Could not get document_node for node in reference: {node_id:?}");
|
||||
return None;
|
||||
};
|
||||
match &document_node.implementation {
|
||||
DocumentNodeImplementation::Network(_) => {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get reference for node in reference: {node_id:?}");
|
||||
return None;
|
||||
};
|
||||
let Some(network_metadata) = node_metadata.persistent_metadata.network_metadata.as_ref() else {
|
||||
log::error!("Network metadata must exist for network node in reference: {node_id:?}");
|
||||
return None;
|
||||
};
|
||||
network_metadata.persistent_metadata.reference.clone().map(DefinitionIdentifier::Network)
|
||||
}
|
||||
DocumentNodeImplementation::ProtoNode(protonode_id) => Some(DefinitionIdentifier::ProtoNode(protonode_id.clone())),
|
||||
_ => None,
|
||||
}
|
||||
self.query(network_path, "reference", |view| view.reference(node_id)).flatten()
|
||||
}
|
||||
|
||||
pub fn implementation(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeImplementation> {
|
||||
let Some(node) = self.document_node(node_id, network_path) else {
|
||||
log::error!("Could not get implementation");
|
||||
return None;
|
||||
};
|
||||
Some(&node.implementation)
|
||||
self.query(network_path, "implementation", |view| view.implementation(node_id))
|
||||
}
|
||||
|
||||
pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str, network_path: &[NodeId]) -> Option<&Value> {
|
||||
let metadata = self
|
||||
.node_metadata(node_id, network_path)
|
||||
.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;
|
||||
metadata.persistent_metadata.input_data.get(key)
|
||||
match self.view(network_path).and_then(|view| view.input_data(node_id, index, key)) {
|
||||
Ok(value) => value,
|
||||
// A missing input slot is the caller probing optional data, but any other failure is a corrupt network worth logging
|
||||
Err(NetworkError::InputNotFound { .. }) => None,
|
||||
Err(error) => {
|
||||
log::error!("{error} in input_data");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputPersistentMetadata> {
|
||||
let metadata = self
|
||||
.node_metadata(node_id, network_path)
|
||||
.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;
|
||||
Some(&metadata.persistent_metadata)
|
||||
self.view(network_path).ok().and_then(|view| view.persistent_input_metadata(node_id, index).ok())
|
||||
}
|
||||
|
||||
pub(crate) fn transient_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputTransientMetadata> {
|
||||
let metadata = self
|
||||
.node_metadata(node_id, network_path)
|
||||
.and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?;
|
||||
Some(&metadata.transient_metadata)
|
||||
self.view(network_path).ok().and_then(|view| view.transient_input_metadata(node_id, index).ok())
|
||||
}
|
||||
|
||||
pub fn set_input_override(&mut self, node_id: &NodeId, index: usize, widget_override: Option<String>, network_path: &[NodeId]) {
|
||||
@@ -916,93 +744,35 @@ impl NodeNetworkInterface {
|
||||
|
||||
/// Returns the display name of the node. If the display name is empty, it will return "Untitled Node" or "Untitled Layer" depending on the node type.
|
||||
pub fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
|
||||
let is_layer = self.is_layer(node_id, network_path);
|
||||
|
||||
let display_name = if let Some(node_metadata) = self.node_metadata(node_id, network_path) {
|
||||
node_metadata.persistent_metadata.display_name.clone()
|
||||
} else {
|
||||
log::error!("Could not get node_metadata in display_name");
|
||||
String::new()
|
||||
};
|
||||
|
||||
if display_name.is_empty() {
|
||||
if is_layer {
|
||||
"Untitled Layer".to_string()
|
||||
} else {
|
||||
// TODO: Have this displayed in italics in the UI
|
||||
self.implementation_name(node_id, network_path)
|
||||
}
|
||||
} else {
|
||||
display_name
|
||||
}
|
||||
self.query(network_path, "display_name", |view| Ok(view.display_name(node_id)))
|
||||
.unwrap_or_else(|| "Custom Node".to_string())
|
||||
}
|
||||
|
||||
/// The uneditable name in the Properties panel which represents the function name of the node implementation.
|
||||
pub fn implementation_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
|
||||
self.reference(node_id, network_path)
|
||||
.map(|identifier| identifier.implementation_name_from_identifier())
|
||||
.unwrap_or("Custom Node".to_string())
|
||||
self.query(network_path, "implementation_name", |view| Ok(view.implementation_name(node_id)))
|
||||
.unwrap_or_else(|| "Custom Node".to_string())
|
||||
}
|
||||
|
||||
pub fn is_locked(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get persistent node metadata in is_locked for node {node_id}");
|
||||
return false;
|
||||
};
|
||||
node_metadata.persistent_metadata.locked
|
||||
self.query(network_path, "is_locked", |view| view.is_locked(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_pinned(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get persistent node metadata in is_pinned for node {node_id}");
|
||||
return false;
|
||||
};
|
||||
node_metadata.persistent_metadata.pinned
|
||||
self.query(network_path, "is_pinned", |view| view.is_pinned(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last.
|
||||
pub fn ordered_pinned_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
|
||||
let Some(network) = self.nested_network(network_path) else { return Vec::new() };
|
||||
|
||||
let order = self
|
||||
.network_metadata(network_path)
|
||||
.map(|network_metadata| network_metadata.persistent_metadata.pinned_node_order.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Follow the saved order, keeping only nodes that still exist and are still pinned
|
||||
let mut pinned_nodes = order
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|node_id| network.nodes.contains_key(node_id) && self.is_pinned(node_id, network_path))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Append any pinned nodes missing from the saved order at the end
|
||||
let mut unordered = network
|
||||
.nodes
|
||||
.keys()
|
||||
.copied()
|
||||
.filter(|node_id| self.is_pinned(node_id, network_path) && !order.contains(node_id))
|
||||
.collect::<Vec<_>>();
|
||||
unordered.sort();
|
||||
pinned_nodes.extend(unordered);
|
||||
|
||||
pinned_nodes
|
||||
self.view(network_path).map(|view| view.ordered_pinned_nodes()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node) = self.document_node(node_id, network_path) else {
|
||||
log::error!("Could not get node in is_visible");
|
||||
return false;
|
||||
};
|
||||
node.visible
|
||||
self.query(network_path, "is_visible", |view| view.is_visible(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get nested node_metadata in is_layer");
|
||||
return false;
|
||||
};
|
||||
node_metadata.persistent_metadata.is_layer()
|
||||
self.query(network_path, "is_layer", |view| view.is_layer(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn primary_output_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
@@ -1022,73 +792,39 @@ impl NodeNetworkInterface {
|
||||
downstream_nodes.iter().any(|node_id| self.is_layer(node_id, network_path))
|
||||
}
|
||||
|
||||
pub fn primary_input_connected_to_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
self.input_from_connector(&InputConnector::node(*node_id, 0), network_path)
|
||||
.and_then(|input| input.as_node())
|
||||
.is_some_and(|node_id| self.is_layer(&node_id, network_path))
|
||||
pub fn primary_input_connected_to_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
self.query(network_path, "primary_input_connected_to_layer", |view| Ok(view.primary_input_connected_to_layer(node_id)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn hidden_primary_export(&self, network_path: &[NodeId]) -> bool {
|
||||
let Some((node_id, network_path)) = network_path.split_last() else {
|
||||
// The document network does not have a hidden primary export
|
||||
return false;
|
||||
};
|
||||
self.hidden_primary_output(node_id, network_path)
|
||||
self.view(network_path).map(|view| view.hidden_primary_export()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn hidden_primary_output(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
match self.implementation(node_id, network_path) {
|
||||
Some(DocumentNodeImplementation::Network(network)) => network.exports.first().is_none_or(|input| !input.is_exposed()),
|
||||
_ => false,
|
||||
}
|
||||
self.view(network_path).and_then(|view| view.hidden_primary_output(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn hidden_primary_import(&self, network_path: &[NodeId]) -> bool {
|
||||
let Some((encapsulating_node_id, encapsulating_path)) = network_path.split_last() else {
|
||||
return false;
|
||||
};
|
||||
self.input_from_connector(&InputConnector::node(*encapsulating_node_id, 0), encapsulating_path)
|
||||
.is_some_and(|input| !input.is_exposed())
|
||||
self.view(network_path).map(|view| view.hidden_primary_import()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_absolute(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get node_metadata in is_absolute");
|
||||
return false;
|
||||
};
|
||||
match &node_metadata.persistent_metadata.node_type_metadata {
|
||||
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Absolute(_)),
|
||||
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Absolute(_)),
|
||||
}
|
||||
self.query(network_path, "is_absolute", |view| view.is_absolute(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_chain(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get node_metadata in is_chain");
|
||||
return false;
|
||||
};
|
||||
match &node_metadata.persistent_metadata.node_type_metadata {
|
||||
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Chain),
|
||||
_ => false,
|
||||
}
|
||||
self.query(network_path, "is_chain", |view| view.is_chain(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_stack(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
|
||||
log::error!("Could not get node_metadata in is_stack");
|
||||
return false;
|
||||
};
|
||||
match &node_metadata.persistent_metadata.node_type_metadata {
|
||||
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Stack(_)),
|
||||
_ => false,
|
||||
}
|
||||
self.query(network_path, "is_stack", |view| view.is_stack(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene.
|
||||
/// Callers that care about scene membership should source their layers from the document structure or check connectivity separately.
|
||||
pub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||
self.reference(node_id, network_path)
|
||||
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into()))
|
||||
self.view(network_path).map(|view| view.is_artboard(node_id)).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// All artboard layers that participate in the scene, excluding disconnected Artboard nodes.
|
||||
@@ -1290,34 +1026,18 @@ impl NodeNetworkInterface {
|
||||
}
|
||||
|
||||
/// Gives an iterator to all nodes connected to the given nodes by all inputs (primary or primary + secondary depending on `only_follow_primary` choice), traversing backwards upstream starting from the given node's inputs.
|
||||
pub fn upstream_flow_back_from_nodes<'a>(&'a self, mut node_ids: Vec<NodeId>, network_path: &'a [NodeId], mut flow_type: FlowType) -> impl Iterator<Item = NodeId> + 'a {
|
||||
let (Some(network), Some(network_metadata)) = (self.nested_network(network_path), self.network_metadata(network_path)) else {
|
||||
log::error!("Could not get network or network_metadata in upstream_flow_back_from_nodes");
|
||||
return FlowIter {
|
||||
stack: Vec::new(),
|
||||
network: self.document_network(),
|
||||
network_metadata: &self.network_metadata,
|
||||
flow_type: FlowType::UpstreamFlow,
|
||||
};
|
||||
};
|
||||
if matches!(flow_type, FlowType::LayerChildrenUpstreamFlow) {
|
||||
node_ids = node_ids
|
||||
.iter()
|
||||
.filter_map(move |node_id| {
|
||||
if self.is_layer(node_id, network_path) {
|
||||
network.nodes.get(node_id).and_then(|node| node.inputs.get(1)).and_then(|input| input.as_node())
|
||||
} else {
|
||||
Some(*node_id)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
flow_type = FlowType::UpstreamFlow;
|
||||
};
|
||||
FlowIter {
|
||||
stack: node_ids,
|
||||
network,
|
||||
network_metadata,
|
||||
flow_type,
|
||||
pub fn upstream_flow_back_from_nodes<'a>(&'a self, node_ids: Vec<NodeId>, network_path: &'a [NodeId], flow_type: FlowType) -> impl Iterator<Item = NodeId> + 'a {
|
||||
match self.view(network_path) {
|
||||
Ok(view) => view.upstream_flow(node_ids, flow_type),
|
||||
Err(error) => {
|
||||
log::error!("{error} in upstream_flow_back_from_nodes");
|
||||
FlowIter {
|
||||
stack: Vec::new(),
|
||||
network: self.document_network(),
|
||||
network_metadata: &self.network_metadata,
|
||||
flow_type: FlowType::UpstreamFlow,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
use super::*;
|
||||
|
||||
/// A failure to resolve part of the network or its metadata, returned by [`NetworkView`] accessors.
|
||||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||||
pub enum NetworkError {
|
||||
#[error("Network not found at path {path:?}")]
|
||||
NetworkNotFound { path: Vec<NodeId> },
|
||||
#[error("Node {node_id} not found")]
|
||||
NodeNotFound { node_id: NodeId },
|
||||
#[error("Node metadata for {node_id} not found")]
|
||||
NodeMetadataNotFound { node_id: NodeId },
|
||||
#[error("Nested network metadata for node {node_id} not found")]
|
||||
NestedMetadataNotFound { node_id: NodeId },
|
||||
#[error("Input {connector:?} not found")]
|
||||
InputNotFound { connector: InputConnector },
|
||||
#[error("The document network has no encapsulating node")]
|
||||
NoEncapsulatingNode,
|
||||
}
|
||||
|
||||
/// A read cursor over one network nesting level, resolving the network and its parallel metadata once instead of per query.
|
||||
/// Obtained from [`NodeNetworkInterface::view`]; accessors return typed errors that callers at the message boundary may log.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NetworkView<'a, 'p> {
|
||||
pub(crate) interface: &'a NodeNetworkInterface,
|
||||
pub(crate) path: &'p [NodeId],
|
||||
pub(crate) network: &'a NodeNetwork,
|
||||
pub(crate) metadata: &'a NodeNetworkMetadata,
|
||||
}
|
||||
|
||||
impl NodeNetworkInterface {
|
||||
/// Resolves a read cursor for the network at `network_path`, walking the nested network and metadata trees once.
|
||||
pub fn view<'a, 'p>(&'a self, network_path: &'p [NodeId]) -> Result<NetworkView<'a, 'p>, NetworkError> {
|
||||
let network = self
|
||||
.document_network()
|
||||
.nested_network(network_path)
|
||||
.ok_or_else(|| NetworkError::NetworkNotFound { path: network_path.to_vec() })?;
|
||||
let metadata = self
|
||||
.document_network_metadata()
|
||||
.nested_metadata(network_path)
|
||||
.ok_or_else(|| NetworkError::NetworkNotFound { path: network_path.to_vec() })?;
|
||||
|
||||
Ok(NetworkView {
|
||||
interface: self,
|
||||
path: network_path,
|
||||
network,
|
||||
metadata,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'p> NetworkView<'a, 'p> {
|
||||
pub fn network(&self) -> &'a NodeNetwork {
|
||||
self.network
|
||||
}
|
||||
|
||||
pub fn network_metadata(&self) -> &'a NodeNetworkMetadata {
|
||||
self.metadata
|
||||
}
|
||||
|
||||
pub fn node(&self, node_id: &NodeId) -> Result<&'a DocumentNode, NetworkError> {
|
||||
self.network.nodes.get(node_id).ok_or(NetworkError::NodeNotFound { node_id: *node_id })
|
||||
}
|
||||
|
||||
pub fn node_metadata(&self, node_id: &NodeId) -> Result<&'a DocumentNodeMetadata, NetworkError> {
|
||||
self.metadata
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.get(node_id)
|
||||
.ok_or(NetworkError::NodeMetadataNotFound { node_id: *node_id })
|
||||
}
|
||||
|
||||
pub fn input(&self, input_connector: &InputConnector) -> Result<&'a NodeInput, NetworkError> {
|
||||
let input = match input_connector {
|
||||
InputConnector::Node { node_id, input_index } => self.node(node_id)?.inputs.get(*input_index),
|
||||
InputConnector::Export(export_index) => self.network.exports.get(*export_index),
|
||||
};
|
||||
input.ok_or(NetworkError::InputNotFound { connector: *input_connector })
|
||||
}
|
||||
|
||||
/// The view over the network which contains this network's encapsulating node, or an error in the document network.
|
||||
pub fn encapsulating(&self) -> Result<NetworkView<'a, 'p>, NetworkError> {
|
||||
let (_, encapsulating_path) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?;
|
||||
self.interface.view(encapsulating_path)
|
||||
}
|
||||
|
||||
/// The node which encapsulates this network, or an error in the document network.
|
||||
pub fn encapsulating_node(&self) -> Result<&'a DocumentNode, NetworkError> {
|
||||
let (node_id, _) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?;
|
||||
self.encapsulating()?.node(node_id)
|
||||
}
|
||||
|
||||
/// The metadata of the node which encapsulates this network, or an error in the document network.
|
||||
pub fn encapsulating_node_metadata(&self) -> Result<&'a DocumentNodeMetadata, NetworkError> {
|
||||
let (node_id, _) = self.path.split_last().ok_or(NetworkError::NoEncapsulatingNode)?;
|
||||
self.encapsulating()?.node_metadata(node_id)
|
||||
}
|
||||
|
||||
pub fn implementation(&self, node_id: &NodeId) -> Result<&'a DocumentNodeImplementation, NetworkError> {
|
||||
Ok(&self.node(node_id)?.implementation)
|
||||
}
|
||||
|
||||
pub fn reference(&self, node_id: &NodeId) -> Result<Option<DefinitionIdentifier>, NetworkError> {
|
||||
match self.implementation(node_id)? {
|
||||
DocumentNodeImplementation::Network(_) => {
|
||||
let node_metadata = self.node_metadata(node_id)?;
|
||||
let network_metadata = node_metadata
|
||||
.persistent_metadata
|
||||
.network_metadata
|
||||
.as_ref()
|
||||
.ok_or(NetworkError::NestedMetadataNotFound { node_id: *node_id })?;
|
||||
Ok(network_metadata.persistent_metadata.reference.clone().map(DefinitionIdentifier::Network))
|
||||
}
|
||||
DocumentNodeImplementation::ProtoNode(protonode_id) => Ok(Some(DefinitionIdentifier::ProtoNode(protonode_id.clone()))),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_layer(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(self.node_metadata(node_id)?.persistent_metadata.is_layer())
|
||||
}
|
||||
|
||||
/// Whether the node is an Artboard node by identity, regardless of whether it currently participates in the scene.
|
||||
pub fn is_artboard(&self, node_id: &NodeId) -> bool {
|
||||
self.reference(node_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into()))
|
||||
}
|
||||
|
||||
/// The uneditable name in the Properties panel which represents the function name of the node implementation.
|
||||
pub fn implementation_name(&self, node_id: &NodeId) -> String {
|
||||
self.reference(node_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|identifier| identifier.implementation_name_from_identifier())
|
||||
.unwrap_or("Custom Node".to_string())
|
||||
}
|
||||
|
||||
/// The display name of the node, falling back to "Untitled Layer" or the implementation name when unnamed.
|
||||
pub fn display_name(&self, node_id: &NodeId) -> String {
|
||||
let display_name = self.node_metadata(node_id).map(|metadata| metadata.persistent_metadata.display_name.clone()).unwrap_or_default();
|
||||
|
||||
if display_name.is_empty() {
|
||||
if self.is_layer(node_id).unwrap_or_default() {
|
||||
"Untitled Layer".to_string()
|
||||
} else {
|
||||
// TODO: Have this displayed in italics in the UI
|
||||
self.implementation_name(node_id)
|
||||
}
|
||||
} else {
|
||||
display_name
|
||||
}
|
||||
}
|
||||
|
||||
/// The current selection of this network, filtered to nodes that still exist.
|
||||
pub fn selected_nodes(&self) -> SelectedNodes {
|
||||
self.metadata
|
||||
.persistent_metadata
|
||||
.selection_undo_history
|
||||
.back()
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.filtered_selected_nodes(|node_id| self.metadata.persistent_metadata.node_metadata.contains_key(node_id))
|
||||
}
|
||||
|
||||
pub fn primary_input_connected_to_layer(&self, node_id: &NodeId) -> bool {
|
||||
self.input(&InputConnector::node(*node_id, 0))
|
||||
.ok()
|
||||
.and_then(|input| input.as_node())
|
||||
.is_some_and(|upstream_id| self.is_layer(&upstream_id).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Whether the node's signature fits the layer form: one output, at most two exposed leading inputs, and no exposed parameters.
|
||||
pub fn is_eligible_to_be_layer(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
let node = self.node(node_id)?;
|
||||
let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count();
|
||||
let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed());
|
||||
let output_count = self.number_of_outputs(node_id)?;
|
||||
|
||||
Ok(!self.hidden_primary_output(node_id)? && output_count == 1 && (input_count <= 2) && parameters_hidden)
|
||||
}
|
||||
|
||||
pub fn is_locked(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(self.node_metadata(node_id)?.persistent_metadata.locked)
|
||||
}
|
||||
|
||||
pub fn is_pinned(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(self.node_metadata(node_id)?.persistent_metadata.pinned)
|
||||
}
|
||||
|
||||
pub fn is_visible(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(self.node(node_id)?.visible)
|
||||
}
|
||||
|
||||
pub fn is_absolute(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata {
|
||||
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Absolute(_)),
|
||||
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Absolute(_)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_chain(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata {
|
||||
NodeTypePersistentMetadata::Node(node_metadata) => matches!(node_metadata.position, NodePosition::Chain),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_stack(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(match &self.node_metadata(node_id)?.persistent_metadata.node_type_metadata {
|
||||
NodeTypePersistentMetadata::Layer(layer_metadata) => matches!(layer_metadata.position, LayerPosition::Stack(_)),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn number_of_inputs(&self, node_id: &NodeId) -> Result<usize, NetworkError> {
|
||||
Ok(self.node(node_id)?.inputs.len())
|
||||
}
|
||||
|
||||
pub fn number_of_displayed_inputs(&self, node_id: &NodeId) -> Result<usize, NetworkError> {
|
||||
Ok(self.node(node_id)?.inputs.iter().filter(|input| input.is_exposed()).count())
|
||||
}
|
||||
|
||||
pub fn number_of_outputs(&self, node_id: &NodeId) -> Result<usize, NetworkError> {
|
||||
Ok(match self.implementation(node_id)? {
|
||||
DocumentNodeImplementation::Network(nested_network) => nested_network.exports.len(),
|
||||
_ => 1,
|
||||
})
|
||||
}
|
||||
|
||||
/// The number of imports as defined by the encapsulating node's input count, or zero for the document network.
|
||||
pub fn number_of_imports(&self) -> usize {
|
||||
self.encapsulating_node().map_or(0, |node| node.inputs.len())
|
||||
}
|
||||
|
||||
pub fn number_of_exports(&self) -> usize {
|
||||
self.network.exports.len()
|
||||
}
|
||||
|
||||
pub fn has_primary_input(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(self.input(&InputConnector::node(*node_id, 0)).is_ok_and(|input| input.is_exposed()))
|
||||
}
|
||||
|
||||
pub fn hidden_primary_output(&self, node_id: &NodeId) -> Result<bool, NetworkError> {
|
||||
Ok(match self.implementation(node_id)? {
|
||||
DocumentNodeImplementation::Network(network) => network.exports.first().is_none_or(|input| !input.is_exposed()),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hidden_primary_export(&self) -> bool {
|
||||
let Some((node_id, _)) = self.path.split_last() else { return false };
|
||||
self.encapsulating().and_then(|parent| parent.hidden_primary_output(node_id)).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn hidden_primary_import(&self) -> bool {
|
||||
self.encapsulating_node().is_ok_and(|node| node.inputs.first().is_some_and(|input| !input.is_exposed()))
|
||||
}
|
||||
|
||||
pub fn previewing(&self) -> Previewing {
|
||||
self.metadata.persistent_metadata.previewing
|
||||
}
|
||||
|
||||
/// The root node (the node that the solid line is connected to), or None if no nodes are connected to the output.
|
||||
pub fn root_node(&self) -> Option<RootNode> {
|
||||
match &self.metadata.persistent_metadata.previewing {
|
||||
Previewing::Yes { root_node_to_restore } => *root_node_to_restore,
|
||||
Previewing::No => self.network.exports.first().and_then(|export| {
|
||||
if let NodeInput::Node { node_id, output_index, .. } = export {
|
||||
Some(RootNode {
|
||||
node_id: *node_id,
|
||||
output_index: *output_index,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str) -> Result<Option<&'a Value>, NetworkError> {
|
||||
Ok(self.persistent_input_metadata(node_id, index)?.input_data.get(key))
|
||||
}
|
||||
|
||||
pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize) -> Result<&'a InputPersistentMetadata, NetworkError> {
|
||||
let metadata = self.node_metadata(node_id)?;
|
||||
let input_metadata = metadata.persistent_metadata.input_metadata.get(index).ok_or(NetworkError::InputNotFound {
|
||||
connector: InputConnector::node(*node_id, index),
|
||||
})?;
|
||||
Ok(&input_metadata.persistent_metadata)
|
||||
}
|
||||
|
||||
pub(crate) fn transient_input_metadata(&self, node_id: &NodeId, index: usize) -> Result<&'a InputTransientMetadata, NetworkError> {
|
||||
let metadata = self.node_metadata(node_id)?;
|
||||
let input_metadata = metadata.persistent_metadata.input_metadata.get(index).ok_or(NetworkError::InputNotFound {
|
||||
connector: InputConnector::node(*node_id, index),
|
||||
})?;
|
||||
Ok(&input_metadata.transient_metadata)
|
||||
}
|
||||
|
||||
pub fn upstream_output_connector(&self, input_connector: &InputConnector) -> Result<Option<OutputConnector>, NetworkError> {
|
||||
Ok(match self.input(input_connector)? {
|
||||
NodeInput::Node { node_id, output_index, .. } => Some(OutputConnector::node(*node_id, *output_index)),
|
||||
NodeInput::Import { import_index, .. } => Some(OutputConnector::Import(*import_index)),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the node reaches the exports by following wires downstream.
|
||||
// O(nodes + wires) reachability walk
|
||||
pub fn connected_to_output(&self, target_node_id: &NodeId) -> bool {
|
||||
if self
|
||||
.network
|
||||
.exports
|
||||
.iter()
|
||||
.any(|export| if let NodeInput::Node { node_id, .. } = export { node_id == target_node_id } else { false })
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut stack = self
|
||||
.network
|
||||
.exports
|
||||
.iter()
|
||||
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { self.network.nodes.get(node_id) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let mut already_visited = HashSet::new();
|
||||
already_visited.extend(
|
||||
self.network
|
||||
.exports
|
||||
.iter()
|
||||
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id) } else { None }),
|
||||
);
|
||||
|
||||
while let Some(node) = stack.pop() {
|
||||
for input in &node.inputs {
|
||||
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
||||
if already_visited.contains(&ref_id) {
|
||||
continue;
|
||||
}
|
||||
if ref_id == *target_node_id {
|
||||
return true;
|
||||
}
|
||||
let Some(ref_node) = self.network.nodes.get(&ref_id) else { continue };
|
||||
already_visited.insert(ref_id);
|
||||
stack.push(ref_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// An iterator of all nodes connected to the given nodes by the flow type, traversing backwards upstream from the given nodes' inputs.
|
||||
pub(crate) fn upstream_flow(&self, mut node_ids: Vec<NodeId>, mut flow_type: FlowType) -> FlowIter<'a> {
|
||||
if matches!(flow_type, FlowType::LayerChildrenUpstreamFlow) {
|
||||
node_ids = node_ids
|
||||
.iter()
|
||||
.filter_map(|node_id| {
|
||||
if self.is_layer(node_id).unwrap_or_default() {
|
||||
self.network.nodes.get(node_id).and_then(|node| node.inputs.get(1)).and_then(|input| input.as_node())
|
||||
} else {
|
||||
Some(*node_id)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
flow_type = FlowType::UpstreamFlow;
|
||||
};
|
||||
FlowIter {
|
||||
stack: node_ids,
|
||||
network: self.network,
|
||||
network_metadata: self.metadata,
|
||||
flow_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// The distance in grid cells from the layer to the end of its chain, or zero for a layer with no chain.
|
||||
pub fn chain_width(&self, node_id: &NodeId) -> u32 {
|
||||
if self.number_of_displayed_inputs(node_id).unwrap_or_default() > 1 {
|
||||
let mut last_chain_node_distance = 0_u32;
|
||||
for (index, node_id) in self.upstream_flow(vec![*node_id], FlowType::HorizontalPrimaryOutputFlow).skip(1).enumerate() {
|
||||
if self.is_chain(&node_id).unwrap_or_default() {
|
||||
last_chain_node_distance = (index as u32) + 1;
|
||||
} else {
|
||||
return last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
last_chain_node_distance * NODE_CHAIN_WIDTH as u32 + 1
|
||||
} else {
|
||||
// A layer with no secondary input has no chain
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// The given network's pinned nodes in display order: pinning appends, dragging rearranges, and any not yet recorded go last.
|
||||
pub fn ordered_pinned_nodes(&self) -> Vec<NodeId> {
|
||||
let order = &self.metadata.persistent_metadata.pinned_node_order;
|
||||
let is_pinned = |node_id: &NodeId| self.is_pinned(node_id).unwrap_or_default();
|
||||
|
||||
// Follow the saved order, keeping only nodes that still exist and are still pinned
|
||||
let mut pinned_nodes = order
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|node_id| self.network.nodes.contains_key(node_id) && is_pinned(node_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Append any pinned nodes missing from the saved order at the end
|
||||
let mut unordered = self.network.nodes.keys().copied().filter(|node_id| is_pinned(node_id) && !order.contains(node_id)).collect::<Vec<_>>();
|
||||
unordered.sort();
|
||||
pinned_nodes.extend(unordered);
|
||||
|
||||
pinned_nodes
|
||||
}
|
||||
|
||||
/// Create a node template from an existing node.
|
||||
pub fn create_node_template(&self, node_id: &NodeId) -> Result<NodeTemplate, NetworkError> {
|
||||
let node = self.node(node_id)?;
|
||||
let node_metadata = self.node_metadata(node_id)?;
|
||||
|
||||
Ok(NodeTemplate {
|
||||
persistent_node_metadata: node_metadata.persistent_metadata.clone(),
|
||||
document_node: node.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user