Move node visibility flag from NodeNetwork to DocumentNode (#1708)

* protonode -> proto node

* Move node visibility flag from NodeNetwork to DocumentNode

* Add serde default for new field

* Logic improvements
This commit is contained in:
Keavon Chambers
2024-03-27 05:17:08 -07:00
committed by GitHub
parent d3e3e19822
commit 27f9e3f00e
27 changed files with 161 additions and 140 deletions
@@ -57,7 +57,7 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut), entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy), entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy),
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes), entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes),
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedHidden), entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility),
// //
// TransformLayerMessage // TransformLayerMessage
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation), entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
@@ -822,7 +822,7 @@ impl DocumentMessageHandler {
self.metadata self.metadata
.root() .root()
.descendants(&self.metadata) .descendants(&self.metadata)
.filter(|&layer| self.selected_nodes.layer_visible(layer, self.network(), self.metadata())) .filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata()))
.filter(|&layer| !is_artboard(layer, network)) .filter(|&layer| !is_artboard(layer, network))
.filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets))) .filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.metadata.transform_to_document(*layer)))) .filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.metadata.transform_to_document(*layer))))
@@ -835,7 +835,7 @@ impl DocumentMessageHandler {
self.metadata self.metadata
.root() .root()
.descendants(&self.metadata) .descendants(&self.metadata)
.filter(|&layer| self.selected_nodes.layer_visible(layer, self.network(), self.metadata())) .filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata()))
.filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets))) .filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.metadata.transform_to_document(*layer)))) .filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.metadata.transform_to_document(*layer))))
.map(|(layer, _)| layer) .map(|(layer, _)| layer)
@@ -849,7 +849,7 @@ impl DocumentMessageHandler {
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space /// Get the combined bounding box of the click targets of the selected visible layers in viewport space
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes self.selected_nodes
.selected_visible_layers(self.network(), self.metadata()) .selected_visible_layers(self.metadata())
.filter_map(|layer| self.metadata.bounding_box_viewport(layer)) .filter_map(|layer| self.metadata.bounding_box_viewport(layer))
.reduce(graphene_core::renderer::Quad::combine_bounds) .reduce(graphene_core::renderer::Quad::combine_bounds)
} }
@@ -887,13 +887,6 @@ impl DocumentMessageHandler {
Ok(document) Ok(document)
} }
/// Returns the bounding boxes for all visible layers.
pub fn bounding_boxes(&self) -> impl Iterator<Item = [DVec2; 2]> + '_ {
// TODO: Remove this function entirely?
// self.visible_layers().filter_map(|path| self.document_legacy.viewport_bounding_box(path, font_cache).ok()?)
std::iter::empty()
}
/// Called recursively by the entry function [`serialize_root`]. /// Called recursively by the entry function [`serialize_root`].
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec<u64>, data_section: &mut Vec<u64>, path: &mut Vec<LayerNodeIdentifier>) { fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec<u64>, data_section: &mut Vec<u64>, path: &mut Vec<LayerNodeIdentifier>) {
let mut space = 0; let mut space = 0;
@@ -89,13 +89,13 @@ pub enum NodeGraphMessage {
ShiftNode { ShiftNode {
node_id: NodeId, node_id: NodeId,
}, },
ToggleSelectedHidden, ToggleSelectedVisibility,
ToggleHidden { ToggleVisibility {
node_id: NodeId, node_id: NodeId,
}, },
SetHidden { SetVisibility {
node_id: NodeId, node_id: NodeId,
hidden: bool, visible: bool,
}, },
SetName { SetName {
node_id: NodeId, node_id: NodeId,
@@ -59,8 +59,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
load_network_structure(document_network, document_metadata, selected_nodes, collapsed); load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
} }
NodeGraphMessage::SelectedNodesUpdated => { NodeGraphMessage::SelectedNodesUpdated => {
self.update_selection_action_buttons(document_network, selected_nodes, responses); self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
self.update_selected(document_network, selected_nodes, responses); self.update_selected(document_network, document_metadata, selected_nodes, responses);
if selected_nodes.selected_layers(document_metadata).count() <= 1 { if selected_nodes.selected_layers(document_metadata).count() <= 1 {
responses.add(DocumentMessage::SetRangeSelectionLayer { responses.add(DocumentMessage::SetRangeSelectionLayer {
new_layer: selected_nodes.selected_layers(document_metadata).next(), new_layer: selected_nodes.selected_layers(document_metadata).next(),
@@ -195,7 +195,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if let Some(network) = document_network.nested_network(&self.network) { if let Some(network) = document_network.nested_network(&self.network) {
self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses); self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses);
} }
self.update_selected(document_network, selected_nodes, responses); self.update_selected(document_network, document_metadata, selected_nodes, responses);
} }
NodeGraphMessage::DuplicateSelectedNodes => { NodeGraphMessage::DuplicateSelectedNodes => {
if let Some(network) = document_network.nested_network(&self.network) { if let Some(network) = document_network.nested_network(&self.network) {
@@ -221,7 +221,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::InsertNode { node_id, document_node }); responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
} }
self.update_selected(document_network, selected_nodes, responses); self.update_selected(document_network, document_metadata, selected_nodes, responses);
} }
} }
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => { NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => {
@@ -234,7 +234,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if let Some(network) = document_network.nested_network(&self.network) { if let Some(network) = document_network.nested_network(&self.network) {
self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses); self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses);
} }
self.update_selected(document_network, selected_nodes, responses); self.update_selected(document_network, document_metadata, selected_nodes, responses);
} }
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => { NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
let Some(network) = document_network.nested_network(&self.network) else { let Some(network) = document_network.nested_network(&self.network) else {
@@ -447,36 +447,46 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses); self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses);
} }
NodeGraphMessage::ToggleSelectedHidden => { NodeGraphMessage::ToggleSelectedVisibility => {
if let Some(network) = document_network.nested_network(&self.network) { responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::StartTransaction);
let new_hidden = !selected_nodes.selected_nodes().any(|id| network.disabled.contains(id)); // If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
for &node_id in selected_nodes.selected_nodes() { let visible = selected_nodes.selected_nodes().all(|&node_id| document_metadata.node_is_visible(node_id));
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: new_hidden }); let visible = !visible;
}
for &node_id in selected_nodes.selected_nodes() {
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
} }
} }
NodeGraphMessage::ToggleHidden { node_id } => { NodeGraphMessage::ToggleVisibility { node_id } => {
if let Some(network) = document_network.nested_network(&self.network) { let visible = document_metadata.node_is_visible(node_id);
let new_hidden = !network.disabled.contains(&node_id); let visible = !visible;
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: new_hidden });
} responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
} }
NodeGraphMessage::SetHidden { node_id, hidden } => { NodeGraphMessage::SetVisibility { node_id, visible } => {
if let Some(network) = document_network.nested_network_mut(&self.network) { (|| {
if !hidden { let Some(network) = document_network.nested_network_mut(&self.network) else { return };
network.disabled.retain(|&id| node_id != id);
} else if !network.imports.contains(&node_id) && !network.original_outputs().iter().any(|output| output.node_id == node_id) { let input_or_output = network.imports.contains(&node_id) || network.original_outputs().iter().any(|output| output.node_id == node_id);
network.disabled.push(node_id); let visibility = if visible {
} true
} else if !input_or_output {
false
} else {
return;
};
// Set what we determined shall be the visibility of the node
let Some(node) = network.nodes.get_mut(&node_id) else { return };
node.visible = visibility;
// Only generate node graph if one of the selected nodes is connected to the output // Only generate node graph if one of the selected nodes is connected to the output
if network.connected_to_output(node_id) { if network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
} })();
self.update_selection_action_buttons(document_network, selected_nodes, responses); self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
} }
NodeGraphMessage::SetName { node_id, name } => { NodeGraphMessage::SetName { node_id, name } => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
@@ -508,7 +518,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
} }
self.update_selection_action_buttons(document_network, selected_nodes, responses); self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
@@ -522,7 +532,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let node_types = document_node_types::collect_node_types(); let node_types = document_node_types::collect_node_types();
responses.add(FrontendMessage::UpdateNodeTypes { node_types }); responses.add(FrontendMessage::UpdateNodeTypes { node_types });
} }
self.update_selected(document_network, selected_nodes, responses); self.update_selected(document_network, document_metadata, selected_nodes, responses);
} }
NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => { NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => {
self.resolved_types = resolved_types; self.resolved_types = resolved_types;
@@ -540,7 +550,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
impl NodeGraphMessageHandler { impl NodeGraphMessageHandler {
pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList { pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList {
if self.has_selection && graph_open { if self.has_selection && graph_open {
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleSelectedHidden) actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleSelectedVisibility)
} else { } else {
actions!(NodeGraphMessageDiscriminant;) actions!(NodeGraphMessageDiscriminant;)
} }
@@ -554,8 +564,8 @@ impl NodeGraphMessageHandler {
}); });
} }
/// Updates the buttons for disable and preview /// Updates the buttons for visibility and preview
fn update_selection_action_buttons(&mut self, document_network: &NodeNetwork, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) { fn update_selection_action_buttons(&mut self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) {
if let Some(network) = document_network.nested_network(&self.network) { if let Some(network) = document_network.nested_network(&self.network) {
let mut widgets = Vec::new(); let mut widgets = Vec::new();
@@ -565,18 +575,18 @@ impl NodeGraphMessageHandler {
// If there is at least one other selected node then show the hide or show button // If there is at least one other selected node then show the hide or show button
if selection.next().is_some() { if selection.next().is_some() {
// Check if any of the selected nodes are disabled // Check if any of the selected nodes are disabled
let is_hidden = selected_nodes.selected_nodes().any(|id| network.disabled.contains(id)); let all_visible = selected_nodes.selected_nodes().all(|&id| document_metadata.node_is_visible(id));
// Check if multiple nodes are selected // Check if multiple nodes are selected
let multiple_nodes = selection.next().is_some(); let multiple_nodes = selection.next().is_some();
// Generate the enable or disable button accordingly // Generate the visible/hidden button accordingly
let (hide_show_label, hide_show_icon) = if is_hidden { ("Make Visible", "EyeHidden") } else { ("Make Hidden", "EyeVisible") }; let (hide_show_label, hide_show_icon) = if all_visible { ("Make Hidden", "EyeVisible") } else { ("Make Visible", "EyeHidden") };
let hide_button = TextButton::new(hide_show_label) let hide_button = TextButton::new(hide_show_label)
.icon(Some(hide_show_icon.to_string())) .icon(Some(hide_show_icon.to_string()))
.tooltip(if is_hidden { "Show selected nodes/layers" } else { "Hide selected nodes/layers" }.to_string() + if multiple_nodes { "s" } else { "" }) .tooltip(if all_visible { "Hide selected nodes/layers" } else { "Show selected nodes/layers" }.to_string() + if multiple_nodes { "s" } else { "" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedHidden)) .tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
.on_update(move |_| NodeGraphMessage::ToggleSelectedHidden.into()) .on_update(move |_| NodeGraphMessage::ToggleSelectedVisibility.into())
.widget_holder(); .widget_holder();
widgets.push(hide_button); widgets.push(hide_button);
@@ -745,7 +755,7 @@ impl NodeGraphMessageHandler {
exposed_outputs, exposed_outputs,
position: node.metadata.position.into(), position: node.metadata.position.into(),
previewed: network.outputs_contain(node_id), previewed: network.outputs_contain(node_id),
disabled: network.disabled.contains(&node_id), visible: node.visible,
errors: errors.map(|e| format!("{e:?}")), errors: errors.map(|e| format!("{e:?}")),
}); });
} }
@@ -774,7 +784,7 @@ impl NodeGraphMessageHandler {
parent_id: layer.parent(metadata).map(|parent| parent.to_node()), parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(), name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() }, tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
disabled: network.disabled.contains(&node_id), visible: node.visible,
}; };
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data }); responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
} }
@@ -795,8 +805,8 @@ impl NodeGraphMessageHandler {
} }
/// Updates the frontend's selection state in line with the backend /// Updates the frontend's selection state in line with the backend
fn update_selected(&mut self, document_network: &NodeNetwork, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) { fn update_selected(&mut self, document_network: &NodeNetwork, document_metadata: &DocumentMetadata, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) {
self.update_selection_action_buttons(document_network, selected_nodes, responses); self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
responses.add(FrontendMessage::UpdateNodeGraphSelection { responses.add(FrontendMessage::UpdateNodeGraphSelection {
selected: selected_nodes.selected_nodes_ref().clone(), selected: selected_nodes.selected_nodes_ref().clone(),
}); });
@@ -84,7 +84,7 @@ pub struct FrontendNode {
#[serde(rename = "exposedOutputs")] #[serde(rename = "exposedOutputs")]
pub exposed_outputs: Vec<FrontendGraphOutput>, pub exposed_outputs: Vec<FrontendGraphOutput>,
pub position: (i32, i32), pub position: (i32, i32),
pub disabled: bool, pub visible: bool,
pub previewed: bool, pub previewed: bool,
pub errors: Option<String>, pub errors: Option<String>,
} }
@@ -8,7 +8,7 @@ use std::collections::HashMap;
pub enum Clipboard { pub enum Clipboard {
Internal, Internal,
_InternalClipboardCount, // Keep this as the last entry in internal clipboards since it is used for counting the number of enum variants _InternalClipboardCount, // Keep this as the last entry of **internal** clipboards since it is used for counting the number of enum variants
Device, Device,
} }
@@ -19,7 +19,7 @@ pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u
pub struct CopyBufferEntry { pub struct CopyBufferEntry {
pub nodes: HashMap<NodeId, DocumentNode>, pub nodes: HashMap<NodeId, DocumentNode>,
pub selected: bool, pub selected: bool,
pub disabled: bool, pub visible: bool,
pub collapsed: bool, pub collapsed: bool,
pub alias: String, pub alias: String,
} }
@@ -14,12 +14,15 @@ use std::num::NonZeroU64;
// DocumentMetadata // DocumentMetadata
// ================ // ================
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
// TODO: it might be better to have a system that can query the state of the node network on demand.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DocumentMetadata { pub struct DocumentMetadata {
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>, upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
structure: HashMap<LayerNodeIdentifier, NodeRelations>, structure: HashMap<LayerNodeIdentifier, NodeRelations>,
artboards: HashSet<LayerNodeIdentifier>, artboards: HashSet<LayerNodeIdentifier>,
folders: HashSet<LayerNodeIdentifier>, folders: HashSet<LayerNodeIdentifier>,
hidden: HashSet<NodeId>,
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
/// Transform from document space to viewport space. /// Transform from document space to viewport space.
pub document_to_viewport: DAffine2, pub document_to_viewport: DAffine2,
@@ -29,10 +32,11 @@ impl Default for DocumentMetadata {
fn default() -> Self { fn default() -> Self {
Self { Self {
upstream_transforms: HashMap::new(), upstream_transforms: HashMap::new(),
click_targets: HashMap::new(),
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]), structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
artboards: HashSet::new(), artboards: HashSet::new(),
folders: HashSet::new(), folders: HashSet::new(),
hidden: HashSet::new(),
click_targets: HashMap::new(),
document_to_viewport: DAffine2::IDENTITY, document_to_viewport: DAffine2::IDENTITY,
} }
} }
@@ -118,6 +122,10 @@ impl DocumentMetadata {
self.artboards.contains(&layer) self.artboards.contains(&layer)
} }
pub fn node_is_visible(&self, layer: NodeId) -> bool {
!self.hidden.contains(&layer)
}
/// Folders sorted from most nested to least nested /// Folders sorted from most nested to least nested
pub fn folders_sorted_by_most_nested(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Vec<LayerNodeIdentifier> { pub fn folders_sorted_by_most_nested(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Vec<LayerNodeIdentifier> {
let mut folders: Vec<_> = layers.filter(|layer| self.folders.contains(layer)).collect(); let mut folders: Vec<_> = layers.filter(|layer| self.folders.contains(layer)).collect();
@@ -138,8 +146,9 @@ impl DocumentMetadata {
} }
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]); self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]);
self.folders = HashSet::new();
self.artboards = HashSet::new(); self.artboards = HashSet::new();
self.folders = HashSet::new();
self.hidden = HashSet::new();
let id = graph.exports[0].node_id; let id = graph.exports[0].node_id;
let Some(output_node) = graph.nodes.get(&id) else { let Some(output_node) = graph.nodes.get(&id) else {
@@ -150,22 +159,26 @@ impl DocumentMetadata {
}; };
let parent = LayerNodeIdentifier::ROOT; let parent = LayerNodeIdentifier::ROOT;
let mut stack = vec![(layer_node, node_id, parent)]; let mut stack = vec![(layer_node, node_id, parent)];
while let Some((node, id, parent)) = stack.pop() { while let Some((node, node_id, parent)) = stack.pop() {
let mut current = Some((node, id)); let mut current = Some((node, node_id));
while let Some(&(current_node, current_id)) = current.as_ref() { while let Some(&(current_node, current_node_id)) = current.as_ref() {
let current_identifier = LayerNodeIdentifier::new_unchecked(current_id); let current_layer_id = LayerNodeIdentifier::new_unchecked(current_node_id);
if !self.structure.contains_key(&current_identifier) { if !self.structure.contains_key(&current_layer_id) {
parent.push_child(self, current_identifier); parent.push_child(self, current_layer_id);
if let Some((child_node, child_id)) = first_child_layer(graph, current_node) { if let Some((child_node, child_id)) = first_child_layer(graph, current_node) {
stack.push((child_node, child_id, current_identifier)); stack.push((child_node, child_id, current_layer_id));
} }
if is_artboard(current_identifier, graph) { if is_artboard(current_layer_id, graph) {
self.artboards.insert(current_identifier); self.artboards.insert(current_layer_id);
} }
if is_folder(current_identifier, graph) { if is_folder(current_layer_id, graph) {
self.folders.insert(current_identifier); self.folders.insert(current_layer_id);
}
if !current_node.visible {
self.hidden.insert(current_node_id);
} }
} }
@@ -532,9 +545,9 @@ impl<'a> Iterator for AxisIter<'a> {
} }
} }
// ============== // ===============
// DescendantsIter // DescendantsIter
// ============== // ===============
#[derive(Clone)] #[derive(Clone)]
pub struct DescendantsIter<'a> { pub struct DescendantsIter<'a> {
@@ -1,9 +1,9 @@
use graph_craft::document::{NodeId, NodeNetwork}; use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use graph_craft::document::NodeId;
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct RawBuffer(Vec<u8>); pub struct RawBuffer(Vec<u8>);
@@ -46,7 +46,7 @@ pub struct LayerPanelEntry {
#[serde(rename = "layerClassification")] #[serde(rename = "layerClassification")]
pub layer_classification: LayerClassification, pub layer_classification: LayerClassification,
pub expanded: bool, pub expanded: bool,
pub disabled: bool, pub visible: bool,
#[serde(rename = "parentId")] #[serde(rename = "parentId")]
pub parent_id: Option<NodeId>, pub parent_id: Option<NodeId>,
pub depth: usize, pub depth: usize,
@@ -56,12 +56,12 @@ pub struct LayerPanelEntry {
pub struct SelectedNodes(pub Vec<NodeId>); pub struct SelectedNodes(pub Vec<NodeId>);
impl SelectedNodes { impl SelectedNodes {
pub fn layer_visible(&self, layer: LayerNodeIdentifier, network: &NodeNetwork, metadata: &DocumentMetadata) -> bool { pub fn layer_visible(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
!layer.ancestors(metadata).any(|layer| network.disabled.contains(&layer.to_node())) layer.ancestors(metadata).all(|layer| metadata.node_is_visible(layer.to_node()))
} }
pub fn selected_visible_layers<'a>(&'a self, network: &'a NodeNetwork, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ { pub fn selected_visible_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.selected_layers(metadata).filter(move |&layer| self.layer_visible(layer, network, metadata)) self.selected_layers(metadata).filter(move |&layer| self.layer_visible(layer, metadata))
} }
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ { pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
@@ -202,7 +202,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
) )
.collect(), .collect(),
selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()), selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()),
disabled: !active_document.selected_nodes.layer_visible(layer, active_document.network(), active_document.metadata()), visible: active_document.selected_nodes.layer_visible(layer, active_document.metadata()),
collapsed: false, collapsed: false,
alias: previous_alias, alias: previous_alias,
}); });
@@ -388,8 +388,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
if entry.selected { if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] }); responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
} }
if entry.disabled { if !entry.visible {
responses.add(NodeGraphMessage::SetHidden { node_id: id, hidden: entry.disabled }); responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false });
} }
} }
}; };
@@ -419,8 +419,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
if entry.selected { if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] }); responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
} }
if entry.disabled { if !entry.visible {
responses.add(NodeGraphMessage::SetHidden { node_id: id, hidden: entry.disabled }); responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false });
} }
} }
@@ -45,7 +45,7 @@ impl Pivot {
/// Recomputes the pivot position and transform. /// Recomputes the pivot position and transform.
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
let mut layers = document.selected_nodes.selected_visible_layers(document.network(), document.metadata()); let mut layers = document.selected_nodes.selected_visible_layers(document.metadata());
let Some(first) = layers.next() else { let Some(first) = layers.next() else {
// If no layers are selected then we revert things back to default // If no layers are selected then we revert things back to default
self.normalized_pivot = DVec2::splat(0.5); self.normalized_pivot = DVec2::splat(0.5);
@@ -66,7 +66,7 @@ impl Pivot {
// If more than one layer is selected we use the AABB with the mean of the pivots // If more than one layer is selected we use the AABB with the mean of the pivots
let xy_summation = document let xy_summation = document
.selected_nodes .selected_nodes
.selected_visible_layers(document.network(), document.metadata()) .selected_visible_layers(document.metadata())
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network, &document.metadata)) .map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network, &document.metadata))
.reduce(|a, b| a + b) .reduce(|a, b| a + b)
.unwrap_or_default(); .unwrap_or_default();
@@ -101,7 +101,7 @@ impl Pivot {
/// Sets the viewport position of the pivot for all selected layers. /// Sets the viewport position of the pivot for all selected layers.
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) { for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let transform = Self::get_layer_pivot_transform(layer, document); let transform = Self::get_layer_pivot_transform(layer, document);
let pivot = transform.inverse().transform_point2(position); let pivot = transform.inverse().transform_point2(position);
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0. // Only update the pivot when computed position is finite. Infinite can happen when scale is 0.
@@ -255,7 +255,7 @@ impl SnapManager {
if candidates.len() > 10 { if candidates.len() > 10 {
return; return;
} }
if !document.selected_nodes.layer_visible(layer, &document.network, &document.metadata) { if !document.selected_nodes.layer_visible(layer, &document.metadata) {
return; return;
} }
if snap_data.ignore.contains(&layer) { if snap_data.ignore.contains(&layer) {
@@ -247,7 +247,7 @@ impl Fsm for GradientToolFsmState {
(_, GradientToolMessage::Overlays(mut overlay_context)) => { (_, GradientToolMessage::Overlays(mut overlay_context)) => {
let selected = tool_data.selected_gradient.as_ref(); let selected = tool_data.selected_gradient.as_ref();
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) { for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(gradient) = get_gradient(layer, &document.network) else { continue }; let Some(gradient) = get_gradient(layer, &document.network) else { continue };
let transform = gradient_space_transform(layer, document); let transform = gradient_space_transform(layer, document);
let dragging = selected.filter(|selected| selected.layer == layer).map(|selected| selected.dragging); let dragging = selected.filter(|selected| selected.layer == layer).map(|selected| selected.dragging);
@@ -318,7 +318,7 @@ impl Fsm for GradientToolFsmState {
self self
} }
(_, GradientToolMessage::InsertStop) => { (_, GradientToolMessage::InsertStop) => {
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) { for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(mut gradient) = get_gradient(layer, &document.network) else { continue }; let Some(mut gradient) = get_gradient(layer, &document.network) else { continue };
let transform = gradient_space_transform(layer, document); let transform = gradient_space_transform(layer, document);
@@ -357,7 +357,7 @@ impl Fsm for GradientToolFsmState {
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2); let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
let mut dragging = false; let mut dragging = false;
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) { for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(gradient) = get_gradient(layer, &document.network) else { continue }; let Some(gradient) = get_gradient(layer, &document.network) else { continue };
let transform = gradient_space_transform(layer, document); let transform = gradient_space_transform(layer, document);
@@ -391,7 +391,7 @@ impl Fsm for SelectToolFsmState {
tool_data.selected_layers_count = selected_layers_count; tool_data.selected_layers_count = selected_layers_count;
// Outline selected layers // Outline selected layers
for layer in document.selected_nodes.selected_visible_layers(document.network(), document.metadata()) { for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
} }
@@ -405,13 +405,13 @@ impl Fsm for SelectToolFsmState {
// Update bounds // Update bounds
let transform = document let transform = document
.selected_nodes .selected_nodes
.selected_visible_layers(document.network(), document.metadata()) .selected_visible_layers(document.metadata())
.next() .next()
.map(|layer| document.metadata().transform_to_viewport(layer)); .map(|layer| document.metadata().transform_to_viewport(layer));
let transform = transform.unwrap_or(DAffine2::IDENTITY); let transform = transform.unwrap_or(DAffine2::IDENTITY);
let bounds = document let bounds = document
.selected_nodes .selected_nodes
.selected_visible_layers(document.network(), document.metadata()) .selected_visible_layers(document.metadata())
.filter_map(|layer| { .filter_map(|layer| {
document document
.metadata() .metadata()
@@ -472,7 +472,7 @@ impl Fsm for SelectToolFsmState {
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position)) .map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
.unwrap_or_default(); .unwrap_or_default();
let mut selected: Vec<_> = document.selected_nodes.selected_visible_layers(document.network(), document.metadata()).collect(); let mut selected: Vec<_> = document.selected_nodes.selected_visible_layers(document.metadata()).collect();
let intersection = document.click(input.mouse.position, &document.network); let intersection = document.click(input.mouse.position, &document.network);
// If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the bounding box bounds, go into ResizingBounds mode.
@@ -215,7 +215,7 @@ impl TextToolData {
/// Set the editing state of the currently modifying layer /// Set the editing state of the currently modifying layer
fn set_editing(&self, editable: bool, font_cache: &FontCache, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { fn set_editing(&self, editable: bool, font_cache: &FontCache, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.network) { if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.network) {
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: editable }); responses.add(NodeGraphMessage::SetVisibility { node_id, visible: !editable });
} }
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) { if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
+2 -2
View File
@@ -190,7 +190,7 @@ impl NodeRuntime {
image_frame: None, image_frame: None,
}; };
// Required to ensure that the appropriate protonodes are reinserted when the Editor API changes. // Required to ensure that the appropriate proto nodes are reinserted when the Editor API changes.
let mut graph_input_hash = DefaultHasher::new(); let mut graph_input_hash = DefaultHasher::new();
editor_api.font_cache.hash(&mut graph_input_hash); editor_api.font_cache.hash(&mut graph_input_hash);
let font_hash_code = graph_input_hash.finish(); let font_hash_code = graph_input_hash.finish();
@@ -218,7 +218,7 @@ impl NodeRuntime {
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?");
if let Err(e) = self.executor.update(proto_network).await { if let Err(e) = self.executor.update(proto_network).await {
self.node_graph_errors = e; self.node_graph_errors = e;
} else { } else {
+3 -1
View File
@@ -50,7 +50,7 @@ module.exports = {
camelcase: ["error", { properties: "always" }], camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"], "eol-last": ["error", "always"],
"max-len": ["error", { code: 200, tabWidth: 4 }], "max-len": ["error", { code: 200, tabWidth: 4, ignorePattern: `d="([\\s\\S]*?)"` }],
"prefer-destructuring": "off", "prefer-destructuring": "off",
"no-console": "warn", "no-console": "warn",
"no-debugger": "warn", "no-debugger": "warn",
@@ -81,6 +81,8 @@ module.exports = {
// Import plugin config (for intelligently validating module import statements) // Import plugin config (for intelligently validating module import statements)
"import/no-unresolved": "error", "import/no-unresolved": "error",
// `no-duplicates` disabled due to <https://github.com/import-js/eslint-plugin-import/issues/1479#issuecomment-1789527447>. Reenable if that issue gets fixed.
"import/no-duplicates": "off",
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"import/no-relative-packages": "error", "import/no-relative-packages": "error",
"import/order": [ "import/order": [
+2 -2
View File
@@ -417,8 +417,8 @@
class={"visibility"} class={"visibility"}
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())} action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
size={24} size={24}
icon={listing.entry.disabled ? "EyeHidden" : "EyeVisible"} icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
tooltip={listing.entry.disabled ? "Disabled" : "Enabled"} tooltip={listing.entry.visible ? "Visible" : "Hidden"}
/> />
</LayoutRow> </LayoutRow>
{/each} {/each}
+4 -4
View File
@@ -795,7 +795,7 @@
class="layer" class="layer"
class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)} class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)}
class:previewed={node.previewed} class:previewed={node.previewed}
class:disabled={node.disabled} class:disabled={!node.visible}
style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)} style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
style:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)} style:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`} style:--clip-path-id={`url(#${clipPathId})`}
@@ -891,8 +891,8 @@
class={"visibility"} class={"visibility"}
action={(e) => (toggleLayerVisibility(node.id), e?.stopPropagation())} action={(e) => (toggleLayerVisibility(node.id), e?.stopPropagation())}
size={24} size={24}
icon={node.disabled ? "EyeHidden" : "EyeVisible"} icon={node.visible ? "EyeVisible" : "EyeHidden"}
tooltip={node.disabled ? "Disabled" : "Enabled"} tooltip={node.visible ? "Visible" : "Hidden"}
/> />
<svg class="border-mask" width="0" height="0"> <svg class="border-mask" width="0" height="0">
@@ -913,7 +913,7 @@
class="node" class="node"
class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)} class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)}
class:previewed={node.previewed} class:previewed={node.previewed}
class:disabled={node.disabled} class:disabled={!node.visible}
style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)} style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
style:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)} style:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`} style:--clip-path-id={`url(#${clipPathId})`}
+2 -2
View File
@@ -124,7 +124,7 @@ export class FrontendNode {
readonly previewed!: boolean; readonly previewed!: boolean;
readonly disabled!: boolean; readonly visible!: boolean;
readonly errors!: string | undefined; readonly errors!: string | undefined;
} }
@@ -613,7 +613,7 @@ export class LayerPanelEntry {
expanded!: boolean; expanded!: boolean;
disabled!: boolean; visible!: boolean;
parentId!: bigint | undefined; parentId!: bigint | undefined;
+2 -2
View File
@@ -757,8 +757,8 @@ impl JsEditorHandle {
/// Toggle visibility of a layer from the layer list /// Toggle visibility of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerVisibility)] #[wasm_bindgen(js_name = toggleLayerVisibility)]
pub fn toggle_layer_visibility(&self, id: u64) { pub fn toggle_layer_visibility(&self, id: u64) {
let id = NodeId(id); let node_id = NodeId(id);
let message = NodeGraphMessage::ToggleHidden { node_id: id }; let message = NodeGraphMessage::ToggleVisibility { node_id };
self.dispatch(message); self.dispatch(message);
} }
+2 -2
View File
@@ -60,7 +60,7 @@ pub fn multiply_opacity(document_node: &DocumentNode, node_id: NodeId, _context:
} }
``` ```
## Graphene (protonode executor) ## Graphene (proto node executor)
The graphene crate (found in `gcore/`) and the graphene standard library (found in `gstd/`) is where actual implementation for nodes are located. The graphene crate (found in `gcore/`) and the graphene standard library (found in `gstd/`) is where actual implementation for nodes are located.
@@ -100,7 +100,7 @@ fn test_opacity_node() {
The `graphene_core::value::CopiedNode` is a node that, when evaluated, copies `10_f32` and returns it. The `graphene_core::value::CopiedNode` is a node that, when evaluated, copies `10_f32` and returns it.
## Creating a new protonode ## Creating a new proto node
Instead of manually implementing the `Node` trait with complex generics, one can use the `node_fn` macro, which can be applied to a function like `opacity_node` with an attribute of the name of the node: Instead of manually implementing the `Node` trait with complex generics, one can use the `node_fn` macro, which can be applied to a function like `opacity_node` with an attribute of the name of the node:
@@ -39,7 +39,6 @@ fn add_network() -> NodeNetwork {
NodeNetwork { NodeNetwork {
imports: vec![], imports: vec![],
exports: vec![NodeOutput::new(NodeId(0), 0)], exports: vec![NodeOutput::new(NodeId(0), 0)],
disabled: vec![],
previous_outputs: None, previous_outputs: None,
nodes: [DocumentNode { nodes: [DocumentNode {
name: "Blend Image".into(), name: "Blend Image".into(),
+20 -16
View File
@@ -152,18 +152,24 @@ pub struct DocumentNode {
/// Now, the call from `F` directly reaches the `CacheNode` and the `CacheNode` can decide whether to call `G.eval(input_from_f)` /// Now, the call from `F` directly reaches the `CacheNode` and the `CacheNode` can decide whether to call `G.eval(input_from_f)`
/// in the event of a cache miss or just return the cached data in the event of a cache hit. /// in the event of a cache miss or just return the cached data in the event of a cache hit.
pub manual_composition: Option<Type>, pub manual_composition: Option<Type>,
// TODO: Remove once this references its definition instead (see above TODO).
/// Indicates to the UI if a primary output should be drawn for this node.
/// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output.
#[serde(default = "return_true")] #[serde(default = "return_true")]
pub has_primary_output: bool, pub has_primary_output: bool,
// A nested document network or a proto-node identifier. // A nested document network or a proto-node identifier.
pub implementation: DocumentNodeImplementation, pub implementation: DocumentNodeImplementation,
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.
#[serde(default = "return_true")]
pub visible: bool,
/// Metadata about the node including its position in the graph UI. /// Metadata about the node including its position in the graph UI.
pub metadata: DocumentNodeMetadata, pub metadata: DocumentNodeMetadata,
/// When two different protonodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed. /// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed.
/// See [`crate::proto::ProtoNetwork::generate_stable_node_ids`] for details. /// See [`crate::proto::ProtoNetwork::generate_stable_node_ids`] for details.
/// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph. /// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph.
#[serde(default)] #[serde(default)]
pub skip_deduplication: bool, pub skip_deduplication: bool,
/// Used as a hash of the graph input where applicable. This ensures that protonodes that depend on the graph's input are always regenerated. /// Used as a hash of the graph input where applicable. This ensures that proto nodes that depend on the graph's input are always regenerated.
#[serde(default)] #[serde(default)]
pub world_state_hash: u64, pub world_state_hash: u64,
/// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. /// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called.
@@ -185,7 +191,7 @@ pub struct Source {
pub struct OriginalLocation { pub struct OriginalLocation {
/// The original location to the document node - e.g. [grandparent_id, parent_id, node_id]. /// The original location to the document node - e.g. [grandparent_id, parent_id, node_id].
pub path: Option<Vec<NodeId>>, pub path: Option<Vec<NodeId>>,
/// Each document input source maps to one protonode input (however one protonode input may come from several sources) /// Each document input source maps to one proto node input (however one proto node input may come from several sources)
pub inputs_source: HashMap<Source, usize>, pub inputs_source: HashMap<Source, usize>,
/// A list of document sources for the node's output /// A list of document sources for the node's output
pub outputs_source: HashMap<Source, usize>, pub outputs_source: HashMap<Source, usize>,
@@ -203,6 +209,7 @@ impl Default for DocumentNode {
manual_composition: Default::default(), manual_composition: Default::default(),
has_primary_output: true, has_primary_output: true,
implementation: Default::default(), implementation: Default::default(),
visible: true,
metadata: Default::default(), metadata: Default::default(),
skip_deduplication: Default::default(), skip_deduplication: Default::default(),
world_state_hash: Default::default(), world_state_hash: Default::default(),
@@ -272,7 +279,7 @@ impl DocumentNode {
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value)) (ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
} }
NodeInput::Node { node_id, output_index, lambda } => { NodeInput::Node { node_id, output_index, lambda } => {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode. {:#?}", self.name); assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node. {:#?}", self.name);
let node = if lambda { ProtoNodeInput::NodeLambda(node_id) } else { ProtoNodeInput::Node(node_id) }; let node = if lambda { ProtoNodeInput::NodeLambda(node_id) } else { ProtoNodeInput::Node(node_id) };
(node, ConstructionArgs::Nodes(vec![])) (node, ConstructionArgs::Nodes(vec![]))
} }
@@ -445,9 +452,9 @@ pub enum DocumentNodeImplementation {
/// ///
/// A nested [`NodeNetwork`] that is flattened by the [`NodeNetwork::flatten`] function. /// A nested [`NodeNetwork`] that is flattened by the [`NodeNetwork::flatten`] function.
Network(NodeNetwork), Network(NodeNetwork),
/// This describes a (document) node implemented as a protonode. /// This describes a (document) node implemented as a proto node.
/// ///
/// A protonode identifier which can be found in `node_registry.rs`. /// A proto node identifier which can be found in `node_registry.rs`.
ProtoNode(ProtoNodeIdentifier), ProtoNode(ProtoNodeIdentifier),
/// The Extract variant is a tag which tells the compilation process to do something special. It invokes language-level functionality built for use by the ExtractNode to enable metaprogramming. /// The Extract variant is a tag which tells the compilation process to do something special. It invokes language-level functionality built for use by the ExtractNode to enable metaprogramming.
/// When the ExtractNode is compiled, it gets replaced by a value node containing a representation of the source code for the function/lambda of the document node that's fed into the ExtractNode /// When the ExtractNode is compiled, it gets replaced by a value node containing a representation of the source code for the function/lambda of the document node that's fed into the ExtractNode
@@ -523,9 +530,6 @@ pub struct NodeNetwork {
pub exports: Vec<NodeOutput>, pub exports: Vec<NodeOutput>,
/// The list of all nodes in this network. /// The list of all nodes in this network.
pub nodes: HashMap<NodeId, DocumentNode>, pub nodes: HashMap<NodeId, DocumentNode>,
/// Nodes that the user has disabled/hidden with the visibility eye icon.
/// These nodes get replaced with Identity nodes during the graph flattening step.
pub disabled: Vec<NodeId>,
/// In the case when another node is previewed (chosen by the user as a temporary output), this stores what it previously was so it can be restored later. /// In the case when another node is previewed (chosen by the user as a temporary output), this stores what it previously was so it can be restored later.
pub previous_outputs: Option<Vec<NodeOutput>>, pub previous_outputs: Option<Vec<NodeOutput>>,
} }
@@ -540,7 +544,6 @@ impl std::hash::Hash for NodeNetwork {
id.hash(state); id.hash(state);
node.hash(state); node.hash(state);
} }
self.disabled.hash(state);
self.previous_outputs.hash(state); self.previous_outputs.hash(state);
} }
} }
@@ -567,7 +570,6 @@ impl NodeNetwork {
imports: node.inputs.iter().filter(|input| matches!(input, NodeInput::Network(_))).map(|_| NodeId(0)).collect(), imports: node.inputs.iter().filter(|input| matches!(input, NodeInput::Network(_))).map(|_| NodeId(0)).collect(),
exports: vec![NodeOutput::new(NodeId(0), 0)], exports: vec![NodeOutput::new(NodeId(0), 0)],
nodes: [(NodeId(0), node)].into_iter().collect(), nodes: [(NodeId(0), node)].into_iter().collect(),
disabled: vec![],
previous_outputs: None, previous_outputs: None,
} }
} }
@@ -815,7 +817,6 @@ impl NodeNetwork {
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) { pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
self.imports.iter_mut().for_each(|id| *id = f(*id)); self.imports.iter_mut().for_each(|id| *id = f(*id));
self.exports.iter_mut().for_each(|output| output.node_id = f(output.node_id)); self.exports.iter_mut().for_each(|output| output.node_id = f(output.node_id));
self.disabled.iter_mut().for_each(|id| *id = f(*id));
self.previous_outputs self.previous_outputs
.iter_mut() .iter_mut()
.for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id))); .for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id)));
@@ -843,7 +844,7 @@ impl NodeNetwork {
outwards_links outwards_links
} }
/// Populate the [`DocumentNode::path`], which stores the location of the document node to allow for matching the resulting protonodes to the document node for the purposes of typing and finding monitor nodes. /// Populate the [`DocumentNode::path`], which stores the location of the document node to allow for matching the resulting proto nodes to the document node for the purposes of typing and finding monitor nodes.
pub fn generate_node_paths(&mut self, prefix: &[NodeId]) { pub fn generate_node_paths(&mut self, prefix: &[NodeId]) {
for (node_id, node) in &mut self.nodes { for (node_id, node) in &mut self.nodes {
let mut new_path = prefix.to_vec(); let mut new_path = prefix.to_vec();
@@ -930,8 +931,11 @@ impl NodeNetwork {
return; return;
}; };
if node.implementation != DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()) && self.disabled.contains(&id) { // If the node is hidden, replace it with an identity node
node.implementation = DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()); let identity_node = DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into());
if !node.visible && node.implementation != identity_node {
node.implementation = identity_node;
if node.is_layer() { if node.is_layer() {
// Connect layer node to the graphic group below // Connect layer node to the graphic group below
node.inputs.drain(..1); node.inputs.drain(..1);
@@ -939,6 +943,7 @@ impl NodeNetwork {
node.inputs.drain(1..); node.inputs.drain(1..);
} }
self.nodes.insert(id, node); self.nodes.insert(id, node);
return; return;
} }
@@ -983,7 +988,6 @@ impl NodeNetwork {
let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>(); let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();
// Copy nodes from the inner network into the parent network // Copy nodes from the inner network into the parent network
self.nodes.extend(inner_network.nodes); self.nodes.extend(inner_network.nodes);
self.disabled.extend(inner_network.disabled);
let mut network_offsets = HashMap::new(); let mut network_offsets = HashMap::new();
assert_eq!( assert_eq!(
+2 -2
View File
@@ -200,7 +200,7 @@ impl ConstructionArgs {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Hash, Eq)] #[derive(Debug, Clone, PartialEq, Hash, Eq)]
/// A protonode is an intermediate step between the `DocumentNode` and the boxed struct that actually runs the node (found in the [`BorrowTree`]). It has one primary input and several secondary inputs in [`ConstructionArgs`]. /// A proto node is an intermediate step between the `DocumentNode` and the boxed struct that actually runs the node (found in the [`BorrowTree`]). It has one primary input and several secondary inputs in [`ConstructionArgs`].
pub struct ProtoNode { pub struct ProtoNode {
pub construction_args: ConstructionArgs, pub construction_args: ConstructionArgs,
pub input: ProtoNodeInput, pub input: ProtoNodeInput,
@@ -718,7 +718,7 @@ impl TypingContext {
ConstructionArgs::Inline(ref inline) => vec![inline.ty.clone()], ConstructionArgs::Inline(ref inline) => vec![inline.ty.clone()],
}; };
// Get the node input type from the protonode declaration // Get the node input type from the proto node declaration
let input = match node.input { let input = match node.input {
ProtoNodeInput::None => concrete!(()), ProtoNodeInput::None => concrete!(()),
ProtoNodeInput::ManualComposition(ref ty) => ty.clone(), ProtoNodeInput::ManualComposition(ref ty) => ty.clone(),
@@ -17,7 +17,7 @@ pub struct DynamicExecutor {
output: NodeId, output: NodeId,
/// Stores all of the dynamic node structs. /// Stores all of the dynamic node structs.
tree: BorrowTree, tree: BorrowTree,
/// Stores the types of the protonodes. /// Stores the types of the proto nodes.
typing_context: TypingContext, typing_context: TypingContext,
// This allows us to keep the nodes around for one more frame which is used for introspection // This allows us to keep the nodes around for one more frame which is used for introspection
orphaned_nodes: Vec<NodeId>, orphaned_nodes: Vec<NodeId>,
@@ -111,11 +111,11 @@ impl<'a, I: StaticType + 'a> Executor<I, TaggedValue> for &'a DynamicExecutor {
pub struct BorrowTree { pub struct BorrowTree {
/// A hashmap of node IDs and dynamically typed nodes. /// A hashmap of node IDs and dynamically typed nodes.
nodes: HashMap<NodeId, SharedNodeContainer>, nodes: HashMap<NodeId, SharedNodeContainer>,
/// A hashmap from the document path to the protonode ID. /// A hashmap from the document path to the proto node ID.
source_map: HashMap<Vec<NodeId>, NodeId>, source_map: HashMap<Vec<NodeId>, NodeId>,
/// Each document input source maps to one protonode input (however one protonode input may come from several sources) /// Each document input source maps to one proto node input (however one proto node input may come from several sources)
inputs_source_map: HashMap<Source, (NodeId, usize)>, inputs_source_map: HashMap<Source, (NodeId, usize)>,
/// A mapping of document input sources to the (single) protonode output /// A mapping of document input sources to the (single) proto node output
outputs_source_map: HashMap<Source, NodeId>, outputs_source_map: HashMap<Source, NodeId>,
} }
@@ -193,7 +193,7 @@ impl BorrowTree {
.extend((0..params).flat_map(|i| proto_node.original_location.inputs(i).map(move |source| (source, (id, i))))); .extend((0..params).flat_map(|i| proto_node.original_location.inputs(i).map(move |source| (source, (id, i)))));
self.outputs_source_map.extend(proto_node.original_location.outputs(0).map(|source| (source, id))); self.outputs_source_map.extend(proto_node.original_location.outputs(0).map(|source| (source, id)));
for x in proto_node.original_location.outputs_source.values() { for x in proto_node.original_location.outputs_source.values() {
assert_eq!(*x, 0, "protonodes should refer to output index 0"); assert_eq!(*x, 0, "Proto nodes should refer to output index 0");
} }
match &proto_node.construction_args { match &proto_node.construction_args {
+1 -1
View File
@@ -38,7 +38,7 @@ module.exports = {
camelcase: ["error", { properties: "always" }], camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"], "eol-last": ["error", "always"],
"max-len": ["error", { code: 200, tabWidth: 4 }], "max-len": ["error", { code: 200, tabWidth: 4, ignorePattern: `d="([\\s\\S]*?)"` }],
"prefer-destructuring": "off", "prefer-destructuring": "off",
"no-console": "warn", "no-console": "warn",
"no-debugger": "warn", "no-debugger": "warn",
+1 -1
View File
@@ -35,7 +35,7 @@ module.exports = {
camelcase: ["error", { properties: "always" }], camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"], "linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"], "eol-last": ["error", "always"],
"max-len": ["error", { code: 200, tabWidth: 4 }], "max-len": ["error", { code: 200, tabWidth: 4, ignorePattern: `d="([\\s\\S]*?)"` }],
"prefer-destructuring": "off", "prefer-destructuring": "off",
"no-console": "warn", "no-console": "warn",
"no-debugger": "warn", "no-debugger": "warn",