Skip to content

Commit b38d725

Browse files
Add input hints to interactions in the node graph (#2415)
* node_graph: add hints in status bar * allow in-progress interactions * Fix node graph hints not restoring after panning operation * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 3c1ec45 commit b38d725

File tree

6 files changed

+106
-34
lines changed

6 files changed

+106
-34
lines changed

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
508508
responses.add(NodeGraphMessage::SetGridAlignedEdges);
509509
responses.add(NodeGraphMessage::UpdateGraphBarRight);
510510
responses.add(NodeGraphMessage::SendGraph);
511+
responses.add(NodeGraphMessage::UpdateHints);
511512
} else {
512513
responses.add(ToolMessage::ActivateTool { tool_type: *current_tool });
513514
}
@@ -2479,6 +2480,10 @@ impl DocumentMessageHandler {
24792480
let insert_index = if relative_index_offset < 0 { neighbor_index } else { neighbor_index + 1 };
24802481
responses.add(DocumentMessage::MoveSelectedLayersTo { parent, insert_index });
24812482
}
2483+
2484+
pub fn graph_view_overlay_open(&self) -> bool {
2485+
self.graph_view_overlay_open
2486+
}
24822487
}
24832488

24842489
/// Create a network interface with a single export

editor/src/messages/portfolio/document/node_graph/node_graph_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,5 +212,6 @@ pub enum NodeGraphMessage {
212212
UpdateActionButtons,
213213
UpdateGraphBarRight,
214214
UpdateInSelectedNetwork,
215+
UpdateHints,
215216
SendSelectedNodes,
216217
}

editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs

Lines changed: 88 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
1515
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
1616
use crate::messages::prelude::*;
1717
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
18+
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
19+
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
1820
use glam::{DAffine2, DVec2, IVec2};
1921
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
2022
use graph_craft::proto::GraphErrors;
@@ -43,14 +45,16 @@ pub struct NodeGraphMessageHandler {
4345
pub node_graph_errors: GraphErrors,
4446
has_selection: bool,
4547
widgets: [LayoutGroup; 2],
46-
/// The start position when dragging nodes
47-
pub drag_start: Option<DragStart>,
4848
/// Used to add a transaction for the first node move when dragging.
4949
begin_dragging: bool,
5050
/// Used to prevent entering a nested network if the node is dragged after double clicking
51-
drag_occurred: bool,
52-
/// Stored in node graph coordinates
53-
box_selection_start: Option<DVec2>,
51+
node_has_moved_in_drag: bool,
52+
/// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates,
53+
/// plus a flag indicating if it has been dragged since the mousedown began.
54+
pub drag_start: Option<(DragStart, bool)>,
55+
/// If dragging the background to create a box selection, this stores its starting point in node graph coordinates,
56+
/// plus a flag indicating if it has been dragged since the mousedown began.
57+
box_selection_start: Option<(DVec2, bool)>,
5458
/// Restore the selection before box selection if it is aborted
5559
selection_before_pointer_down: Vec<NodeId>,
5660
/// If the grip icon is held during a drag, then shift without pushing other nodes
@@ -294,7 +298,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
294298
}
295299
NodeGraphMessage::EnterNestedNetwork => {
296300
// Do not enter the nested network if the node was dragged
297-
if self.drag_occurred {
301+
if self.node_has_moved_in_drag {
298302
return;
299303
}
300304

@@ -377,7 +381,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
377381
) else {
378382
return;
379383
};
380-
//Ensure that nodes can be grouped by checking if there is an unselected node between selected nodes
384+
// Ensure that nodes can be grouped by checking if there is an unselected node between selected nodes
381385
for selected_node_id in &selected_node_ids {
382386
for input_index in 0..network_interface.number_of_inputs(selected_node_id, breadcrumb_network_path) {
383387
let input_connector = InputConnector::node(*selected_node_id, input_index);
@@ -580,8 +584,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
580584
if right_click {
581585
// Abort dragging a node
582586
if self.drag_start.is_some() {
583-
responses.add(DocumentMessage::AbortTransaction);
584587
self.drag_start = None;
588+
responses.add(DocumentMessage::AbortTransaction);
585589
responses.add(NodeGraphMessage::SelectedNodesSet {
586590
nodes: self.selection_before_pointer_down.clone(),
587591
});
@@ -598,18 +602,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
598602
}
599603
// Abort dragging a wire
600604
if self.wire_in_progress_from_connector.is_some() {
601-
responses.add(DocumentMessage::AbortTransaction);
602605
self.wire_in_progress_from_connector = None;
603606
self.wire_in_progress_to_connector = None;
607+
responses.add(DocumentMessage::AbortTransaction);
604608
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
605609
return;
606610
}
607611

608612
let context_menu_data = if let Some(node_id) = clicked_id {
609-
ContextMenuData::ToggleLayer {
610-
node_id,
611-
currently_is_node: !network_interface.is_layer(&node_id, selection_network_path),
612-
}
613+
let currently_is_node = !network_interface.is_layer(&node_id, selection_network_path);
614+
ContextMenuData::ToggleLayer { node_id, currently_is_node }
613615
} else {
614616
ContextMenuData::CreateNode
615617
};
@@ -721,6 +723,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
721723
self.initial_disconnecting = false;
722724

723725
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
726+
self.update_node_graph_hints(responses);
724727
return;
725728
}
726729

@@ -764,9 +767,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
764767
round_y: 0,
765768
};
766769

767-
self.drag_start = Some(drag_start);
770+
self.drag_start = Some((drag_start, false));
768771
self.begin_dragging = true;
769-
self.drag_occurred = false;
772+
self.node_has_moved_in_drag = false;
773+
self.update_node_graph_hints(responses);
770774
}
771775

772776
// Update the selection if it was modified
@@ -783,7 +787,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
783787
if !shift_click {
784788
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() })
785789
}
786-
self.box_selection_start = Some(node_graph_point);
790+
self.box_selection_start = Some((node_graph_point, false));
791+
self.update_node_graph_hints(responses);
787792
}
788793
NodeGraphMessage::PointerMove { shift } => {
789794
if selection_network_path != breadcrumb_network_path {
@@ -872,11 +877,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
872877
};
873878
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) });
874879
}
875-
} else if let Some(drag_start) = &mut self.drag_start {
876-
self.drag_occurred = true;
880+
} else if let Some((drag_start, dragged)) = &mut self.drag_start {
881+
if drag_start.start_x != point.x || drag_start.start_y != point.y {
882+
*dragged = true;
883+
}
884+
885+
self.node_has_moved_in_drag = true;
877886
if self.begin_dragging {
878887
self.begin_dragging = false;
879-
if ipp.keyboard.get(crate::messages::tool::tool_messages::tool_prelude::Key::Alt as usize) {
888+
if ipp.keyboard.get(Key::Alt as usize) {
880889
responses.add(NodeGraphMessage::DuplicateSelectedNodes);
881890
// Duplicating sets a 2x2 offset, so shift the nodes back to the original position
882891
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount {
@@ -898,8 +907,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
898907
graph_delta.y -= previous_round_y;
899908

900909
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: true });
901-
} else if self.box_selection_start.is_some() {
910+
911+
self.update_node_graph_hints(responses);
912+
} else if let Some((_, box_selection_dragged)) = &mut self.box_selection_start {
913+
*box_selection_dragged = true;
902914
responses.add(NodeGraphMessage::UpdateBoxSelection);
915+
self.update_node_graph_hints(responses);
903916
} else if self.reordering_import.is_some() {
904917
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
905918
log::error!("Could not get modify import export in PointerUp");
@@ -1016,18 +1029,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
10161029
}
10171030
}
10181031
// End of dragging a node
1019-
else if let Some(drag_start) = &self.drag_start {
1032+
else if let Some((drag_start, _)) = &self.drag_start {
10201033
self.shift_without_push = false;
1034+
10211035
// Reset all offsets to end the rubber banding while dragging
10221036
network_interface.unload_stack_dependents_y_offset(selection_network_path);
10231037
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
10241038
log::error!("Could not get selected nodes in PointerUp");
10251039
return;
10261040
};
1041+
10271042
// Only select clicked node if multiple are selected and they were not dragged
10281043
if let Some(select_if_not_dragged) = self.select_if_not_dragged {
1029-
if drag_start.start_x == point.x
1030-
&& drag_start.start_y == point.y
1044+
let not_dragged = drag_start.start_x == point.x && drag_start.start_y == point.y;
1045+
if not_dragged
10311046
&& (selected_nodes.selected_nodes_ref().len() != 1
10321047
|| selected_nodes
10331048
.selected_nodes_ref()
@@ -1218,6 +1233,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
12181233
responses.add(FrontendMessage::UpdateBox { box_selection: None });
12191234
responses.add(FrontendMessage::UpdateImportReorderIndex { index: None });
12201235
responses.add(FrontendMessage::UpdateExportReorderIndex { index: None });
1236+
self.update_node_graph_hints(responses);
12211237
}
12221238
NodeGraphMessage::PointerOutsideViewport { shift } => {
12231239
if self.drag_start.is_some() || self.box_selection_start.is_some() {
@@ -1306,6 +1322,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
13061322
has_left_input_wire,
13071323
});
13081324
responses.add(NodeGraphMessage::SendSelectedNodes);
1325+
self.update_node_graph_hints(responses);
13091326
}
13101327
}
13111328
NodeGraphMessage::SetGridAlignedEdges => {
@@ -1542,7 +1559,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
15421559
responses.add(PropertiesPanelMessage::Refresh);
15431560
}
15441561
NodeGraphMessage::UpdateBoxSelection => {
1545-
if let Some(box_selection_start) = self.box_selection_start {
1562+
if let Some((box_selection_start, _)) = self.box_selection_start {
15461563
// The mouse button was released but we missed the pointer up event
15471564
// if ((e.buttons & 1) === 0) {
15481565
// completeBoxSelection();
@@ -1572,7 +1589,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
15721589
.inverse()
15731590
.transform_point2(ipp.mouse.position);
15741591

1575-
let shift = ipp.keyboard.get(crate::messages::tool::tool_messages::tool_prelude::Key::Shift as usize);
1592+
let shift = ipp.keyboard.get(Key::Shift as usize);
15761593
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
15771594
log::error!("Could not get selected nodes in PointerMove");
15781595
return;
@@ -1666,6 +1683,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
16661683
NodeGraphMessage::UpdateInSelectedNetwork => responses.add(FrontendMessage::UpdateInSelectedNetwork {
16671684
in_selected_network: selection_network_path == breadcrumb_network_path,
16681685
}),
1686+
NodeGraphMessage::UpdateHints => {
1687+
self.update_node_graph_hints(responses);
1688+
}
16691689
NodeGraphMessage::SendSelectedNodes => {
16701690
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(breadcrumb_network_path) else {
16711691
log::error!("Could not get selected nodes in NodeGraphMessage::SendSelectedNodes");
@@ -1751,7 +1771,7 @@ impl NodeGraphMessageHandler {
17511771
let mut widgets = vec![
17521772
PopoverButton::new()
17531773
.icon(Some("Node".to_string()))
1754-
.tooltip("Add a new node")
1774+
.tooltip("New Node (Right Click)")
17551775
.popover_layout({
17561776
let node_chooser = NodeCatalog::new()
17571777
.on_update(move |node_type| {
@@ -2421,6 +2441,46 @@ impl NodeGraphMessageHandler {
24212441
DVec2::new(input_position.x, input_position.y),
24222442
]
24232443
}
2444+
2445+
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
2446+
// A wire is in progress and its start and end connectors are set
2447+
let wiring = self.wire_in_progress_from_connector.is_some();
2448+
2449+
// Node gragging is in progress (having already moved at least one pixel from the mouse down position)
2450+
let dragging_nodes = self.drag_start.as_ref().is_some_and(|(_, dragged)| *dragged);
2451+
2452+
// A box selection is in progress
2453+
let dragging_box_selection = self.box_selection_start.is_some_and(|(_, box_selection_dragged)| box_selection_dragged);
2454+
2455+
// Cancel the ongoing action
2456+
if wiring || dragging_nodes || dragging_box_selection {
2457+
let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]);
2458+
responses.add(FrontendMessage::UpdateInputHints { hint_data });
2459+
return;
2460+
}
2461+
2462+
// Default hints for all other states
2463+
let mut hint_data = HintData(vec![
2464+
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, "Add Node")]),
2465+
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Node"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()]),
2466+
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()]),
2467+
]);
2468+
if self.has_selection {
2469+
hint_data.0.extend([
2470+
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
2471+
HintGroup(vec![HintInfo::keys([Key::Delete], "Delete Selected"), HintInfo::keys([Key::Control], "Keep Children").prepend_plus()]),
2472+
HintGroup(vec![
2473+
HintInfo::keys_and_mouse([Key::Alt], MouseMotion::LmbDrag, "Move Duplicate"),
2474+
HintInfo::keys([Key::Control, Key::KeyD], "Duplicate").add_mac_keys([Key::Command, Key::KeyD]),
2475+
]),
2476+
]);
2477+
}
2478+
hint_data.0.extend([
2479+
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDouble, "Enter Node Subgraph")]),
2480+
HintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::Lmb, "Preview Node Output")]),
2481+
]);
2482+
responses.add(FrontendMessage::UpdateInputHints { hint_data });
2483+
}
24242484
}
24252485

24262486
#[derive(Default)]
@@ -2499,7 +2559,7 @@ impl Default for NodeGraphMessageHandler {
24992559
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: Vec::new() }],
25002560
drag_start: None,
25012561
begin_dragging: false,
2502-
drag_occurred: false,
2562+
node_has_moved_in_drag: false,
25032563
shift_without_push: false,
25042564
box_selection_start: None,
25052565
selection_before_pointer_down: Vec::new(),
@@ -2527,7 +2587,7 @@ impl PartialEq for NodeGraphMessageHandler {
25272587
&& self.widgets == other.widgets
25282588
&& self.drag_start == other.drag_start
25292589
&& self.begin_dragging == other.begin_dragging
2530-
&& self.drag_occurred == other.drag_occurred
2590+
&& self.node_has_moved_in_drag == other.node_has_moved_in_drag
25312591
&& self.box_selection_start == other.box_selection_start
25322592
&& self.initial_disconnecting == other.initial_disconnecting
25332593
&& self.select_if_not_dragged == other.select_if_not_dragged

editor/src/messages/tool/tool_message_handler.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
263263
let tool_data = &mut self.tool_state.tool_data;
264264

265265
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
266+
let graph_view_overlay_open = document.graph_view_overlay_open();
267+
266268
if tool_type == tool_data.active_tool_type {
267269
let mut data = ToolActionHandlerData {
268270
document,
@@ -275,7 +277,10 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
275277
preferences,
276278
};
277279
if matches!(tool_message, ToolMessage::UpdateHints) {
278-
if self.transform_layer_handler.is_transforming() {
280+
if graph_view_overlay_open {
281+
// When graph view is open, forward the hint update to the node graph handler
282+
responses.add(NodeGraphMessage::UpdateHints);
283+
} else if self.transform_layer_handler.is_transforming() {
279284
self.transform_layer_handler.hints(responses);
280285
} else {
281286
tool.process_message(ToolMessage::UpdateHints, responses, &mut data)

editor/src/messages/tool/tool_messages/path_tool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1442,7 +1442,7 @@ impl Fsm for PathToolFsmState {
14421442
fn update_hints(&self, responses: &mut VecDeque<Message>) {
14431443
let hint_data = match self {
14441444
PathToolFsmState::Ready => HintData(vec![
1445-
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()]),
1445+
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()]),
14461446
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"), HintInfo::keys([Key::Control], "Lasso").prepend_plus()]),
14471447
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
14481448
// TODO: Only show if at least one anchor is selected, and dynamically show either "Smooth" or "Sharp" based on the current state

editor/src/messages/tool/tool_messages/select_tool.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,11 +1497,10 @@ impl Fsm for SelectToolFsmState {
14971497
match self {
14981498
SelectToolFsmState::Ready { selection } => {
14991499
let hint_data = HintData(vec![
1500-
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
15011500
HintGroup({
1502-
let mut hints = vec![HintInfo::mouse(MouseMotion::Lmb, "Select Object"), HintInfo::keys([Key::Shift], "Extend Selection").prepend_plus()];
1501+
let mut hints = vec![HintInfo::mouse(MouseMotion::Lmb, "Select Object"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()];
15031502
if *selection == NestedSelectionBehavior::Shallowest {
1504-
hints.extend([HintInfo::keys([Key::Accel], "Deepest").prepend_plus(), HintInfo::mouse(MouseMotion::LmbDouble, "Deepen Selection")]);
1503+
hints.extend([HintInfo::keys([Key::Accel], "Deepest").prepend_plus(), HintInfo::mouse(MouseMotion::LmbDouble, "Deepen")]);
15051504
}
15061505
hints
15071506
}),
@@ -1511,6 +1510,8 @@ impl Fsm for SelectToolFsmState {
15111510
HintInfo::keys([Key::Alt], "Subtract").prepend_plus(),
15121511
HintInfo::keys([Key::Control], "Lasso").prepend_plus(),
15131512
]),
1513+
// TODO: Make all the following hints only appear if there is at least one selected layer
1514+
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")]),
15141515
HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]),
15151516
HintGroup(vec![
15161517
HintInfo::arrow_keys("Nudge Selected"),
@@ -1546,7 +1547,7 @@ impl Fsm for SelectToolFsmState {
15461547
HintGroup(vec![HintInfo::keys([Key::Shift], "Extend"), HintInfo::keys([Key::Alt], "Subtract")]),
15471548
// TODO: Re-select deselected layers during drag when Shift is pressed, and re-deselect if Shift is released before drag ends.
15481549
// TODO: (See https://discord.com/channels/731730685944922173/1216976541947531264/1321360311298818048)
1549-
// HintGroup(vec![HintInfo::keys([Key::Shift], "Extend Selection")])
1550+
// HintGroup(vec![HintInfo::keys([Key::Shift], "Extend")])
15501551
]);
15511552
responses.add(FrontendMessage::UpdateInputHints { hint_data });
15521553
}

0 commit comments

Comments
 (0)