Node 'Disable' and 'Preview' UX improvements (#918)
* Don't allow disabling input or output nodes * Shortcuts to show and preview node * Update preview tooltip and label Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
509aab72c1
commit
c552edd525
|
|
@ -33,6 +33,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::ToggleHidden),
|
||||||
//
|
//
|
||||||
// TransformLayerMessage
|
// TransformLayerMessage
|
||||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||||
|
|
|
||||||
|
|
@ -67,13 +67,11 @@ pub enum NodeGraphMessage {
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
value: TaggedValue,
|
value: TaggedValue,
|
||||||
},
|
},
|
||||||
SetSelectedEnabled {
|
|
||||||
enabled: bool,
|
|
||||||
},
|
|
||||||
SetSelectedOutput {
|
|
||||||
output: bool,
|
|
||||||
},
|
|
||||||
ShiftNode {
|
ShiftNode {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
},
|
},
|
||||||
|
ToggleHidden,
|
||||||
|
TogglePreview {
|
||||||
|
node_id: NodeId,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
use crate::messages::layout::utility_types::widgets::button_widgets::{BreadcrumbTrailButtons, TextButton};
|
use crate::messages::layout::utility_types::widgets::button_widgets::{BreadcrumbTrailButtons, TextButton};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
@ -175,25 +176,40 @@ impl NodeGraphMessageHandler {
|
||||||
if let Some(network) = self.get_active_network_mut(document) {
|
if let Some(network) = self.get_active_network_mut(document) {
|
||||||
let mut widgets = Vec::new();
|
let mut widgets = Vec::new();
|
||||||
|
|
||||||
// Show an enable or disable nodes button if there is a selection
|
// Don't allow disabling input or output nodes
|
||||||
if !self.selected_nodes.is_empty() {
|
let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && network.original_output() != id);
|
||||||
let is_enabled = self.selected_nodes.iter().any(|id| !network.disabled.contains(id));
|
|
||||||
let enable_button = WidgetHolder::new(Widget::TextButton(TextButton {
|
// If there is at least one other selected node then show the hide or show button
|
||||||
label: if is_enabled { "Disable" } else { "Enable" }.to_string(),
|
if selected_nodes.next().is_some() {
|
||||||
on_update: WidgetCallback::new(move |_| NodeGraphMessage::SetSelectedEnabled { enabled: !is_enabled }.into()),
|
// 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()
|
..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 only one node is selected then show the preview or stop previewing button
|
||||||
if self.selected_nodes.len() == 1 {
|
if self.selected_nodes.len() == 1 {
|
||||||
let is_output = network.output == self.selected_nodes[0];
|
let node_id = self.selected_nodes[0];
|
||||||
// Don't show stop previewing on the output node
|
// 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()) {
|
if !(is_output && network.previous_output.filter(|&id| id != self.selected_nodes[0]).is_none()) {
|
||||||
let output_button = WidgetHolder::new(Widget::TextButton(TextButton {
|
let output_button = WidgetHolder::new(Widget::TextButton(TextButton {
|
||||||
label: if is_output { "Stop Previewing" } else { "Preview" }.to_string(),
|
label: if is_output { "End Preview" } else { "Preview" }.to_string(),
|
||||||
on_update: WidgetCallback::new(move |_| NodeGraphMessage::SetSelectedOutput { output: !is_output }.into()),
|
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()
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
widgets.push(output_button);
|
widgets.push(output_button);
|
||||||
|
|
@ -675,31 +691,6 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetSelectedEnabled { enabled } => {
|
|
||||||
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 } => {
|
NodeGraphMessage::ShiftNode { node_id } => {
|
||||||
let Some(network) = self.get_active_network_mut(document) else{
|
let Some(network) = self.get_active_network_mut(document) else{
|
||||||
warn!("No network");
|
warn!("No network");
|
||||||
|
|
@ -747,12 +738,42 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||||
}
|
}
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, responses);
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::ToggleHidden => {
|
||||||
|
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 {
|
fn actions(&self) -> ActionList {
|
||||||
if self.layer_path.is_some() && !self.selected_nodes.is_empty() {
|
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 {
|
} else {
|
||||||
actions!(NodeGraphMessageDiscriminant;)
|
actions!(NodeGraphMessageDiscriminant;)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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) {
|
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;
|
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) {
|
if (e.button === 2) {
|
||||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||||
const graph = graphDiv?.getBoundingClientRect() || new DOMRect();
|
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 the user is clicking on the add nodes list, exit here
|
||||||
if (nodeList) return;
|
if (nodeList) return;
|
||||||
|
|
||||||
|
if (e.altKey && nodeId) {
|
||||||
|
this.editor.instance.togglePreview(BigInt(nodeId));
|
||||||
|
}
|
||||||
|
|
||||||
// Clicked on a port dot
|
// Clicked on a port dot
|
||||||
if (port && node) {
|
if (port && node) {
|
||||||
const isOutput = Boolean(port.getAttribute("data-port") === "output");
|
const isOutput = Boolean(port.getAttribute("data-port") === "output");
|
||||||
|
|
|
||||||
|
|
@ -648,6 +648,13 @@ impl JsEditorHandle {
|
||||||
self.dispatch(message);
|
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
|
/// Pastes an image
|
||||||
#[wasm_bindgen(js_name = pasteImage)]
|
#[wasm_bindgen(js_name = pasteImage)]
|
||||||
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,11 @@ impl NodeNetwork {
|
||||||
nodes,
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue