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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 161 additions and 140 deletions

View file

@ -57,7 +57,7 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=NodeGraphMessage::Cut),
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=NodeGraphMessage::Copy),
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
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),

View file

@ -822,7 +822,7 @@ impl DocumentMessageHandler {
self.metadata
.root()
.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_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))))
@ -835,7 +835,7 @@ impl DocumentMessageHandler {
self.metadata
.root()
.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(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.metadata.transform_to_document(*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
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes
.selected_visible_layers(self.network(), self.metadata())
.selected_visible_layers(self.metadata())
.filter_map(|layer| self.metadata.bounding_box_viewport(layer))
.reduce(graphene_core::renderer::Quad::combine_bounds)
}
@ -887,13 +887,6 @@ impl DocumentMessageHandler {
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`].
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec<u64>, data_section: &mut Vec<u64>, path: &mut Vec<LayerNodeIdentifier>) {
let mut space = 0;

View file

@ -89,13 +89,13 @@ pub enum NodeGraphMessage {
ShiftNode {
node_id: NodeId,
},
ToggleSelectedHidden,
ToggleHidden {
ToggleSelectedVisibility,
ToggleVisibility {
node_id: NodeId,
},
SetHidden {
SetVisibility {
node_id: NodeId,
hidden: bool,
visible: bool,
},
SetName {
node_id: NodeId,

View file

@ -59,8 +59,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
}
NodeGraphMessage::SelectedNodesUpdated => {
self.update_selection_action_buttons(document_network, selected_nodes, responses);
self.update_selected(document_network, selected_nodes, responses);
self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
self.update_selected(document_network, document_metadata, selected_nodes, responses);
if selected_nodes.selected_layers(document_metadata).count() <= 1 {
responses.add(DocumentMessage::SetRangeSelectionLayer {
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) {
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 => {
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 });
}
self.update_selected(document_network, selected_nodes, responses);
self.update_selected(document_network, document_metadata, selected_nodes, responses);
}
}
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) {
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 } => {
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);
}
NodeGraphMessage::ToggleSelectedHidden => {
if let Some(network) = document_network.nested_network(&self.network) {
responses.add(DocumentMessage::StartTransaction);
NodeGraphMessage::ToggleSelectedVisibility => {
responses.add(DocumentMessage::StartTransaction);
let new_hidden = !selected_nodes.selected_nodes().any(|id| network.disabled.contains(id));
for &node_id in selected_nodes.selected_nodes() {
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: new_hidden });
}
// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
let visible = selected_nodes.selected_nodes().all(|&node_id| document_metadata.node_is_visible(node_id));
let visible = !visible;
for &node_id in selected_nodes.selected_nodes() {
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
}
}
NodeGraphMessage::ToggleHidden { node_id } => {
if let Some(network) = document_network.nested_network(&self.network) {
let new_hidden = !network.disabled.contains(&node_id);
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: new_hidden });
}
NodeGraphMessage::ToggleVisibility { node_id } => {
let visible = document_metadata.node_is_visible(node_id);
let visible = !visible;
responses.add(NodeGraphMessage::SetVisibility { node_id, visible });
}
NodeGraphMessage::SetHidden { node_id, hidden } => {
if let Some(network) = document_network.nested_network_mut(&self.network) {
if !hidden {
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) {
network.disabled.push(node_id);
}
NodeGraphMessage::SetVisibility { node_id, visible } => {
(|| {
let Some(network) = document_network.nested_network_mut(&self.network) else { return };
let input_or_output = network.imports.contains(&node_id) || network.original_outputs().iter().any(|output| output.node_id == 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
if network.connected_to_output(node_id) {
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 } => {
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);
}
@ -522,7 +532,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let node_types = document_node_types::collect_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 } => {
self.resolved_types = resolved_types;
@ -540,7 +550,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
impl NodeGraphMessageHandler {
pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList {
if self.has_selection && graph_open {
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleSelectedHidden)
actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleSelectedVisibility)
} else {
actions!(NodeGraphMessageDiscriminant;)
}
@ -554,8 +564,8 @@ impl NodeGraphMessageHandler {
});
}
/// Updates the buttons for disable and preview
fn update_selection_action_buttons(&mut self, document_network: &NodeNetwork, selected_nodes: &SelectedNodes, responses: &mut VecDeque<Message>) {
/// Updates the buttons for visibility and preview
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) {
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 selection.next().is_some() {
// 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
let multiple_nodes = selection.next().is_some();
// Generate the enable or disable button accordingly
let (hide_show_label, hide_show_icon) = if is_hidden { ("Make Visible", "EyeHidden") } else { ("Make Hidden", "EyeVisible") };
// Generate the visible/hidden button accordingly
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)
.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_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedHidden))
.on_update(move |_| NodeGraphMessage::ToggleSelectedHidden.into())
.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::ToggleSelectedVisibility))
.on_update(move |_| NodeGraphMessage::ToggleSelectedVisibility.into())
.widget_holder();
widgets.push(hide_button);
@ -745,7 +755,7 @@ impl NodeGraphMessageHandler {
exposed_outputs,
position: node.metadata.position.into(),
previewed: network.outputs_contain(node_id),
disabled: network.disabled.contains(&node_id),
visible: node.visible,
errors: errors.map(|e| format!("{e:?}")),
});
}
@ -774,7 +784,7 @@ impl NodeGraphMessageHandler {
parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
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() },
disabled: network.disabled.contains(&node_id),
visible: node.visible,
};
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
}
@ -795,8 +805,8 @@ impl NodeGraphMessageHandler {
}
/// 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>) {
self.update_selection_action_buttons(document_network, selected_nodes, responses);
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, document_metadata, selected_nodes, responses);
responses.add(FrontendMessage::UpdateNodeGraphSelection {
selected: selected_nodes.selected_nodes_ref().clone(),
});

View file

@ -84,7 +84,7 @@ pub struct FrontendNode {
#[serde(rename = "exposedOutputs")]
pub exposed_outputs: Vec<FrontendGraphOutput>,
pub position: (i32, i32),
pub disabled: bool,
pub visible: bool,
pub previewed: bool,
pub errors: Option<String>,
}

View file

@ -8,7 +8,7 @@ use std::collections::HashMap;
pub enum Clipboard {
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,
}
@ -19,7 +19,7 @@ pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u
pub struct CopyBufferEntry {
pub nodes: HashMap<NodeId, DocumentNode>,
pub selected: bool,
pub disabled: bool,
pub visible: bool,
pub collapsed: bool,
pub alias: String,
}

View file

@ -14,12 +14,15 @@ use std::num::NonZeroU64;
// 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)]
pub struct DocumentMetadata {
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
structure: HashMap<LayerNodeIdentifier, NodeRelations>,
artboards: HashSet<LayerNodeIdentifier>,
folders: HashSet<LayerNodeIdentifier>,
hidden: HashSet<NodeId>,
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
/// Transform from document space to viewport space.
pub document_to_viewport: DAffine2,
@ -29,10 +32,11 @@ impl Default for DocumentMetadata {
fn default() -> Self {
Self {
upstream_transforms: HashMap::new(),
click_targets: HashMap::new(),
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
artboards: HashSet::new(),
folders: HashSet::new(),
hidden: HashSet::new(),
click_targets: HashMap::new(),
document_to_viewport: DAffine2::IDENTITY,
}
}
@ -118,6 +122,10 @@ impl DocumentMetadata {
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
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();
@ -138,8 +146,9 @@ impl DocumentMetadata {
}
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]);
self.folders = HashSet::new();
self.artboards = HashSet::new();
self.folders = HashSet::new();
self.hidden = HashSet::new();
let id = graph.exports[0].node_id;
let Some(output_node) = graph.nodes.get(&id) else {
@ -150,22 +159,26 @@ impl DocumentMetadata {
};
let parent = LayerNodeIdentifier::ROOT;
let mut stack = vec![(layer_node, node_id, parent)];
while let Some((node, id, parent)) = stack.pop() {
let mut current = Some((node, id));
while let Some(&(current_node, current_id)) = current.as_ref() {
let current_identifier = LayerNodeIdentifier::new_unchecked(current_id);
if !self.structure.contains_key(&current_identifier) {
parent.push_child(self, current_identifier);
while let Some((node, node_id, parent)) = stack.pop() {
let mut current = Some((node, node_id));
while let Some(&(current_node, current_node_id)) = current.as_ref() {
let current_layer_id = LayerNodeIdentifier::new_unchecked(current_node_id);
if !self.structure.contains_key(&current_layer_id) {
parent.push_child(self, current_layer_id);
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) {
self.artboards.insert(current_identifier);
if is_artboard(current_layer_id, graph) {
self.artboards.insert(current_layer_id);
}
if is_folder(current_identifier, graph) {
self.folders.insert(current_identifier);
if is_folder(current_layer_id, graph) {
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
// ==============
// ===============
#[derive(Clone)]
pub struct DescendantsIter<'a> {

View file

@ -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 super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct RawBuffer(Vec<u8>);
@ -46,7 +46,7 @@ pub struct LayerPanelEntry {
#[serde(rename = "layerClassification")]
pub layer_classification: LayerClassification,
pub expanded: bool,
pub disabled: bool,
pub visible: bool,
#[serde(rename = "parentId")]
pub parent_id: Option<NodeId>,
pub depth: usize,
@ -56,12 +56,12 @@ pub struct LayerPanelEntry {
pub struct SelectedNodes(pub Vec<NodeId>);
impl SelectedNodes {
pub fn layer_visible(&self, layer: LayerNodeIdentifier, network: &NodeNetwork, metadata: &DocumentMetadata) -> bool {
!layer.ancestors(metadata).any(|layer| network.disabled.contains(&layer.to_node()))
pub fn layer_visible(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
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> + '_ {
self.selected_layers(metadata).filter(move |&layer| self.layer_visible(layer, network, metadata))
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, metadata))
}
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {

View file

@ -202,7 +202,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
)
.collect(),
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,
alias: previous_alias,
});
@ -388,8 +388,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
}
if entry.disabled {
responses.add(NodeGraphMessage::SetHidden { node_id: id, hidden: entry.disabled });
if !entry.visible {
responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false });
}
}
};
@ -419,8 +419,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
}
if entry.disabled {
responses.add(NodeGraphMessage::SetHidden { node_id: id, hidden: entry.disabled });
if !entry.visible {
responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false });
}
}

View file

@ -45,7 +45,7 @@ impl Pivot {
/// Recomputes the pivot position and transform.
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 {
// If no layers are selected then we revert things back to default
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
let xy_summation = document
.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))
.reduce(|a, b| a + b)
.unwrap_or_default();
@ -101,7 +101,7 @@ impl Pivot {
/// 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>) {
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 pivot = transform.inverse().transform_point2(position);
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.

View file

@ -255,7 +255,7 @@ impl SnapManager {
if candidates.len() > 10 {
return;
}
if !document.selected_nodes.layer_visible(layer, &document.network, &document.metadata) {
if !document.selected_nodes.layer_visible(layer, &document.metadata) {
return;
}
if snap_data.ignore.contains(&layer) {

View file

@ -247,7 +247,7 @@ impl Fsm for GradientToolFsmState {
(_, GradientToolMessage::Overlays(mut overlay_context)) => {
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 transform = gradient_space_transform(layer, document);
let dragging = selected.filter(|selected| selected.layer == layer).map(|selected| selected.dragging);
@ -318,7 +318,7 @@ impl Fsm for GradientToolFsmState {
self
}
(_, 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 transform = gradient_space_transform(layer, document);
@ -357,7 +357,7 @@ impl Fsm for GradientToolFsmState {
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
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 transform = gradient_space_transform(layer, document);

View file

@ -391,7 +391,7 @@ impl Fsm for SelectToolFsmState {
tool_data.selected_layers_count = selected_layers_count;
// 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));
}
@ -405,13 +405,13 @@ impl Fsm for SelectToolFsmState {
// Update bounds
let transform = document
.selected_nodes
.selected_visible_layers(document.network(), document.metadata())
.selected_visible_layers(document.metadata())
.next()
.map(|layer| document.metadata().transform_to_viewport(layer));
let transform = transform.unwrap_or(DAffine2::IDENTITY);
let bounds = document
.selected_nodes
.selected_visible_layers(document.network(), document.metadata())
.selected_visible_layers(document.metadata())
.filter_map(|layer| {
document
.metadata()
@ -472,7 +472,7 @@ impl Fsm for SelectToolFsmState {
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
.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);
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.

View file

@ -215,7 +215,7 @@ impl TextToolData {
/// Set the editing state of the currently modifying layer
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) {
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) {

View file

@ -190,7 +190,7 @@ impl NodeRuntime {
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();
editor_api.font_cache.hash(&mut graph_input_hash);
let font_hash_code = graph_input_hash.finish();
@ -218,7 +218,7 @@ impl NodeRuntime {
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 {
self.node_graph_errors = e;
} else {

View file

@ -50,7 +50,7 @@ module.exports = {
camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"],
"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",
"no-console": "warn",
"no-debugger": "warn",
@ -81,6 +81,8 @@ module.exports = {
// Import plugin config (for intelligently validating module import statements)
"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/no-relative-packages": "error",
"import/order": [

View file

@ -417,8 +417,8 @@
class={"visibility"}
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
size={24}
icon={listing.entry.disabled ? "EyeHidden" : "EyeVisible"}
tooltip={listing.entry.disabled ? "Disabled" : "Enabled"}
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
tooltip={listing.entry.visible ? "Visible" : "Hidden"}
/>
</LayoutRow>
{/each}

View file

@ -795,7 +795,7 @@
class="layer"
class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)}
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-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`}
@ -891,8 +891,8 @@
class={"visibility"}
action={(e) => (toggleLayerVisibility(node.id), e?.stopPropagation())}
size={24}
icon={node.disabled ? "EyeHidden" : "EyeVisible"}
tooltip={node.disabled ? "Disabled" : "Enabled"}
icon={node.visible ? "EyeVisible" : "EyeHidden"}
tooltip={node.visible ? "Visible" : "Hidden"}
/>
<svg class="border-mask" width="0" height="0">
@ -913,7 +913,7 @@
class="node"
class:selected={showSelected($nodeGraph.selected, boxSelection, node.id, nodeIndex)}
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-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`}

View file

@ -124,7 +124,7 @@ export class FrontendNode {
readonly previewed!: boolean;
readonly disabled!: boolean;
readonly visible!: boolean;
readonly errors!: string | undefined;
}
@ -613,7 +613,7 @@ export class LayerPanelEntry {
expanded!: boolean;
disabled!: boolean;
visible!: boolean;
parentId!: bigint | undefined;

View file

@ -757,8 +757,8 @@ impl JsEditorHandle {
/// Toggle visibility of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerVisibility)]
pub fn toggle_layer_visibility(&self, id: u64) {
let id = NodeId(id);
let message = NodeGraphMessage::ToggleHidden { node_id: id };
let node_id = NodeId(id);
let message = NodeGraphMessage::ToggleVisibility { node_id };
self.dispatch(message);
}

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.
@ -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.
## 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:

View file

@ -39,7 +39,6 @@ fn add_network() -> NodeNetwork {
NodeNetwork {
imports: vec![],
exports: vec![NodeOutput::new(NodeId(0), 0)],
disabled: vec![],
previous_outputs: None,
nodes: [DocumentNode {
name: "Blend Image".into(),

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)`
/// 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>,
// 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")]
pub has_primary_output: bool,
// A nested document network or a proto-node identifier.
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.
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.
/// 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)]
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)]
pub world_state_hash: u64,
/// 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 {
/// The original location to the document node - e.g. [grandparent_id, parent_id, node_id].
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>,
/// A list of document sources for the node's output
pub outputs_source: HashMap<Source, usize>,
@ -203,6 +209,7 @@ impl Default for DocumentNode {
manual_composition: Default::default(),
has_primary_output: true,
implementation: Default::default(),
visible: true,
metadata: Default::default(),
skip_deduplication: Default::default(),
world_state_hash: Default::default(),
@ -272,7 +279,7 @@ impl DocumentNode {
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
}
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) };
(node, ConstructionArgs::Nodes(vec![]))
}
@ -445,9 +452,9 @@ pub enum DocumentNodeImplementation {
///
/// A nested [`NodeNetwork`] that is flattened by the [`NodeNetwork::flatten`] function.
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),
/// 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
@ -523,9 +530,6 @@ pub struct NodeNetwork {
pub exports: Vec<NodeOutput>,
/// The list of all nodes in this network.
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.
pub previous_outputs: Option<Vec<NodeOutput>>,
}
@ -540,7 +544,6 @@ impl std::hash::Hash for NodeNetwork {
id.hash(state);
node.hash(state);
}
self.disabled.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(),
exports: vec![NodeOutput::new(NodeId(0), 0)],
nodes: [(NodeId(0), node)].into_iter().collect(),
disabled: vec![],
previous_outputs: None,
}
}
@ -815,7 +817,6 @@ impl NodeNetwork {
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
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.disabled.iter_mut().for_each(|id| *id = f(*id));
self.previous_outputs
.iter_mut()
.for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id)));
@ -843,7 +844,7 @@ impl NodeNetwork {
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]) {
for (node_id, node) in &mut self.nodes {
let mut new_path = prefix.to_vec();
@ -930,8 +931,11 @@ impl NodeNetwork {
return;
};
if node.implementation != DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()) && self.disabled.contains(&id) {
node.implementation = DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into());
// If the node is hidden, replace it with an identity node
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() {
// Connect layer node to the graphic group below
node.inputs.drain(..1);
@ -939,6 +943,7 @@ impl NodeNetwork {
node.inputs.drain(1..);
}
self.nodes.insert(id, node);
return;
}
@ -983,7 +988,6 @@ impl NodeNetwork {
let new_nodes = inner_network.nodes.keys().cloned().collect::<Vec<_>>();
// Copy nodes from the inner network into the parent network
self.nodes.extend(inner_network.nodes);
self.disabled.extend(inner_network.disabled);
let mut network_offsets = HashMap::new();
assert_eq!(

View file

@ -200,7 +200,7 @@ impl ConstructionArgs {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[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 construction_args: ConstructionArgs,
pub input: ProtoNodeInput,
@ -718,7 +718,7 @@ impl TypingContext {
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 {
ProtoNodeInput::None => concrete!(()),
ProtoNodeInput::ManualComposition(ref ty) => ty.clone(),

View file

@ -17,7 +17,7 @@ pub struct DynamicExecutor {
output: NodeId,
/// Stores all of the dynamic node structs.
tree: BorrowTree,
/// Stores the types of the protonodes.
/// Stores the types of the proto nodes.
typing_context: TypingContext,
// This allows us to keep the nodes around for one more frame which is used for introspection
orphaned_nodes: Vec<NodeId>,
@ -111,11 +111,11 @@ impl<'a, I: StaticType + 'a> Executor<I, TaggedValue> for &'a DynamicExecutor {
pub struct BorrowTree {
/// A hashmap of node IDs and dynamically typed nodes.
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>,
/// 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)>,
/// 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>,
}
@ -193,7 +193,7 @@ impl BorrowTree {
.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)));
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 {

View file

@ -38,7 +38,7 @@ module.exports = {
camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"],
"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",
"no-console": "warn",
"no-debugger": "warn",

View file

@ -35,7 +35,7 @@ module.exports = {
camelcase: ["error", { properties: "always" }],
"linebreak-style": ["error", "unix"],
"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",
"no-console": "warn",
"no-debugger": "warn",