Skip to content

Commit f299497

Browse files
Keavonadamgerhant
andauthored
Add shaking input gesture to disconnect a node being dragged (#2889)
* Add shaking input gesture to disconnect a node being dragged * Improve shake detection algorithm * Fix reconnection * Improve shake reconnect logic * Fix history --------- Co-authored-by: Adam <[email protected]>
1 parent e4ec67d commit f299497

File tree

11 files changed

+302
-36
lines changed

11 files changed

+302
-36
lines changed

editor/src/messages/input_mapper/input_mapper_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ pub enum InputMapperMessage {
1919

2020
// Messages
2121
PointerMove,
22+
PointerShake,
2223
WheelScroll,
2324
}

editor/src/messages/input_mapper/input_mappings.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ pub fn input_mappings() -> Mapping {
5454
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
5555
//
5656
// NodeGraphMessage
57-
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}),
58-
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}),
59-
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: true, alt_click: false, right_click: false}),
60-
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: true, alt_click: false, right_click: false}),
61-
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: true, right_click: false}),
62-
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: true}),
57+
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
58+
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
59+
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: true, alt_click: false, right_click: false }),
60+
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: true, alt_click: false, right_click: false }),
61+
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: true, right_click: false }),
62+
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: true }),
6363
entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
64-
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}),
64+
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove { shift: Shift }),
65+
entry!(PointerShake; action_dispatch=NodeGraphMessage::ShakeNode),
6566
entry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp),
6667
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
6768
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
@@ -417,7 +418,7 @@ pub fn input_mappings() -> Mapping {
417418
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
418419
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
419420
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
420-
entry!(KeyDown(KeyW); modifiers=[Accel,Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
421+
entry!(KeyDown(KeyW); modifiers=[Accel, Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
421422
entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument),
422423
entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import),
423424
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
@@ -440,7 +441,7 @@ pub fn input_mappings() -> Mapping {
440441
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
441442
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),
442443
];
443-
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
444+
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move, mut pointer_shake) = mappings;
444445

445446
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|a, b| b.modifiers.count_ones().cmp(&a.modifiers.count_ones()));
446447
// Sort the sublists of `key_up`, `key_down`, `key_up_no_repeat`, and `key_down_no_repeat`
@@ -457,6 +458,8 @@ pub fn input_mappings() -> Mapping {
457458
sort(&mut wheel_scroll);
458459
// Sort `pointer_move`
459460
sort(&mut pointer_move);
461+
// Sort `pointer_shake`
462+
sort(&mut pointer_shake);
460463

461464
Mapping {
462465
key_up,
@@ -466,6 +469,7 @@ pub fn input_mappings() -> Mapping {
466469
double_click,
467470
wheel_scroll,
468471
pointer_move,
472+
pointer_shake,
469473
}
470474
}
471475

editor/src/messages/input_mapper/utility_types/macros.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ macro_rules! mapping {
9090
let mut double_click = KeyMappingEntries::mouse_buttons_arrays();
9191
let mut wheel_scroll = KeyMappingEntries::new();
9292
let mut pointer_move = KeyMappingEntries::new();
93+
let mut pointer_shake = KeyMappingEntries::new();
9394

9495
$(
9596
// Each of the many entry slices, one specified per action
@@ -104,14 +105,15 @@ macro_rules! mapping {
104105
InputMapperMessage::DoubleClick(key) => &mut double_click[key as usize],
105106
InputMapperMessage::WheelScroll => &mut wheel_scroll,
106107
InputMapperMessage::PointerMove => &mut pointer_move,
108+
InputMapperMessage::PointerShake => &mut pointer_shake,
107109
};
108110
// Push each entry to the corresponding `KeyMappingEntries` list for its input type
109111
corresponding_list.push(entry.clone());
110112
}
111113
}
112114
)*
113115

114-
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move)
116+
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move, pointer_shake)
115117
}};
116118
}
117119

editor/src/messages/input_mapper/utility_types/misc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct Mapping {
1414
pub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS],
1515
pub wheel_scroll: KeyMappingEntries,
1616
pub pointer_move: KeyMappingEntries,
17+
pub pointer_shake: KeyMappingEntries,
1718
}
1819

1920
impl Default for Mapping {
@@ -47,6 +48,7 @@ impl Mapping {
4748
InputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize],
4849
InputMapperMessage::WheelScroll => &self.wheel_scroll,
4950
InputMapperMessage::PointerMove => &self.pointer_move,
51+
InputMapperMessage::PointerShake => &self.pointer_shake,
5052
}
5153
}
5254

@@ -59,6 +61,7 @@ impl Mapping {
5961
InputMapperMessage::DoubleClick(key) => &mut self.double_click[*key as usize],
6062
InputMapperMessage::WheelScroll => &mut self.wheel_scroll,
6163
InputMapperMessage::PointerMove => &mut self.pointer_move,
64+
InputMapperMessage::PointerShake => &mut self.pointer_shake,
6265
}
6366
}
6467
}

editor/src/messages/input_preprocessor/input_preprocessor_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub enum InputPreprocessorMessage {
1212
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
1313
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
1414
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
15+
PointerShake { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
1516
CurrentTime { timestamp: u64 },
1617
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
1718
}

editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> f
9797

9898
self.translate_mouse_event(mouse_state, false, responses);
9999
}
100+
InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => {
101+
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
102+
103+
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
104+
self.mouse.position = mouse_state.position;
105+
106+
responses.add(InputMapperMessage::PointerShake);
107+
}
100108
InputPreprocessorMessage::CurrentTime { timestamp } => {
101109
responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
102110
self.time = timestamp;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ pub enum NodeGraphMessage {
8282
node_id: NodeId,
8383
parent: LayerNodeIdentifier,
8484
},
85+
SetChainPosition {
86+
node_id: NodeId,
87+
},
8588
PasteNodes {
8689
serialized_nodes: String,
8790
},
@@ -98,6 +101,7 @@ pub enum NodeGraphMessage {
98101
PointerOutsideViewport {
99102
shift: Key,
100103
},
104+
ShakeNode,
101105
RemoveImport {
102106
import_index: usize,
103107
},

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

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ContextMen
1010
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
1111
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
1212
use crate::messages::portfolio::document::utility_types::network_interface::{
13-
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
13+
self, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
1414
};
1515
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
1616
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
@@ -56,6 +56,8 @@ pub struct NodeGraphMessageHandler {
5656
/// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates,
5757
/// plus a flag indicating if it has been dragged since the mousedown began.
5858
pub drag_start: Option<(DragStart, bool)>,
59+
// Store the selected chain nodes on drag start so they can be reconnected if shaken
60+
pub drag_start_chain_nodes: Vec<NodeId>,
5961
/// If dragging the background to create a box selection, this stores its starting point in node graph coordinates,
6062
/// plus a flag indicating if it has been dragged since the mousedown began.
6163
box_selection_start: Option<(DVec2, bool)>,
@@ -601,6 +603,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
601603
NodeGraphMessage::MoveNodeToChainStart { node_id, parent } => {
602604
network_interface.move_node_to_chain_start(&node_id, parent, selection_network_path);
603605
}
606+
NodeGraphMessage::SetChainPosition { node_id } => {
607+
network_interface.set_chain_position(&node_id, selection_network_path);
608+
}
604609
NodeGraphMessage::PasteNodes { serialized_nodes } => {
605610
let data = match serde_json::from_str::<Vec<(NodeId, NodeTemplate)>>(&serialized_nodes) {
606611
Ok(d) => d,
@@ -854,6 +859,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
854859
};
855860

856861
self.drag_start = Some((drag_start, false));
862+
let selected_chain_nodes = updated_selected
863+
.iter()
864+
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
865+
.copied()
866+
.collect::<Vec<_>>();
867+
self.drag_start_chain_nodes = selected_chain_nodes
868+
.iter()
869+
.flat_map(|selected| {
870+
network_interface
871+
.upstream_flow_back_from_nodes(vec![*selected], selection_network_path, FlowType::PrimaryFlow)
872+
.skip(1)
873+
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
874+
})
875+
.collect::<Vec<_>>();
857876
self.begin_dragging = true;
858877
self.node_has_moved_in_drag = false;
859878
self.update_node_graph_hints(responses);
@@ -1221,6 +1240,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
12211240
{
12221241
return None;
12231242
}
1243+
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
12241244
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
12251245
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
12261246
})
@@ -1303,6 +1323,135 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
13031323
self.auto_panning.stop(&messages, responses);
13041324
}
13051325
}
1326+
NodeGraphMessage::ShakeNode => {
1327+
let Some(drag_start) = &self.drag_start else {
1328+
log::error!("Drag start should be initialized when shaking a node");
1329+
return;
1330+
};
1331+
1332+
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
1333+
return;
1334+
};
1335+
1336+
let viewport_location = ipp.mouse.position;
1337+
let point = network_metadata
1338+
.persistent_metadata
1339+
.navigation_metadata
1340+
.node_graph_to_viewport
1341+
.inverse()
1342+
.transform_point2(viewport_location);
1343+
1344+
// Collect the distance to move the shaken nodes after the undo
1345+
let graph_delta = IVec2::new(((point.x - drag_start.0.start_x) / 24.).round() as i32, ((point.y - drag_start.0.start_y) / 24.).round() as i32);
1346+
1347+
// Undo to the state of the graph before shaking
1348+
responses.add(DocumentMessage::AbortTransaction);
1349+
1350+
// Add a history step to abort to the state before shaking if right clicked
1351+
responses.add(DocumentMessage::StartTransaction);
1352+
1353+
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
1354+
log::error!("Could not get selected nodes in ShakeNode");
1355+
return;
1356+
};
1357+
1358+
let mut all_selected_nodes = selected_nodes.0.iter().copied().collect::<HashSet<_>>();
1359+
for selected_layer in selected_nodes
1360+
.0
1361+
.iter()
1362+
.filter(|selected_node| network_interface.is_layer(selected_node, selection_network_path))
1363+
.copied()
1364+
.collect::<Vec<_>>()
1365+
{
1366+
for sole_dependent in network_interface.upstream_nodes_below_layer(&selected_layer, selection_network_path) {
1367+
all_selected_nodes.insert(sole_dependent);
1368+
}
1369+
}
1370+
1371+
for selected_node in &all_selected_nodes {
1372+
// Handle inputs of selected node
1373+
for input_index in 0..network_interface.number_of_inputs(selected_node, selection_network_path) {
1374+
let input_connector = InputConnector::node(*selected_node, input_index);
1375+
// Only disconnect inputs to non selected nodes
1376+
if network_interface
1377+
.upstream_output_connector(&input_connector, selection_network_path)
1378+
.and_then(|connector| connector.node_id())
1379+
.is_some_and(|node_id| !all_selected_nodes.contains(&node_id))
1380+
{
1381+
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
1382+
}
1383+
}
1384+
1385+
let number_of_outputs = network_interface.number_of_outputs(selected_node, selection_network_path);
1386+
let first_deselected_upstream_node = network_interface
1387+
.upstream_flow_back_from_nodes(vec![*selected_node], selection_network_path, FlowType::PrimaryFlow)
1388+
.find(|upstream_node| !all_selected_nodes.contains(upstream_node));
1389+
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
1390+
log::error!("Could not get output wires in shake input");
1391+
continue;
1392+
};
1393+
1394+
// Disconnect output wires to non selected nodes
1395+
for output_index in 0..number_of_outputs {
1396+
let output_connector = OutputConnector::node(*selected_node, output_index);
1397+
if let Some(downstream_connections) = outward_wires.get(&output_connector) {
1398+
for &input_connector in downstream_connections {
1399+
if input_connector.node_id().is_some_and(|downstream_node| !all_selected_nodes.contains(&downstream_node)) {
1400+
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
1401+
}
1402+
}
1403+
}
1404+
}
1405+
1406+
// Handle reconnection
1407+
// Find first non selected upstream node by primary flow
1408+
if let Some(first_deselected_upstream_node) = first_deselected_upstream_node {
1409+
let Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::node(*selected_node, 0)).cloned() else {
1410+
log::error!("Could not get downstream_connections_to_first_output in shake node");
1411+
return;
1412+
};
1413+
// Reconnect only if all downstream outputs are not selected
1414+
if !downstream_connections_to_first_output
1415+
.iter()
1416+
.any(|connector| connector.node_id().is_some_and(|node_id| all_selected_nodes.contains(&node_id)))
1417+
{
1418+
// Find what output on the deselected upstream node to reconnect to
1419+
for output_index in 0..network_interface.number_of_outputs(&first_deselected_upstream_node, selection_network_path) {
1420+
let output_connector = &OutputConnector::node(first_deselected_upstream_node, output_index);
1421+
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
1422+
log::error!("Could not get output wires in shake input");
1423+
continue;
1424+
};
1425+
if let Some(inputs) = outward_wires.get(output_connector) {
1426+
// This can only run once
1427+
if inputs.iter().any(|input_connector| {
1428+
input_connector
1429+
.node_id()
1430+
.is_some_and(|upstream_node| all_selected_nodes.contains(&upstream_node) && input_connector.input_index() == 0)
1431+
}) {
1432+
// Output index is the output of the deselected upstream node to reconnect to
1433+
for downstream_connections_to_first_output in &downstream_connections_to_first_output {
1434+
responses.add(NodeGraphMessage::CreateWire {
1435+
output_connector: OutputConnector::node(first_deselected_upstream_node, output_index),
1436+
input_connector: *downstream_connections_to_first_output,
1437+
});
1438+
}
1439+
}
1440+
}
1441+
1442+
// Set all chain nodes back to chain position
1443+
// TODO: Fix
1444+
// for chain_node_to_reset in std::mem::take(&mut self.drag_start_chain_nodes) {
1445+
// responses.add(NodeGraphMessage::SetChainPosition { node_id: chain_node_to_reset });
1446+
// }
1447+
}
1448+
}
1449+
}
1450+
}
1451+
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: false });
1452+
responses.add(NodeGraphMessage::RunDocumentGraph);
1453+
responses.add(NodeGraphMessage::SendGraph);
1454+
}
13061455
NodeGraphMessage::RemoveImport { import_index: usize } => {
13071456
network_interface.remove_import(usize, selection_network_path);
13081457
responses.add(NodeGraphMessage::SendGraph);
@@ -1823,6 +1972,12 @@ impl NodeGraphMessageHandler {
18231972
));
18241973
}
18251974

1975+
if self.drag_start.is_some() {
1976+
common.extend(actions!(NodeGraphMessageDiscriminant;
1977+
ShakeNode,
1978+
));
1979+
}
1980+
18261981
common
18271982
}
18281983

@@ -2597,6 +2752,7 @@ impl Default for NodeGraphMessageHandler {
25972752
node_has_moved_in_drag: false,
25982753
shift_without_push: false,
25992754
box_selection_start: None,
2755+
drag_start_chain_nodes: Vec::new(),
26002756
selection_before_pointer_down: Vec::new(),
26012757
disconnecting: None,
26022758
initial_disconnecting: false,

0 commit comments

Comments
 (0)