Break apart the enormous network_interface.rs into submodules (#4371)

Split the network interface monolith into submodules with no behavior change
This commit is contained in:
Keavon Chambers
2026-07-23 22:43:19 -07:00
committed by Dennis Kobert
parent a1a5c4427e
commit 5558a322f0
8 changed files with 6997 additions and 6977 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,381 @@
use super::*;
// Helper functions for mutable getters
impl NodeNetworkInterface {
pub fn upstream_chain_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
log::error!("Could not get selected nodes in upstream_chain_nodes");
return Vec::new();
};
let mut all_selected_nodes = selected_nodes.selected_nodes().cloned().collect::<Vec<_>>();
for selected_node_id in selected_nodes.selected_nodes() {
if self.is_layer(selected_node_id, network_path) {
let unique_upstream_chain = self
.upstream_flow_back_from_nodes(vec![*selected_node_id], network_path, FlowType::HorizontalFlow)
.skip(1)
.take_while(|node_id| self.is_chain(node_id, network_path))
.filter(|upstream_node| all_selected_nodes.iter().all(|new_selected_node| new_selected_node != upstream_node))
.collect::<Vec<_>>();
all_selected_nodes.extend(unique_upstream_chain);
}
}
all_selected_nodes
}
pub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets {
let mut all_node_click_targets = Vec::new();
let mut connector_click_targets = Vec::new();
let mut icon_click_targets = Vec::new();
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in collect_frontend_click_targets");
return FrontendClickTargets::default();
};
let nodes = network_metadata.persistent_metadata.node_metadata.keys().copied().collect::<Vec<_>>();
if let Some(import_export_click_targets) = self.import_export_ports(network_path).cloned() {
for port in import_export_click_targets.click_targets() {
if let ClickTargetType::Subpath(subpath) = port.target_type() {
connector_click_targets.push(subpath.to_bezpath().to_svg());
}
}
}
nodes.into_iter().for_each(|node_id| {
if let Some(node_click_targets) = self.node_click_targets(&node_id, network_path) {
let mut node_path = String::new();
if let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() {
node_path.push_str(subpath.to_bezpath().to_svg().as_str())
}
all_node_click_targets.push((node_id, node_path));
for port in node_click_targets.port_click_targets.click_targets() {
if let ClickTargetType::Subpath(subpath) = port.target_type() {
connector_click_targets.push(subpath.to_bezpath().to_svg());
}
}
if let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {
// Visibility button (eye icon)
if let ClickTargetType::Subpath(subpath) = layer_metadata.visibility_click_target.target_type() {
icon_click_targets.push(subpath.to_bezpath().to_svg());
}
// Lock button (padlock icon), only when the layer is locked
if let Some(lock_click_target) = &layer_metadata.lock_click_target
&& let ClickTargetType::Subpath(subpath) = lock_click_target.target_type()
{
icon_click_targets.push(subpath.to_bezpath().to_svg());
}
// Drag grip (dotted symbol)
if let ClickTargetType::Subpath(subpath) = layer_metadata.grip_click_target.target_type() {
icon_click_targets.push(subpath.to_bezpath().to_svg());
}
}
}
});
let mut layer_click_targets = Vec::new();
let mut node_click_targets = Vec::new();
all_node_click_targets.into_iter().for_each(|(node_id, path)| {
if self.is_layer(&node_id, network_path) {
layer_click_targets.push(path);
} else {
node_click_targets.push(path);
}
});
let bounds = self.all_nodes_bounding_box(network_path).cloned().unwrap_or([DVec2::ZERO, DVec2::ZERO]);
let rect = Subpath::<PointId>::new_rectangle(bounds[0], bounds[1]);
let all_nodes_bounding_box = rect.to_bezpath().to_svg();
let mut modify_import_export = Vec::new();
if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) {
for click_target in modify_import_export_click_targets
.remove_imports_exports
.click_targets()
.chain(modify_import_export_click_targets.reorder_imports_exports.click_targets())
{
if let ClickTargetType::Subpath(subpath) = click_target.target_type() {
modify_import_export.push(subpath.to_bezpath().to_svg());
}
}
}
FrontendClickTargets {
node_click_targets,
layer_click_targets,
connector_click_targets,
icon_click_targets,
all_nodes_bounding_box,
modify_import_export,
}
}
pub fn set_document_to_viewport_transform(&mut self, transform: DAffine2) {
self.document_metadata.document_to_viewport = transform;
}
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
}
pub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in node_graph_ptz_mut");
return None;
};
Some(&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz)
}
pub fn node_graph_ptz_mut(&mut self, network_path: &[NodeId]) -> Option<&mut PTZ> {
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
log::error!("Could not get nested network_metadata in node_graph_ptz_mut");
return None;
};
Some(&mut network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz)
}
// TODO: Optimize getting click target intersections from click by using a spacial data structure like a quadtree instead of linear search
/// Click target getter methods
pub fn node_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option<NodeId> {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in node_from_click");
return None;
};
let Some(network) = self.nested_network(network_path) else {
log::error!("Could not get nested network in node_from_click");
return None;
};
let point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
let nodes = network.nodes.keys().copied().collect::<Vec<_>>();
let clicked_nodes = nodes
.iter()
.filter(|node_id| {
self.node_click_targets(node_id, network_path)
.is_some_and(|transient_node_metadata| transient_node_metadata.node_click_target.intersect_point_no_stroke(point))
})
.cloned()
.collect::<Vec<_>>();
// Since nodes are placed on top of layer chains, find the first non layer node that was clicked, and if there way no non layer nodes clicked, then find the first layer node that was clicked
clicked_nodes
.iter()
.find_map(|node_id| {
let Some(node_metadata) = self.network_metadata(network_path)?.persistent_metadata.node_metadata.get(node_id) else {
log::error!("Could not get node_metadata for node {node_id}");
return None;
};
if !node_metadata.persistent_metadata.is_layer() { Some(*node_id) } else { None }
})
.or_else(|| clicked_nodes.into_iter().next())
}
pub fn layer_click_target_from_click(&mut self, click: DVec2, click_target_type: LayerClickTargetTypes, network_path: &[NodeId]) -> Option<NodeId> {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in visibility_from_click");
return None;
};
let Some(network) = self.nested_network(network_path) else {
log::error!("Could not get nested network in visibility_from_click");
return None;
};
let point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
let node_ids: Vec<_> = network.nodes.keys().copied().collect();
node_ids
.iter()
.filter_map(|node_id| {
self.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| {
if let NodeTypeClickTargets::Layer(layer) = &transient_node_metadata.node_type_metadata {
match click_target_type {
LayerClickTargetTypes::Visibility => layer.visibility_click_target.intersect_point_no_stroke(point).then_some(*node_id),
LayerClickTargetTypes::Lock => layer.lock_click_target.as_ref().and_then(|target| target.intersect_point_no_stroke(point).then_some(*node_id)),
LayerClickTargetTypes::Grip => layer.grip_click_target.intersect_point_no_stroke(point).then_some(*node_id),
LayerClickTargetTypes::Name => layer.name_click_target.as_ref().and_then(|target| target.intersect_point_no_stroke(point).then_some(*node_id)),
}
} else {
None
}
})
})
.next()
}
pub fn input_connector_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option<InputConnector> {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in input_connector_from_click");
return None;
};
let Some(network) = self.nested_network(network_path) else {
log::error!("Could not get nested network in input_connector_from_click");
return None;
};
let point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
network
.nodes
.keys()
.copied()
.collect::<Vec<_>>()
.iter()
.filter_map(|node_id| {
self.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| {
transient_node_metadata
.port_click_targets
.clicked_input_port_from_point(point)
.map(|port| InputConnector::node(*node_id, port))
})
})
.next()
.or_else(|| {
self.import_export_ports(network_path)
.and_then(|import_export_ports| import_export_ports.clicked_input_port_from_point(point).map(InputConnector::Export))
})
}
pub fn output_connector_from_click(&mut self, click: DVec2, network_path: &[NodeId]) -> Option<OutputConnector> {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in output_connector_from_click");
return None;
};
let Some(network) = self.nested_network(network_path) else {
log::error!("Could not get nested network in output_connector_from_click");
return None;
};
let point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
let nodes = network.nodes.keys().copied().collect::<Vec<_>>();
nodes
.iter()
.filter_map(|node_id| {
self.node_click_targets(node_id, network_path).and_then(|transient_node_metadata| {
transient_node_metadata
.port_click_targets
.clicked_output_port_from_point(point)
.map(|output_index| OutputConnector::node(*node_id, output_index))
})
})
.next()
.or_else(|| {
self.import_export_ports(network_path)
.and_then(|import_export_ports| import_export_ports.clicked_output_port_from_point(point).map(OutputConnector::Import))
})
}
pub fn input_position(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<DVec2> {
match input_connector {
InputConnector::Node { node_id, input_index } => self
.node_click_targets(node_id, network_path)
.and_then(|transient_node_metadata| transient_node_metadata.port_click_targets.input_port_position(*input_index)),
InputConnector::Export(export_index) => self
.import_export_ports(network_path)
.and_then(|import_export_ports| import_export_ports.input_port_position(*export_index)),
}
}
pub fn output_position(&mut self, output_connector: &OutputConnector, network_path: &[NodeId]) -> Option<DVec2> {
match output_connector {
OutputConnector::Node { node_id, output_index } => self
.node_click_targets(node_id, network_path)
.and_then(|transient_node_metadata| transient_node_metadata.port_click_targets.output_port_position(*output_index)),
OutputConnector::Import(import_index) => self
.import_export_ports(network_path)
.and_then(|import_export_ports| import_export_ports.output_port_position(*import_index)),
}
}
/// Get the combined bounding box of the click targets of the selected nodes in the node graph in viewport space
pub fn selected_nodes_bounding_box_viewport(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
// Always get the bounding box for nodes in the currently viewed network
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in selected_nodes_bounding_box_viewport");
return None;
};
let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport;
self.selected_nodes_bounding_box(network_path)
.map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)])
}
pub fn selected_layers_artwork_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes()
.0
.iter()
.filter(|node| self.is_layer(node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds)
}
pub fn selected_unlocked_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes()
.0
.iter()
.filter(|node| self.is_layer(node, &[]) && !self.is_locked(node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds)
}
/// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space
pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
log::error!("Could not get selected nodes in selected_nodes_bounding_box_viewport");
return None;
};
selected_nodes
.selected_nodes()
.cloned()
.collect::<Vec<_>>()
.iter()
.filter_map(|node_id| {
self.node_click_targets(node_id, network_path)
.and_then(|transient_node_metadata| transient_node_metadata.node_click_target.bounding_box())
})
.reduce(graphene_std::renderer::Quad::combine_bounds)
}
/// Gets the bounding box in viewport coordinates for each node in the node graph
pub fn graph_bounds_viewport_space(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
let bounds = *self.all_nodes_bounding_box(network_path)?;
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in graph_bounds_viewport_space");
return None;
};
let bounding_box_subpath = Subpath::<PointId>::new_rectangle(bounds[0], bounds[1]);
bounding_box_subpath.bounding_box_with_transform(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport)
}
pub fn collect_layer_widths(&mut self, network_path: &[NodeId]) -> (HashMap<NodeId, u32>, HashMap<NodeId, u32>, HashMap<NodeId, bool>) {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in collect_layer_widths");
return (HashMap::new(), HashMap::new(), HashMap::new());
};
let nodes = network_metadata
.persistent_metadata
.node_metadata
.keys()
.filter_map(|node_id| if self.is_layer(node_id, network_path) { Some(*node_id) } else { None })
.collect::<Vec<_>>();
let layer_widths = nodes
.iter()
.filter_map(|node_id| self.layer_width(node_id, network_path).map(|layer_width| (*node_id, layer_width)))
.collect::<HashMap<NodeId, u32>>();
let chain_widths = nodes.iter().map(|node_id| (*node_id, self.chain_width(node_id, network_path))).collect::<HashMap<NodeId, u32>>();
let has_left_input_wire = nodes
.iter()
.map(|node_id| {
(
*node_id,
!self
.upstream_flow_back_from_nodes(vec![*node_id], network_path, FlowType::HorizontalFlow)
.skip(1)
.all(|node_id| self.is_chain(&node_id, network_path)),
)
})
.collect::<HashMap<NodeId, bool>>();
(layer_widths, chain_widths, has_left_input_wire)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,195 @@
use super::*;
impl NodeNetworkInterface {
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<Vector> {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self);
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer(&DefinitionIdentifier::Network("Path".into()))
&& let Some(vector) = self.document_metadata.vector_modify.get(&path_node)
{
let mut modified = vector.clone();
let path_node = self.document_network().nodes.get(&path_node);
let modification_input = path_node.and_then(|node: &DocumentNode| node.inputs.get(1)).and_then(|input| input.as_value());
if let Some(TaggedValue::VectorModification(modification)) = modification_input {
modification.apply(&mut modified);
}
return Some(modified);
}
self.document_metadata.layer_vector_data.get(&layer).map(|arc| arc.as_ref().clone())
}
/// The vector geometry an upstream Path node would surface for editing.
/// This is the result of `compute_modified_vector`, but only if a visible 'Path' node is actually upstream.
/// Useful for tool overlays and snap target collection usages that want to match the Path tool's view
/// (e.g. the pre-solidified centerline for a Solidify Stroke layer) and otherwise do nothing.
pub fn upstream_path_node_vector(&self, layer: LayerNodeIdentifier) -> Option<Vector> {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self);
graph_layer.upstream_visible_node_id_from_name_in_layer(&DefinitionIdentifier::Network("Path".into()))?;
self.compute_modified_vector(layer)
}
/// Outline targets for the Select tool's hover/selection overlay, mirroring the Path tool's view.
/// Returns `Some` when an upstream Path node exists so the outline matches what the Path tool edits
/// (e.g. the pre-solidified centerline for a Solidify Stroke layer); returns `None` otherwise so the
/// caller can fall back to the layer's recorded `outlines`/`click_targets`.
pub fn path_aware_outline_targets(&self, layer: LayerNodeIdentifier) -> Option<Vec<ClickTargetType>> {
let vector = self.upstream_path_node_vector(layer)?;
let mut targets = Vec::new();
let subpaths: Vec<Subpath<PointId>> = vector.stroke_bezier_paths().collect();
if !subpaths.is_empty() {
targets.push(ClickTargetType::CompoundPath(subpaths));
}
for &point_id in vector.point_domain.ids() {
if !vector.any_connected(point_id) {
let position = vector.point_domain.position_from_id(point_id).unwrap_or_default();
targets.push(ClickTargetType::FreePoint(FreePoint::new(point_id, position)));
}
}
Some(targets)
}
/// Loads the structure of layer nodes from a node graph.
pub fn load_structure(&mut self) {
self.document_metadata.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT_PARENT, NodeRelations::default())]);
// Only load structure if there is a root node
let Some(root_node) = self.root_node(&[]) else { return };
let Some(first_root_layer) = self
.upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow)
.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self)) } else { None })
else {
return;
};
// Should refer to output node
let mut awaiting_horizontal_flow = vec![(first_root_layer.to_node(), first_root_layer)];
let mut awaiting_primary_flow = vec![];
while let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() {
let horizontal_flow_iter = self.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], &[], FlowType::HorizontalFlow);
let mut children = Vec::new();
// Special handling for the root layer, since it should not be skipped
if horizontal_root_node_id == first_root_layer.to_node() {
for current_node_id in horizontal_flow_iter {
if self.is_layer(&current_node_id, &[]) {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
if current_node_id == first_root_layer.to_node() {
awaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT));
children.push((LayerNodeIdentifier::ROOT_PARENT, current_layer_node));
} else {
awaiting_primary_flow.push((current_node_id, parent_layer_node));
children.push((parent_layer_node, current_layer_node));
}
parent_layer_node = current_layer_node;
}
}
}
} else {
// Skip the horizontal_root_node_id node
for current_node_id in horizontal_flow_iter.skip(1) {
if self.is_layer(&current_node_id, &[]) {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
awaiting_primary_flow.push((current_node_id, parent_layer_node));
children.push((parent_layer_node, current_layer_node));
parent_layer_node = current_layer_node;
}
}
}
}
for (parent, child) in children {
parent.push_child(&mut self.document_metadata, child);
}
while let Some((primary_root_node_id, parent_layer_node)) = awaiting_primary_flow.pop() {
let primary_flow_iter = self.upstream_flow_back_from_nodes(vec![primary_root_node_id], &[], FlowType::PrimaryFlow);
// Skip the primary_root_node_id node
let mut children = Vec::new();
for current_node_id in primary_flow_iter.skip(1) {
if self.is_layer(&current_node_id, &[]) {
// Create a new layer for the top of each stack, and add it as a child to the previous parent
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
children.push(current_layer_node);
// The layer nodes for the horizontal flow is itself
awaiting_horizontal_flow.push((current_node_id, current_layer_node));
}
}
}
for child in children {
parent_layer_node.push_child(&mut self.document_metadata, child);
}
}
}
let nodes: HashSet<NodeId> = self.document_network().nodes.keys().cloned().collect::<HashSet<_>>();
self.document_metadata.upstream_footprints.retain(|node, _| nodes.contains(node));
self.document_metadata.local_transforms.retain(|node, _| nodes.contains(node));
self.document_metadata.vector_modify.retain(|node, _| nodes.contains(node));
self.document_metadata.click_targets.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
self.document_metadata.outlines.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
self.document_metadata.text_frames.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
}
/// Update the cached transforms of the layers
pub fn update_transforms(&mut self, upstream_footprints: HashMap<NodeId, Footprint>, local_transforms: HashMap<NodeId, DAffine2>) {
self.document_metadata.upstream_footprints = upstream_footprints;
self.document_metadata.local_transforms = local_transforms;
}
/// Update the cached first item's source id of the layers
pub fn update_first_element_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
self.document_metadata.first_element_source_ids = new;
}
/// Update the cached click targets of the layers
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<Arc<ClickTarget>>>) {
self.document_metadata.click_targets = new_click_targets;
}
/// Update the cached source-geometry outline targets of the layers
pub fn update_outlines(&mut self, new_outlines: HashMap<LayerNodeIdentifier, Vec<Arc<ClickTarget>>>) {
self.document_metadata.outlines = new_outlines;
}
/// Update the cached per-layer 'Text' node text frames in row-local space (as `DAffine2`
/// mapping the unit square onto the frame).
pub fn update_text_frames(&mut self, new_text_frames: HashMap<LayerNodeIdentifier, DAffine2>) {
self.document_metadata.text_frames = new_text_frames;
}
/// Update the cached clip targets of the layers
pub fn update_clip_targets(&mut self, new_clip_targets: HashSet<NodeId>) {
self.document_metadata.clip_targets = new_clip_targets;
}
/// Update the vector modify of the layers
pub fn update_vector_modify(&mut self, new_vector_modify: HashMap<NodeId, Vector>) {
self.document_metadata.vector_modify = new_vector_modify;
}
/// Update the layer vector data (for layers without Path nodes)
pub fn update_vector_data(&mut self, new_layer_vector_data: HashMap<LayerNodeIdentifier, Arc<Vector>>) {
self.document_metadata.layer_vector_data = new_layer_vector_data;
}
/// Update the per-layer `ATTR_FILL` snapshot.
pub fn update_fill_attributes(&mut self, new_layer_fill_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>) {
self.document_metadata.layer_fill_attributes = new_layer_fill_attributes;
}
/// Update the per-layer `ATTR_STROKE` snapshot.
pub fn update_stroke_attributes(&mut self, new_layer_stroke_attributes: HashMap<LayerNodeIdentifier, Arc<List<Graphic<'static>>>>) {
self.document_metadata.layer_stroke_attributes = new_layer_stroke_attributes;
}
}

View File

@@ -0,0 +1,851 @@
use super::*;
#[derive(PartialEq)]
pub enum FlowType {
/// Iterate over all upstream nodes (inclusive) from every input (the primary and all secondary).
UpstreamFlow,
/// Iterate over nodes (inclusive) connected to the primary input.
PrimaryFlow,
/// Iterate over the secondary input (inclusive) for layer nodes and primary input for non layer nodes.
HorizontalFlow,
/// Same as horizontal flow, but only iterates over connections to primary outputs
HorizontalPrimaryOutputFlow,
/// Upstream flow starting from the either the node (inclusive) or secondary input of the layer (not inclusive).
LayerChildrenUpstreamFlow,
}
/// Iterate over upstream nodes. The behavior changes based on the `flow_type` that's set.
/// - [`FlowType::UpstreamFlow`]: iterates over all upstream nodes from every input (the primary and all secondary).
/// - [`FlowType::PrimaryFlow`]: iterates along the horizontal inputs of nodes, so in the case of a node chain `a -> b -> c`, this would yield `c, b, a` if we started from `c`.
/// - [`FlowType::HorizontalFlow`]: iterates over the secondary input for layer nodes and primary input for non layer nodes.
/// - [`FlowType::LayerChildrenUpstreamFlow`]: iterates over all upstream nodes from the secondary input of the node.
pub(crate) struct FlowIter<'a> {
pub(crate) stack: Vec<NodeId>,
pub(crate) network: &'a NodeNetwork,
pub(crate) network_metadata: &'a NodeNetworkMetadata,
pub(crate) flow_type: FlowType,
}
impl Iterator for FlowIter<'_> {
type Item = NodeId;
fn next(&mut self) -> Option<Self::Item> {
loop {
let node_id = self.stack.pop()?;
if let (Some(document_node), Some(node_metadata)) = (self.network.nodes.get(&node_id), self.network_metadata.persistent_metadata.node_metadata.get(&node_id)) {
let skip = if matches!(self.flow_type, FlowType::HorizontalFlow | FlowType::HorizontalPrimaryOutputFlow) && node_metadata.persistent_metadata.is_layer() {
1
} else {
0
};
let take = if self.flow_type == FlowType::UpstreamFlow { u32::MAX as usize } else { 1 };
let inputs = document_node.inputs.iter().skip(skip).take(take);
let node_ids = inputs.filter_map(|input| match input {
NodeInput::Node { output_index, .. } if self.flow_type == FlowType::HorizontalPrimaryOutputFlow && *output_index != 0 => None,
NodeInput::Node { node_id, .. } => Some(node_id),
_ => None,
});
self.stack.extend(node_ids);
return Some(node_id);
}
}
}
}
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum ImportOrExport {
Import(usize),
Export(usize),
}
/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum InputConnector {
#[serde(rename = "node")]
Node {
#[serde(rename = "nodeId")]
node_id: NodeId,
#[serde(rename = "inputIndex")]
input_index: usize,
},
#[serde(rename = "export")]
Export(usize),
}
impl Default for InputConnector {
fn default() -> Self {
InputConnector::Export(0)
}
}
impl InputConnector {
pub fn node(node_id: NodeId, input_index: usize) -> Self {
InputConnector::Node { node_id, input_index }
}
pub fn input_index(&self) -> usize {
match self {
InputConnector::Node { input_index, .. } => *input_index,
InputConnector::Export(input_index) => *input_index,
}
}
pub fn node_id(&self) -> Option<NodeId> {
match self {
InputConnector::Node { node_id, .. } => Some(*node_id),
_ => None,
}
}
}
/// Represents an output connector
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum OutputConnector {
#[serde(rename = "node")]
Node {
#[serde(rename = "nodeId")]
node_id: NodeId,
#[serde(rename = "outputIndex")]
output_index: usize,
},
#[serde(rename = "import")]
Import(usize),
}
impl Default for OutputConnector {
fn default() -> Self {
OutputConnector::Import(0)
}
}
impl OutputConnector {
pub fn node(node_id: NodeId, output_index: usize) -> Self {
OutputConnector::Node { node_id, output_index }
}
pub fn index(&self) -> usize {
match self {
OutputConnector::Node { output_index, .. } => *output_index,
OutputConnector::Import(output_index) => *output_index,
}
}
pub fn node_id(&self) -> Option<NodeId> {
match self {
OutputConnector::Node { node_id, .. } => Some(*node_id),
_ => None,
}
}
pub fn from_input(input: &NodeInput) -> Option<Self> {
match input {
NodeInput::Import { import_index, .. } => Some(Self::Import(*import_index)),
NodeInput::Node { node_id, output_index, .. } => Some(Self::node(*node_id, *output_index)),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct Ports {
pub(crate) input_ports: Vec<(usize, ClickTarget)>,
pub(crate) output_ports: Vec<(usize, ClickTarget)>,
}
impl Default for Ports {
fn default() -> Self {
Self::new()
}
}
impl Ports {
pub fn new() -> Ports {
Ports {
input_ports: Vec::new(),
output_ports: Vec::new(),
}
}
pub fn click_targets(&self) -> impl Iterator<Item = &ClickTarget> {
self.input_ports
.iter()
.map(|(_, click_target)| click_target)
.chain(self.output_ports.iter().map(|(_, click_target)| click_target))
}
pub fn input_ports(&self) -> impl Iterator<Item = &(usize, ClickTarget)> {
self.input_ports.iter()
}
pub fn output_ports(&self) -> impl Iterator<Item = &(usize, ClickTarget)> {
self.output_ports.iter()
}
pub(crate) fn insert_input_port_at_center(&mut self, input_index: usize, center: DVec2) {
let subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.));
self.insert_custom_input_port(input_index, ClickTarget::new_with_subpath(subpath, 0.));
}
pub(crate) fn insert_custom_input_port(&mut self, input_index: usize, click_target: ClickTarget) {
self.input_ports.push((input_index, click_target));
}
pub(crate) fn insert_output_port_at_center(&mut self, output_index: usize, center: DVec2) {
let subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.));
self.insert_custom_output_port(output_index, ClickTarget::new_with_subpath(subpath, 0.));
}
pub(crate) fn insert_custom_output_port(&mut self, output_index: usize, click_target: ClickTarget) {
self.output_ports.push((output_index, click_target));
}
pub(crate) fn insert_node_input(&mut self, input_index: usize, row_index: usize, node_top_left: DVec2) {
// The center of the click target is always 24 px down from the top left corner of the node
let center = node_top_left + DVec2::new(0., 24. + 24. * row_index as f64);
self.insert_input_port_at_center(input_index, center);
}
pub(crate) fn insert_node_output(&mut self, output_index: usize, node_top_left: DVec2) {
// The center of the click target is always 24 px down from the top left corner of the node
let center = node_top_left + DVec2::new(5. * 24., 24. + 24. * output_index as f64);
self.insert_output_port_at_center(output_index, center);
}
pub(crate) fn insert_layer_input(&mut self, input_index: usize, node_top_left: DVec2) {
let center = if input_index == 0 {
node_top_left + DVec2::new(2. * 24., 24. * 2. + 8.)
} else {
node_top_left + DVec2::new(0., 24. * 1.)
};
self.insert_input_port_at_center(input_index, center);
}
pub(crate) fn insert_layer_output(&mut self, node_top_left: DVec2) {
// The center of the click target is always 24 px down from the top left corner of the node
let center = node_top_left + DVec2::new(2. * 24., -8.);
self.insert_output_port_at_center(0, center);
}
pub fn clicked_input_port_from_point(&self, point: DVec2) -> Option<usize> {
self.input_ports.iter().find_map(|(port, click_target)| click_target.intersect_point_no_stroke(point).then_some(*port))
}
pub fn clicked_output_port_from_point(&self, point: DVec2) -> Option<usize> {
self.output_ports.iter().find_map(|(port, click_target)| click_target.intersect_point_no_stroke(point).then_some(*port))
}
pub fn input_port_position(&self, index: usize) -> Option<DVec2> {
self.input_ports.iter().find_map(|(port_index, click_target)| {
if *port_index == index {
click_target.bounding_box().map(|bounds| bounds[0] + DVec2::new(8., 8.))
} else {
None
}
})
}
pub fn output_port_position(&self, index: usize) -> Option<DVec2> {
self.output_ports.iter().find_map(|(port_index, click_target)| {
if *port_index == index {
click_target.bounding_box().map(|bounds| bounds[0] + DVec2::new(8., 8.))
} else {
None
}
})
}
}
#[derive(PartialEq, Debug, Clone, Copy, Hash, Default, serde::Serialize, serde::Deserialize)]
pub struct RootNode {
pub node_id: NodeId,
pub output_index: usize,
}
impl RootNode {
pub fn to_connector(&self) -> OutputConnector {
OutputConnector::Node {
node_id: self.node_id,
output_index: self.output_index,
}
}
}
#[derive(PartialEq, Debug, Clone, Copy, Hash, Default, serde::Serialize, serde::Deserialize)]
pub enum Previewing {
/// If there is a node to restore the connection to the export for, then it is stored in the option.
/// Otherwise, nothing gets restored and the primary export is disconnected.
Yes { root_node_to_restore: Option<RootNode> },
#[default]
No,
}
/// All fields in NetworkMetadata should automatically be updated by using the network interface API. If a field is none then it should be calculated based on the network state.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct NodeNetworkMetadata {
pub persistent_metadata: NodeNetworkPersistentMetadata,
#[serde(skip)]
pub transient_metadata: NodeNetworkTransientMetadata,
}
impl Clone for NodeNetworkMetadata {
fn clone(&self) -> Self {
NodeNetworkMetadata {
persistent_metadata: self.persistent_metadata.clone(),
transient_metadata: Default::default(),
}
}
}
impl PartialEq for NodeNetworkMetadata {
fn eq(&self, other: &Self) -> bool {
self.persistent_metadata == other.persistent_metadata
}
}
impl NodeNetworkMetadata {
pub fn nested_metadata(&self, nested_path: &[NodeId]) -> Option<&Self> {
let mut network_metadata = Some(self);
for segment in nested_path {
network_metadata = network_metadata
.and_then(|network| network.persistent_metadata.node_metadata.get(segment))
.and_then(|node| node.persistent_metadata.network_metadata.as_ref());
}
network_metadata
}
/// Get the mutable nested network given by the path of node ids
pub fn nested_metadata_mut(&mut self, nested_path: &[NodeId]) -> Option<&mut Self> {
let mut network_metadata = Some(self);
for segment in nested_path {
network_metadata = network_metadata
.and_then(|network: &mut NodeNetworkMetadata| network.persistent_metadata.node_metadata.get_mut(segment))
.and_then(|node| node.persistent_metadata.network_metadata.as_mut());
}
network_metadata
}
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NodeNetworkPersistentMetadata {
/// The identifier for the node definition created for custom network nodes in [`DocumentNodeDefinition`].
/// It is only used to associate network nodes with their definition. Protonodes use their ProtonodeIdentifier.
/// The reference is removed once the node is modified, since the node now stores its own implementation and inputs.
/// TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition.
/// TODO: Implement node versioning so that references to old nodes can be updated to the new node definition.
pub reference: Option<String>,
/// Node metadata must exist for every document node in the network
#[serde(serialize_with = "graphene_std::vector::serialize_hashmap", deserialize_with = "graphene_std::vector::deserialize_hashmap")]
pub node_metadata: HashMap<NodeId, DocumentNodeMetadata>,
/// The display order of pinned nodes in the Properties panel (shown when nothing is selected in this network), keyed by node ID.
#[serde(default)]
pub pinned_node_order: Vec<NodeId>,
/// Cached metadata for each node, which is calculated when adding a node to node_metadata
/// Indicates whether the network is currently rendered with a particular node that is previewed, and if so, which connection should be restored when the preview ends.
pub previewing: Previewing,
// Stores the transform and navigation state for the network
pub navigation_metadata: NavigationMetadata,
/// Stack of selection snapshots for previous history states. Session state that is not persisted into saved documents.
#[serde(skip)]
pub selection_undo_history: VecDeque<SelectedNodes>,
/// Stack of selection snapshots for future history states.
#[serde(skip)]
pub selection_redo_history: VecDeque<SelectedNodes>,
}
/// This is the same as Option, but more clear in the context of having cached metadata either being loaded or unloaded
#[derive(Debug, Default, Clone)]
pub enum TransientMetadata<T> {
Loaded(T),
#[default]
Unloaded,
}
impl<T> TransientMetadata<T> {
/// Set the current transient metadata to unloaded
pub fn unload(&mut self) {
*self = TransientMetadata::Unloaded;
}
pub fn is_loaded(&self) -> bool {
matches!(self, TransientMetadata::Loaded(_))
}
}
/// If some network calculation is too slow to compute for every usage, cache the data here
#[derive(Debug, Default, Clone)]
pub struct NodeNetworkTransientMetadata {
pub selected_nodes: SelectedNodes,
/// Sole dependents of the top of the stacks of all selected nodes. Used to determine which nodes are checked for collision when shifting.
/// The LayerOwner is used to determine whether the collided node should be shifted, or the layer that owns it.
pub stack_dependents: TransientMetadata<HashMap<NodeId, LayerOwner>>,
/// Cache for the bounding box around all nodes in node graph space.
pub all_nodes_bounding_box: TransientMetadata<[DVec2; 2]>,
// /// Cache bounding box for all "groups of nodes", which will be used to prevent overlapping nodes
// node_group_bounding_box: Vec<(Subpath<ManipulatorGroupId>, Vec<Nodes>)>,
/// Cache for all outward wire connections
pub outward_wires: TransientMetadata<HashMap<OutputConnector, Vec<InputConnector>>>,
/// All export connector click targets
pub import_export_ports: TransientMetadata<Ports>,
/// Click targets for adding, removing, and moving import/export ports
pub modify_import_export: TransientMetadata<ModifyImportExportClickTarget>,
// Wires from the exports
pub wires: Vec<TransientMetadata<WirePathUpdate>>,
}
#[derive(Debug, Clone)]
pub struct ModifyImportExportClickTarget {
// Subtract icon that appears when hovering over an import/export
pub remove_imports_exports: Ports,
// Grip drag icon that appears when hovering over an import/export
pub reorder_imports_exports: Ports,
}
#[derive(Debug, Clone)]
pub struct NetworkEdgeDistance {
/// The viewport pixel distance between the left edge of the node graph and the exports.
pub exports_to_edge_distance: DVec2,
/// The viewport pixel distance between the left edge of the node graph and the imports.
pub imports_to_edge_distance: DVec2,
}
#[derive(Debug, Clone)]
pub enum LayerOwner {
// Used to get the layer that should be shifted when there is a collision.
Layer(NodeId),
// The vertical offset of a node from the start of its shift. Should be reset when the drag ends.
None(i32),
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct DocumentNodeMetadata {
#[serde(deserialize_with = "deserialize_node_persistent_metadata")]
pub persistent_metadata: DocumentNodePersistentMetadata,
#[serde(skip)]
pub transient_metadata: DocumentNodeTransientMetadata,
}
impl Clone for DocumentNodeMetadata {
fn clone(&self) -> Self {
DocumentNodeMetadata {
persistent_metadata: self.persistent_metadata.clone(),
transient_metadata: Default::default(),
}
}
}
impl PartialEq for DocumentNodeMetadata {
fn eq(&self, other: &Self) -> bool {
self.persistent_metadata == other.persistent_metadata
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct NumberInputSettings {
pub unit: Option<String>,
pub min: Option<f64>,
pub max: Option<f64>,
pub step: Option<f64>,
pub mode: NumberInputMode,
pub range_min: Option<f64>,
pub range_max: Option<f64>,
pub is_integer: bool,
pub blank_assist: bool,
}
impl Default for NumberInputSettings {
fn default() -> Self {
NumberInputSettings {
unit: None,
min: None,
max: None,
step: None,
mode: NumberInputMode::default(),
range_min: None,
range_max: None,
is_integer: false,
blank_assist: true,
}
}
}
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct Vec2InputSettings {
pub x: String,
pub y: String,
pub unit: String,
pub min: Option<f64>,
pub is_integer: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum WidgetOverride {
None,
Hidden,
String(String),
Number(NumberInputSettings),
Vec2(Vec2InputSettings),
Custom(String),
}
// TODO: Custom deserialization/serialization to ensure number of properties row matches number of node inputs
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct InputPersistentMetadata {
/// A general datastore than can store key value pairs of any types for any input
/// Each instance of the input node needs to store its own data, since it can lose the reference to its
/// node definition if the node signature is modified by the user. For example adding/removing/renaming an import/export of a network node.
#[serde(serialize_with = "graphene_std::vector::serialize_hashmap_as_sorted_object")]
pub input_data: HashMap<String, Value>,
// An input can override a widget, which would otherwise be automatically generated from the type
// The string is the identifier to the widget override function stored in INPUT_OVERRIDES
pub widget_override: Option<String>,
/// An empty input name means to use the type as the name.
pub input_name: String,
/// Displayed as the tooltip description.
pub input_description: String,
}
impl InputPersistentMetadata {
pub fn with_name(mut self, input_name: &str) -> Self {
self.input_name = input_name.to_string();
self
}
pub fn with_override(mut self, widget_override: WidgetOverride) -> Self {
match widget_override {
// Uses the default widget for the type
WidgetOverride::None => {
self.widget_override = None;
}
WidgetOverride::Hidden => {
self.widget_override = Some("hidden".to_string());
}
WidgetOverride::String(string_properties) => {
self.input_data.insert("string_properties".to_string(), Value::String(string_properties));
self.widget_override = Some("string".to_string());
}
WidgetOverride::Number(mut number_properties) => {
if let Some(unit) = number_properties.unit.take() {
self.input_data.insert("unit".to_string(), json!(unit));
}
if let Some(min) = number_properties.min.take() {
self.input_data.insert("min".to_string(), json!(min));
}
if let Some(max) = number_properties.max.take() {
self.input_data.insert("max".to_string(), json!(max));
}
if let Some(step) = number_properties.step.take() {
self.input_data.insert("step".to_string(), json!(step));
}
if let Some(range_min) = number_properties.range_min.take() {
self.input_data.insert("range_min".to_string(), json!(range_min));
}
if let Some(range_max) = number_properties.range_max.take() {
self.input_data.insert("range_max".to_string(), json!(range_max));
}
self.input_data.insert("mode".to_string(), json!(number_properties.mode));
self.input_data.insert("is_integer".to_string(), Value::Bool(number_properties.is_integer));
self.input_data.insert("blank_assist".to_string(), Value::Bool(number_properties.blank_assist));
self.widget_override = Some("number".to_string());
}
WidgetOverride::Vec2(vec2_properties) => {
self.input_data.insert("x".to_string(), json!(vec2_properties.x));
self.input_data.insert("y".to_string(), json!(vec2_properties.y));
self.input_data.insert("unit".to_string(), json!(vec2_properties.unit));
self.input_data.insert("is_integer".to_string(), Value::Bool(vec2_properties.is_integer));
if let Some(min) = vec2_properties.min {
self.input_data.insert("min".to_string(), json!(min));
}
self.widget_override = Some("vec2".to_string());
}
WidgetOverride::Custom(lambda_name) => {
self.widget_override = Some(lambda_name);
}
};
self
}
pub fn with_description(mut self, description: &str) -> Self {
self.input_description = description.to_string();
self
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct InputTransientMetadata {
pub(crate) wire: TransientMetadata<WirePathUpdate>,
// downstream_protonode: populated for all inputs after each compile
// types: populated for each protonode after each
}
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentNodePersistentMetadata {
/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the implementation name is displayed to the user in italics.
#[serde(default)]
pub display_name: String,
/// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function.
/// Must match the length of node inputs
pub input_metadata: Vec<InputMetadata>,
pub output_names: Vec<String>,
/// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI.
#[serde(default)]
pub locked: bool,
/// Indicates that the node will be shown in the Properties panel when it would otherwise be empty, letting a user easily edit its properties by just deselecting everything.
#[serde(default)]
pub pinned: bool,
/// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.
/// All fields in NodeTypePersistentMetadata should automatically be updated by using the network interface API
pub node_type_metadata: NodeTypePersistentMetadata,
/// This should always be Some for nodes with a [`DocumentNodeImplementation::Network`], and none for [`DocumentNodeImplementation::ProtoNode`]
pub network_metadata: Option<NodeNetworkMetadata>,
}
impl DocumentNodePersistentMetadata {
pub fn is_layer(&self) -> bool {
matches!(self.node_type_metadata, NodeTypePersistentMetadata::Layer(_))
}
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct InputMetadata {
pub persistent_metadata: InputPersistentMetadata,
#[serde(skip)]
pub(crate) transient_metadata: InputTransientMetadata,
}
impl Clone for InputMetadata {
fn clone(&self) -> Self {
InputMetadata {
persistent_metadata: self.persistent_metadata.clone(),
transient_metadata: Default::default(),
}
}
}
impl PartialEq for InputMetadata {
fn eq(&self, other: &Self) -> bool {
self.persistent_metadata == other.persistent_metadata
}
}
impl From<(&str, &str)> for InputMetadata {
fn from(input_name_and_description: (&str, &str)) -> Self {
InputMetadata {
persistent_metadata: InputPersistentMetadata::default()
.with_name(input_name_and_description.0)
.with_description(input_name_and_description.1),
..Default::default()
}
}
}
impl InputMetadata {
pub fn with_name_description_override(input_name: &str, description: &str, widget_override: WidgetOverride) -> Self {
InputMetadata {
persistent_metadata: InputPersistentMetadata::default().with_name(input_name).with_description(description).with_override(widget_override),
..Default::default()
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum NodeTypePersistentMetadata {
Layer(LayerPersistentMetadata),
Node(NodePersistentMetadata),
}
impl Default for NodeTypePersistentMetadata {
fn default() -> Self {
NodeTypePersistentMetadata::node(IVec2::ZERO)
}
}
impl NodeTypePersistentMetadata {
pub fn node(position: IVec2) -> NodeTypePersistentMetadata {
NodeTypePersistentMetadata::Node(NodePersistentMetadata {
position: NodePosition::Absolute(position),
})
}
pub fn layer(position: IVec2) -> NodeTypePersistentMetadata {
NodeTypePersistentMetadata::Layer(LayerPersistentMetadata {
position: LayerPosition::Absolute(position),
owned_nodes: TransientMetadata::default(),
})
}
}
/// All fields in LayerMetadata should automatically be updated by using the network interface API
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct LayerPersistentMetadata {
// TODO: Store click target for the preview button, which will appear when the node is a selected/(hovered?) layer node
// preview_click_target: Option<ClickTarget>,
/// Stores the position of a layer node, which can either be Absolute or Stack
pub position: LayerPosition,
/// All nodes that should be moved when the layer is moved.
#[serde(skip)]
pub owned_nodes: TransientMetadata<HashSet<NodeId>>,
}
impl PartialEq for LayerPersistentMetadata {
fn eq(&self, other: &Self) -> bool {
self.position == other.position
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NodePersistentMetadata {
/// Stores the position of a non layer node, which can either be Absolute or Chain
pub(crate) position: NodePosition,
}
impl NodePersistentMetadata {
pub fn new(position: NodePosition) -> Self {
Self { position }
}
pub fn position(&self) -> &NodePosition {
&self.position
}
}
/// A layer can either be position as Absolute or in a Stack
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum LayerPosition {
// Position of the node in grid spaces
Absolute(IVec2),
// A layer is in a Stack when it feeds into the bottom input of a layer. The Y position stores the vertical distance between the layer and its upstream sibling/parent.
Stack(u32),
}
/// A node can either be position as Absolute or in a Chain
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum NodePosition {
// Position of the node in grid spaces
Absolute(IVec2),
// In a chain the position is based on the number of nodes to the first layer node
Chain,
}
/// Cached metadata that should be calculated when creating a node, and should be recalculated when modifying a node property that affects one of the cached fields.
#[derive(Debug, Default, Clone)]
pub struct DocumentNodeTransientMetadata {
// The click targets are stored as a single struct since it is very rare for only one to be updated, and recomputing all click targets in one function is more efficient than storing them separately.
pub click_targets: TransientMetadata<DocumentNodeClickTargets>,
// Metadata that is specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.
pub node_type_metadata: NodeTypeTransientMetadata,
}
#[derive(Debug, Clone)]
pub struct DocumentNodeClickTargets {
/// In order to keep the displayed position of the node in sync with the click target, the displayed position of a node is derived from the top left of the click target
/// Ensure node_click_target is kept in sync when modifying a node property that changes its size. Currently this is alias, inputs, is_layer, and metadata
pub node_click_target: ClickTarget,
/// Stores all port click targets in node graph space.
pub port_click_targets: Ports,
// Click targets that are specific to either nodes or layers, which are chosen states for displaying as a left-to-right node or bottom-to-top layer.
pub node_type_metadata: NodeTypeClickTargets,
}
#[derive(Debug, Default, Clone)]
pub enum NodeTypeTransientMetadata {
Layer(LayerTransientMetadata),
#[default]
Node, // No transient data is stored exclusively for nodes
}
#[derive(Debug, Default, Clone)]
pub struct LayerTransientMetadata {
// Stores the width in grid units for layer nodes from the left edge of the thumbnail (+12px padding since thumbnail ends between grid spaces) to the left end of the node
/// This is necessary since calculating the layer width through web_sys is very slow
pub layer_width: TransientMetadata<u32>,
// Should not be a performance concern to calculate when needed with chain_width.
// Stores the width in grid units for layer nodes from the left edge of the thumbnail to the end of the chain
// chain_width: u32,
}
#[derive(Debug, Clone)]
pub enum NodeTypeClickTargets {
Layer(Box<LayerClickTargets>),
Node, // No transient click targets are stored exclusively for nodes
}
/// All fields in TransientLayerMetadata should automatically be updated by using the network interface API
#[derive(Debug, Clone)]
pub struct LayerClickTargets {
/// Cache for all visibility buttons. Should be automatically updated when update_click_target is called
pub visibility_click_target: ClickTarget,
/// Cache for the lock icon button, only present when the layer is locked.
pub lock_click_target: Option<ClickTarget>,
/// Cache for the grip icon, which is next to the visibility button.
pub grip_click_target: ClickTarget,
/// Cache for the layer's display-name text bounds. Used to detect double-click rename and
/// to skip the drill-into-subgraph behavior when the click lands on the name itself.
/// `None` for layers whose display name is empty.
pub name_click_target: Option<ClickTarget>,
// TODO: Store click target for the preview button, which will appear when the node is a selected/(hovered?) layer node
// preview_click_target: ClickTarget,
}
pub enum LayerClickTargetTypes {
Visibility,
Lock,
Grip,
Name,
// Preview,
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NavigationMetadata {
/// The current pan, and zoom state of the viewport's view of the node graph.
/// Ensure `DocumentMessage::UpdateDocumentTransform` is called when the pan, zoom, or transform changes.
pub node_graph_ptz: PTZ,
// TODO: Eventually remove once te click targets are extracted from the native render
/// Transform from node graph space to viewport space.
pub node_graph_to_viewport: DAffine2,
// TODO: Eventually remove once the import/export positions are extracted from the native render
/// The width of the node graph in viewport space
#[serde(default)]
pub node_graph_width: f64,
}
// PartialEq required by message handlers
/// All persistent editor and Graphene data for a node. Used to serialize and deserialize a node, pass it through the editor, and create definitions.
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NodeTemplate {
pub document_node: DocumentNode,
pub persistent_node_metadata: DocumentNodePersistentMetadata,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum TransactionStatus {
Started,
Modified,
#[default]
Finished,
}
pub(crate) fn collect_network_resources(network: &NodeNetwork, out: &mut HashSet<ResourceId>) {
for node in network.nodes.values() {
collect_node_resources(node, out);
}
}
/// Collects resource IDs referenced by a node and its nested networks.
pub fn collect_node_resources(node: &DocumentNode, out: &mut HashSet<ResourceId>) {
for input in &node.inputs {
if let NodeInput::Value { tagged_value, .. } = input
&& let TaggedValue::Resource(id) = &**tagged_value
{
out.insert(*id);
}
}
if let DocumentNodeImplementation::Network(nested) = &node.implementation {
collect_network_resources(nested, out);
}
}