From 8d7e6c530edd47e64f7fa0953681d30d5ad52849 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Sun, 25 Dec 2022 10:28:18 +0000 Subject: [PATCH] Add preview and disable buttons for nodes (#905) * Add preview and disable button * Fix tests Co-authored-by: Keavon Chambers --- .../layout/utility_types/layout_widget.rs | 7 + .../document/node_graph/node_graph_message.rs | 6 + .../node_graph/node_graph_message_handler.rs | 136 ++++++++++++++---- .../portfolio/portfolio_message_handler.rs | 47 +++--- .../tool/tool_messages/imaginate_tool.rs | 2 + .../tool_messages/node_graph_frame_tool.rs | 1 + frontend/src/components/panels/NodeGraph.vue | 22 ++- frontend/src/wasm-communication/messages.ts | 4 + node-graph/graph-craft/src/document.rs | 18 +++ node-graph/interpreted-executor/src/lib.rs | 2 + 10 files changed, 196 insertions(+), 49 deletions(-) diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index 8b9c12e5..9ad8653a 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -250,6 +250,13 @@ pub enum LayoutGroup { #[serde(rename = "section")] Section { name: String, layout: SubLayout }, } + +impl Default for LayoutGroup { + fn default() -> Self { + Self::Row { widgets: Vec::new() } + } +} + impl LayoutGroup { /// Applies a tooltip to all widgets in this row or column without a tooltip. pub fn with_tooltip(self, tooltip: impl Into) -> Self { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index ef7d3cee..4d062bb5 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -67,6 +67,12 @@ pub enum NodeGraphMessage { input_index: usize, value: TaggedValue, }, + SetSelectedEnabled { + enabled: bool, + }, + SetSelectedOutput { + output: bool, + }, ShiftNode { node_id: NodeId, }, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 0fe33c6d..324c3d0d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,5 +1,5 @@ use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::messages::layout::utility_types::widgets::button_widgets::BreadcrumbTrailButtons; +use crate::messages::layout::utility_types::widgets::button_widgets::{BreadcrumbTrailButtons, TextButton}; use crate::messages::prelude::*; use document_legacy::document::Document; @@ -65,6 +65,8 @@ pub struct FrontendNode { pub exposed_inputs: Vec, pub outputs: Vec, pub position: (i32, i32), + pub disabled: bool, + pub output: bool, } // (link_start, link_end, link_end_input_index) @@ -92,11 +94,13 @@ impl FrontendNodeType { } } -#[derive(Debug, Clone, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] pub struct NodeGraphMessageHandler { pub layer_path: Option>, pub nested_path: Vec, pub selected_nodes: Vec, + #[serde(skip)] + pub widgets: [LayoutGroup; 2], } impl NodeGraphMessageHandler { @@ -124,8 +128,20 @@ impl NodeGraphMessageHandler { network } + /// Send the cached layout for the bar at the top of the node panel to the frontend + fn send_node_bar_layout(&self, responses: &mut VecDeque) { + responses.push_back( + LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(self.widgets.to_vec())), + layout_target: crate::messages::layout::utility_types::misc::LayoutTarget::NodeGraphBar, + } + .into(), + ); + } + /// Collect the addresses of the currently viewed nested node e.g. Root -> MyFunFilter -> Exposure - fn collect_nested_addresses(&self, document: &Document, responses: &mut VecDeque) { + fn collect_nested_addresses(&mut self, document: &Document, responses: &mut VecDeque) { + // Build path list let mut path = vec!["Root".to_string()]; let mut network = self.get_root_network(document); for node_id in &self.nested_path { @@ -137,24 +153,56 @@ impl NodeGraphMessageHandler { } let nesting = path.len(); - responses.push_back( - LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::BreadcrumbTrailButtons(BreadcrumbTrailButtons { - labels: path, - on_update: WidgetCallback::new(move |input: &u64| { - NodeGraphMessage::ExitNestedNetwork { - depth_of_nesting: nesting - (*input as usize) - 1, - } - .into() - }), - ..Default::default() - }))], - }])), - layout_target: crate::messages::layout::utility_types::misc::LayoutTarget::NodeGraphBar, + // Update UI + self.widgets[0] = LayoutGroup::Row { + widgets: vec![WidgetHolder::new(Widget::BreadcrumbTrailButtons(BreadcrumbTrailButtons { + labels: path.clone(), + on_update: WidgetCallback::new(move |input: &u64| { + NodeGraphMessage::ExitNestedNetwork { + depth_of_nesting: nesting - (*input as usize) - 1, + } + .into() + }), + ..Default::default() + }))], + }; + + self.send_node_bar_layout(responses); + } + + /// Updates the buttons for disable and preview + fn update_selection_action_buttons(&mut self, document: &mut Document, responses: &mut VecDeque) { + if let Some(network) = self.get_active_network_mut(document) { + let mut widgets = Vec::new(); + + // Show an enable or disable nodes button if there is a selection + if !self.selected_nodes.is_empty() { + let is_enabled = self.selected_nodes.iter().any(|id| !network.disabled.contains(id)); + let enable_button = WidgetHolder::new(Widget::TextButton(TextButton { + label: if is_enabled { "Disable" } else { "Enable" }.to_string(), + on_update: WidgetCallback::new(move |_| NodeGraphMessage::SetSelectedEnabled { enabled: !is_enabled }.into()), + ..Default::default() + })); + widgets.push(enable_button); } - .into(), - ); + + // If only one node is selected then show the preview or stop previewing button + if self.selected_nodes.len() == 1 { + let is_output = network.output == self.selected_nodes[0]; + // Don't show stop previewing on the output node + if !(is_output && network.previous_output.filter(|&id| id != self.selected_nodes[0]).is_none()) { + let output_button = WidgetHolder::new(Widget::TextButton(TextButton { + label: if is_output { "Stop Previewing" } else { "Preview" }.to_string(), + on_update: WidgetCallback::new(move |_| NodeGraphMessage::SetSelectedOutput { output: !is_output }.into()), + ..Default::default() + })); + widgets.push(output_button); + } + } + + self.widgets[1] = LayoutGroup::Row { widgets }; + } + self.send_node_bar_layout(responses); } pub fn collate_properties(&self, node_graph_frame: &NodeGraphFrameLayer, context: &mut NodePropertiesContext, sections: &mut Vec) { @@ -237,14 +285,17 @@ impl NodeGraphMessageHandler { .collect(), outputs: node_type.outputs.to_vec(), position: node.metadata.position, + output: network.output == *id, + disabled: network.disabled.contains(id), }) } log::debug!("Frontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", nodes, links); responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into()); } - /// Updates the frontend's selection state inline with the backend - fn update_selected(&self, responses: &mut VecDeque) { + /// Updates the frontend's selection state in line with the backend + fn update_selected(&mut self, document: &mut Document, responses: &mut VecDeque) { + self.update_selection_action_buttons(document, responses); responses.push_back( FrontendMessage::UpdateNodeGraphSelection { selected: self.selected_nodes.clone(), @@ -388,6 +439,7 @@ impl MessageHandler { if let Some(network) = self.get_active_network_mut(document) { @@ -425,6 +478,7 @@ impl MessageHandler { let Some(network) = self.get_active_network_mut(document) else { @@ -443,7 +497,7 @@ impl MessageHandler { - self.selected_nodes = Vec::new(); + self.selected_nodes.clear(); if let Some(network) = self.get_active_network_mut(document) { if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() { self.nested_path.push(node); @@ -453,7 +507,7 @@ impl MessageHandler { if let Some(network) = self.get_active_network_mut(document) { @@ -476,11 +530,11 @@ impl MessageHandler { - self.selected_nodes = Vec::new(); + self.selected_nodes.clear(); for _ in 0..depth_of_nesting { self.nested_path.pop(); } @@ -488,6 +542,7 @@ impl MessageHandler { let Some(network) = self.get_active_network_mut(document) else{ @@ -542,6 +597,7 @@ impl MessageHandler { let Some(network) = self.get_active_network_mut(document) else{ @@ -567,10 +623,11 @@ impl MessageHandler { self.selected_nodes = nodes; + self.update_selection_action_buttons(document, responses); responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into()); } NodeGraphMessage::SetInputValue { node, input_index, value } => { @@ -615,6 +672,31 @@ impl MessageHandler { + if let Some(network) = self.get_active_network_mut(document) { + if enabled { + network.disabled.retain(|id| !self.selected_nodes.contains(id)); + } else { + network.disabled.extend(&self.selected_nodes); + } + Self::send_graph(network, responses); + } + self.update_selection_action_buttons(document, responses); + responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); + } + NodeGraphMessage::SetSelectedOutput { output } => { + if let Some(network) = self.get_active_network_mut(document) { + if output { + network.previous_output = Some(network.previous_output.unwrap_or(network.output)); + network.output = self.selected_nodes[0]; + } else if let Some(output) = network.previous_output.take() { + network.output = output + } + Self::send_graph(network, responses); + } + self.update_selection_action_buttons(document, responses); + responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); + } NodeGraphMessage::ShiftNode { node_id } => { let Some(network) = self.get_active_network_mut(document) else{ warn!("No network"); diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index aaa5cd81..a4a3c525 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -657,6 +657,30 @@ impl PortfolioMessageHandler { self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids") } + /// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`. + fn execute_network(mut network: NodeNetwork, image: Image) -> Result { + for node_id in network.nodes.keys().copied().collect::>() { + network.flatten(node_id); + } + + let mut proto_network = network.into_proto_network(); + proto_network.reorder_ids(); + + assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); + let stack = borrow_stack::FixedSizeStack::new(proto_network.nodes.len()); + for (_id, node) in proto_network.nodes { + interpreted_executor::node_registry::push_node(node, &stack); + } + + use borrow_stack::BorrowStack; + use dyn_any::IntoDynAny; + use graphene_core::Node; + + let boxed = unsafe { stack.get().last().unwrap().eval(image.into_dyn()) }; + + dyn_any::downcast::(boxed).map(|v| *v) + } + /// Computes an input for a node in the graph fn compute_input(old_network: &NodeNetwork, node_path: &[NodeId], mut input_index: usize, image: Cow) -> Result { let mut network = old_network.clone(); @@ -694,26 +718,7 @@ impl PortfolioMessageHandler { } } - let stack = borrow_stack::FixedSizeStack::new(256); - for node_id in old_network.nodes.keys() { - network.flatten(*node_id); - } - - let mut proto_network = network.into_proto_network(); - proto_network.reorder_ids(); - - assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?"); - for (_id, node) in proto_network.nodes { - interpreted_executor::node_registry::push_node(node, &stack); - } - - use borrow_stack::BorrowStack; - use dyn_any::IntoDynAny; - use graphene_core::Node; - - let boxed = unsafe { stack.get().last().unwrap().eval(image.into_owned().into_dyn()) }; - - dyn_any::downcast::(boxed).map(|v| *v) + Self::execute_network(network, image.into_owned()) } /// Encodes an image into a format using the image crate @@ -856,7 +861,7 @@ impl PortfolioMessageHandler { .into(), ); } else { - let mut image: Image = Self::compute_input(&network, &[1], 0, Cow::Owned(image))?; + let mut image: Image = Self::execute_network(network, image)?; // If no image was generated, use the input image if image.width == 0 || image.height == 0 { diff --git a/editor/src/messages/tool/tool_messages/imaginate_tool.rs b/editor/src/messages/tool/tool_messages/imaginate_tool.rs index da0494ec..d4e5a29e 100644 --- a/editor/src/messages/tool/tool_messages/imaginate_tool.rs +++ b/editor/src/messages/tool/tool_messages/imaginate_tool.rs @@ -158,6 +158,7 @@ impl Fsm for ImaginateToolFsmState { )] .into_iter() .collect(), + ..Default::default() }; let mut imaginate_inputs: Vec = imaginate_node_type.inputs.iter().map(|input| input.default.clone()).collect(); imaginate_inputs[0] = NodeInput::Node(0); @@ -197,6 +198,7 @@ impl Fsm for ImaginateToolFsmState { ] .into_iter() .collect(), + ..Default::default() }; responses.push_back( diff --git a/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs b/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs index 3ae782b9..6caea50d 100644 --- a/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs +++ b/editor/src/messages/tool/tool_messages/node_graph_frame_tool.rs @@ -163,6 +163,7 @@ impl Fsm for NodeGraphToolFsmState { ] .into_iter() .collect(), + ..Default::default() }; responses.push_back( diff --git a/frontend/src/components/panels/NodeGraph.vue b/frontend/src/components/panels/NodeGraph.vue index 1b806b02..4620b2aa 100644 --- a/frontend/src/components/panels/NodeGraph.vue +++ b/frontend/src/components/panels/NodeGraph.vue @@ -36,7 +36,7 @@ v-for="node in nodes" :key="String(node.id)" class="node" - :class="{ selected: selected.includes(node.id) }" + :class="{ selected: selected.includes(node.id), output: node.output, disabled: node.disabled }" :style="{ '--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0), '--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0), @@ -127,6 +127,12 @@ margin: 0 4px; flex: 0 0 auto; align-items: center; + + .widget-layout { + flex-direction: row; + flex-grow: 1; + justify-content: space-between; + } } .graph { @@ -193,6 +199,19 @@ margin: -1px; } + &.disabled { + background: var(--color-3-darkgray); + color: var(--color-a-softgray); + + .icon-label { + fill: var(--color-a-softgray); + } + } + + &.output { + outline: 3px solid var(--color-data-vector); + } + .primary { display: flex; align-items: center; @@ -385,6 +404,7 @@ export default defineComponent({ nodes: { immediate: true, async handler() { + this.selected = this.selected.filter((id) => this.nodeGraph.state.nodes.find((node) => node.id === id)); await this.refreshLinks(); }, }, diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index f78b5249..783f4ce2 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -96,6 +96,10 @@ export class FrontendNode { @TupleToVec2 readonly position!: XY | undefined; + + readonly output!: boolean; + + readonly disabled!: boolean; } export class FrontendNodeLink { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 91488a2b..5dbc3b0a 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -163,12 +163,18 @@ pub struct NodeNetwork { pub inputs: Vec, pub output: NodeId, pub nodes: HashMap, + /// These nodes are replaced with identity nodes when flattening + pub disabled: Vec, + /// In the case where a new node is chosen as output - what was the origional + pub previous_output: Option, } impl NodeNetwork { pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) { self.inputs.iter_mut().for_each(|id| *id = f(*id)); self.output = f(self.output); + self.disabled.iter_mut().for_each(|id| *id = f(*id)); + self.previous_output = self.previous_output.map(f); let mut empty = HashMap::new(); std::mem::swap(&mut self.nodes, &mut empty); self.nodes = empty @@ -204,6 +210,13 @@ impl NodeNetwork { .remove_entry(&node) .unwrap_or_else(|| panic!("The node which was supposed to be flattened does not exist in the network, id {} network {:#?}", node, self)); + if self.disabled.contains(&id) { + node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])); + node.inputs.drain(1..); + self.nodes.insert(id, node); + return; + } + match node.implementation { DocumentNodeImplementation::Network(mut inner_network) => { // Connect all network inputs to either the parent network nodes, or newly created value nodes. @@ -211,6 +224,7 @@ impl NodeNetwork { let new_nodes = inner_network.nodes.keys().cloned().collect::>(); // 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(); for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) { @@ -305,6 +319,7 @@ mod test { ] .into_iter() .collect(), + ..Default::default() } } @@ -337,6 +352,7 @@ mod test { ] .into_iter() .collect(), + ..Default::default() }; assert_eq!(network, maped_add); } @@ -363,6 +379,7 @@ mod test { )] .into_iter() .collect(), + ..Default::default() }; network.flatten_with_fns(1, |self_id, inner_id| self_id * 10 + inner_id, gen_node_id); let flat_network = flat_network(); @@ -480,6 +497,7 @@ mod test { ] .into_iter() .collect(), + ..Default::default() } } } diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 7a3af043..8b310531 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -87,6 +87,7 @@ mod tests { ] .into_iter() .collect(), + ..Default::default() } } @@ -110,6 +111,7 @@ mod tests { )] .into_iter() .collect(), + ..Default::default() }; use crate::executor::DynamicExecutor;