diff --git a/editor/src/messages/input_mapper/default_mapping.rs b/editor/src/messages/input_mapper/default_mapping.rs index ffc787c07c..7df5e40a96 100644 --- a/editor/src/messages/input_mapper/default_mapping.rs +++ b/editor/src/messages/input_mapper/default_mapping.rs @@ -33,6 +33,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::ToggleHidden), // // TransformLayerMessage entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation), 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 4d062bb514..53d8116206 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,13 +67,11 @@ pub enum NodeGraphMessage { input_index: usize, value: TaggedValue, }, - SetSelectedEnabled { - enabled: bool, - }, - SetSelectedOutput { - output: bool, - }, ShiftNode { node_id: NodeId, }, + ToggleHidden, + TogglePreview { + 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 83a72143a4..1d259fefd2 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,3 +1,4 @@ +use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; use crate::messages::layout::utility_types::widgets::button_widgets::{BreadcrumbTrailButtons, TextButton}; use crate::messages::prelude::*; @@ -175,25 +176,40 @@ impl NodeGraphMessageHandler { 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()), + // Don't allow disabling input or output nodes + let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && network.original_output() != id); + + // If there is at least one other selected node then show the hide or show button + if selected_nodes.next().is_some() { + // Check if any of the selected nodes are disabled + let is_hidden = self.selected_nodes.iter().any(|id| network.disabled.contains(id)); + + // Check if multiple nodes are selected + let mutliple_nodes = selected_nodes.next().is_some(); + + // Generate the enable or disable button accordingly + let hide_button = WidgetHolder::new(Widget::TextButton(TextButton { + label: if is_hidden { "Show" } else { "Hide" }.to_string(), + tooltip: if is_hidden { "Show node" } else { "Hide node" }.to_string() + if mutliple_nodes { "s" } else { "" }, + tooltip_shortcut: action_keys!(NodeGraphMessageDiscriminant::ToggleHidden), + on_update: WidgetCallback::new(move |_| NodeGraphMessage::ToggleHidden.into()), ..Default::default() })); - widgets.push(enable_button); + widgets.push(hide_button); } // 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 + let node_id = self.selected_nodes[0]; + // Is this node the current output + let is_output = network.output == node_id; + + // Don't show stop previewing button on the origional 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()), + label: if is_output { "End Preview" } else { "Preview" }.to_string(), + tooltip: if is_output { "Restore preview to Output node" } else { "Preview node" }.to_string() + " (shortcut: Alt+click node)", + on_update: WidgetCallback::new(move |_| NodeGraphMessage::TogglePreview { node_id }.into()), ..Default::default() })); widgets.push(output_button); @@ -675,31 +691,6 @@ 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"); @@ -747,12 +738,42 @@ impl MessageHandler { + if let Some(network) = self.get_active_network_mut(document) { + // Check if any of the selected nodes are hidden + if self.selected_nodes.iter().any(|id| network.disabled.contains(id)) { + // Remove all selected nodes from the disabled list + network.disabled.retain(|id| !self.selected_nodes.contains(id)); + } else { + let original_output = network.original_output(); + // Add all selected nodes to the disabled list (excluding input or output nodes) + network.disabled.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && original_output != *id)); + } + Self::send_graph(network, responses); + } + self.update_selection_action_buttons(document, responses); + responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into()); + } + NodeGraphMessage::TogglePreview { node_id } => { + if let Some(network) = self.get_active_network_mut(document) { + // Check if the node is not already + if network.output != node_id { + network.previous_output = Some(network.previous_output.unwrap_or(network.output)); + network.output = node_id; + } 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()); + } } } fn actions(&self) -> ActionList { if self.layer_path.is_some() && !self.selected_nodes.is_empty() { - actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes) + actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleHidden) } else { actions!(NodeGraphMessageDiscriminant;) } diff --git a/frontend/src/components/panels/NodeGraph.vue b/frontend/src/components/panels/NodeGraph.vue index 4620b2aa94..d755c82728 100644 --- a/frontend/src/components/panels/NodeGraph.vue +++ b/frontend/src/components/panels/NodeGraph.vue @@ -534,8 +534,10 @@ export default defineComponent({ }, // TODO: Move the event listener from the graph to the window so dragging outside the graph area (or even the browser window) works pointerDown(e: PointerEvent) { + // Exit the add node popup by clicking elsewhere in the graph if (this.nodeListLocation && !(e.target as HTMLElement).closest("[data-node-list]")) this.nodeListLocation = undefined; + // Handle the add node popup on right click if (e.button === 2) { const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el; const graph = graphDiv?.getBoundingClientRect() || new DOMRect(); @@ -558,6 +560,10 @@ export default defineComponent({ // If the user is clicking on the add nodes list, exit here if (nodeList) return; + if (e.altKey && nodeId) { + this.editor.instance.togglePreview(BigInt(nodeId)); + } + // Clicked on a port dot if (port && node) { const isOutput = Boolean(port.getAttribute("data-port") === "output"); diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index b35d563079..71321e1460 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -648,6 +648,13 @@ impl JsEditorHandle { self.dispatch(message); } + /// Toggle preview on node + #[wasm_bindgen(js_name = togglePreview)] + pub fn toggle_preview(&self, node_id: NodeId) { + let message = NodeGraphMessage::TogglePreview { node_id }; + self.dispatch(message); + } + /// Pastes an image #[wasm_bindgen(js_name = pasteImage)] pub fn paste_image(&self, mime: String, image_data: Vec, mouse_x: Option, mouse_y: Option) { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 5dbc3b0a9c..3b857f5d1c 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -277,6 +277,11 @@ impl NodeNetwork { nodes, } } + + /// Get the original output node of this network, ignoring any preview node + pub fn original_output(&self) -> NodeId { + self.previous_output.unwrap_or(self.output) + } } #[cfg(test)]