-
-
Notifications
You must be signed in to change notification settings - Fork 989
Add lasso selection to node graph #3333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
c4f4e89
c880368
52a3bbf
4f14e21
3f14023
31e3508
66abc57
9427062
d3aca58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; | |
| use crate::messages::portfolio::document::document_message_handler::navigation_controls; | ||
| use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; | ||
| use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; | ||
| use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, NodeGraphErrorDiagnostic}; | ||
| use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, LassoSelection, NodeGraphErrorDiagnostic}; | ||
| use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; | ||
| use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; | ||
| use crate::messages::portfolio::document::utility_types::network_interface::{ | ||
|
|
@@ -25,8 +25,9 @@ use glam::{DAffine2, DVec2, IVec2}; | |
| use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; | ||
| use graphene_std::math::math_ext::QuadExt; | ||
| use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath; | ||
| use graphene_std::vector::misc::dvec2_to_point; | ||
| use graphene_std::*; | ||
| use kurbo::{DEFAULT_ACCURACY, Shape}; | ||
| use kurbo::{DEFAULT_ACCURACY, Line, PathSeg, Shape}; | ||
| use renderer::Quad; | ||
| use std::cmp::Ordering; | ||
|
|
||
|
|
@@ -63,7 +64,12 @@ pub struct NodeGraphMessageHandler { | |
| pub drag_start_chain_nodes: Vec<NodeId>, | ||
| /// If dragging the background to create a box selection, this stores its starting point in node graph coordinates, | ||
| /// plus a flag indicating if it has been dragged since the mousedown began. | ||
| /// (We should only update hints when it has been dragged after the initial mousedown.) | ||
| box_selection_start: Option<(DVec2, bool)>, | ||
| /// If dragging the background to create a lasso selection, this stores its current lasso polygon in node graph coordinates. | ||
| /// Notice that it has been dragged since the mousedown began iff the polygon has at least two points. | ||
| /// (We should only update hints when it has been dragged after the initial mousedown.) | ||
| lasso_selection_curr: Option<Vec<DVec2>>, | ||
| /// Restore the selection before box selection if it is aborted | ||
| selection_before_pointer_down: Vec<NodeId>, | ||
| /// If the grip icon is held during a drag, then shift without pushing other nodes | ||
|
|
@@ -765,6 +771,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG | |
| responses.add(FrontendMessage::UpdateBox { box_selection: None }); | ||
| return; | ||
| } | ||
| // Abort a lasso selection | ||
| if self.lasso_selection_curr.is_some() { | ||
| self.lasso_selection_curr = None; | ||
| responses.add(NodeGraphMessage::SelectedNodesSet { | ||
| nodes: self.selection_before_pointer_down.clone(), | ||
| }); | ||
| responses.add(FrontendMessage::UpdateLasso { lasso_selection: None }); | ||
| return; | ||
| } | ||
| // Abort dragging a wire | ||
| if self.wire_in_progress_from_connector.is_some() { | ||
| self.wire_in_progress_from_connector = None; | ||
|
|
@@ -974,7 +989,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG | |
| if !shift_click && !alt_click { | ||
| responses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() }) | ||
| } | ||
| self.box_selection_start = Some((node_graph_point, false)); | ||
|
|
||
| if control_click { | ||
| self.lasso_selection_curr = Some(vec![node_graph_point]); | ||
| } else { | ||
| self.box_selection_start = Some((node_graph_point, false)); | ||
| } | ||
|
|
||
| self.update_node_graph_hints(responses); | ||
| } | ||
| NodeGraphMessage::PointerMove { shift } => { | ||
|
|
@@ -1109,6 +1130,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG | |
| *box_selection_dragged = true; | ||
| responses.add(NodeGraphMessage::UpdateBoxSelection); | ||
| self.update_node_graph_hints(responses); | ||
| } else if self.lasso_selection_curr.is_some() { | ||
| responses.add(NodeGraphMessage::UpdateLassoSelection); | ||
| self.update_node_graph_hints(responses); | ||
| } else if self.reordering_import.is_some() { | ||
| let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { | ||
| log::error!("Could not get modify import export in PointerMove"); | ||
|
|
@@ -1391,6 +1415,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG | |
| self.drag_start = None; | ||
| self.begin_dragging = false; | ||
| self.box_selection_start = None; | ||
| self.lasso_selection_curr = None; | ||
| self.wire_in_progress_from_connector = None; | ||
| self.wire_in_progress_type = FrontendGraphDataType::General; | ||
| self.wire_in_progress_to_connector = None; | ||
|
|
@@ -1399,12 +1424,17 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG | |
| responses.add(DocumentMessage::EndTransaction); | ||
| responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None }); | ||
| responses.add(FrontendMessage::UpdateBox { box_selection: None }); | ||
| responses.add(FrontendMessage::UpdateLasso { lasso_selection: None }); | ||
| responses.add(FrontendMessage::UpdateImportReorderIndex { index: None }); | ||
| responses.add(FrontendMessage::UpdateExportReorderIndex { index: None }); | ||
| self.update_node_graph_hints(responses); | ||
| } | ||
| NodeGraphMessage::PointerOutsideViewport { shift } => { | ||
| if self.drag_start.is_some() || self.box_selection_start.is_some() || (self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none()) { | ||
| if self.drag_start.is_some() | ||
| || self.box_selection_start.is_some() | ||
| || self.lasso_selection_curr.is_some() | ||
| || (self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none()) | ||
| { | ||
| let _ = self.auto_panning.shift_viewport(ipp, viewport, responses); | ||
| } else { | ||
| // Auto-panning | ||
|
|
@@ -1892,15 +1922,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG | |
| } | ||
| NodeGraphMessage::UpdateBoxSelection => { | ||
| if let Some((box_selection_start, _)) = self.box_selection_start { | ||
| // The mouse button was released but we missed the pointer up event | ||
| // if ((e.buttons & 1) === 0) { | ||
| // completeBoxSelection(); | ||
| // boxSelection = undefined; | ||
| // } else if ((e.buttons & 2) !== 0) { | ||
| // editor.handle.selectNodes(new BigUint64Array(previousSelection)); | ||
| // boxSelection = undefined; | ||
| // } | ||
|
|
||
| let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { | ||
| log::error!("Could not get network metadata in UpdateBoxSelection"); | ||
| return; | ||
|
|
@@ -1956,6 +1977,70 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG | |
| responses.add(FrontendMessage::UpdateBox { box_selection }) | ||
| } | ||
| } | ||
| NodeGraphMessage::UpdateLassoSelection => { | ||
| if let Some(lasso_selection_curr) = &mut self.lasso_selection_curr { | ||
| let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { | ||
| log::error!("Could not get network metadata in UpdateLassoSelection"); | ||
| return; | ||
| }; | ||
|
|
||
| let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport; | ||
| let viewport_to_node_graph = node_graph_to_viewport.inverse(); | ||
|
|
||
| lasso_selection_curr.push(viewport_to_node_graph.transform_point2(ipp.mouse.position)); | ||
|
|
||
| responses.add(FrontendMessage::UpdateLasso { | ||
| lasso_selection: Some(LassoSelection::from_iter( | ||
| lasso_selection_curr.iter().map(|selection_point| node_graph_to_viewport.transform_point2(*selection_point)), | ||
| )), | ||
| }); | ||
|
|
||
| let shift = ipp.keyboard.get(Key::Shift as usize); | ||
| let alt = ipp.keyboard.get(Key::Alt as usize); | ||
| let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { | ||
| log::error!("Could not get selected nodes in UpdateLassoSelection"); | ||
| return; | ||
| }; | ||
| let previous_selection = selected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>(); | ||
| let mut nodes = if shift || alt { | ||
| selected_nodes.selected_nodes_ref().iter().cloned().collect::<HashSet<_>>() | ||
| } else { | ||
| HashSet::new() | ||
| }; | ||
| let all_nodes = network_metadata.persistent_metadata.node_metadata.keys().cloned().collect::<Vec<_>>(); | ||
| let path: Vec<PathSeg> = { | ||
| fn points_to_polygon(points: &[DVec2]) -> Vec<PathSeg> { | ||
| points | ||
| .windows(2) | ||
| .map(|w| PathSeg::Line(Line::new(dvec2_to_point(w[0]), dvec2_to_point(w[1])))) | ||
| .chain(std::iter::once(PathSeg::Line(Line::new( | ||
| dvec2_to_point(*points.last().unwrap()), | ||
| dvec2_to_point(*points.first().unwrap()), | ||
| )))) | ||
| .collect() | ||
| } | ||
| points_to_polygon(lasso_selection_curr) | ||
|
Comment on lines
+2012
to
+2022
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than allocating, consider a simplye point in polygon algorithm https://wrfranklin.org/Research/Short_Notes/pnpoly.html.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I feel the disappeal of needing to allocate, but I'm not sure how point-in-polygon helps. I stole That is, without some digging, I currently don't know how to get the bounding boxes of all the nodes.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it wouldn't matter too much. |
||
| }; | ||
| for node_id in all_nodes { | ||
| let Some(click_targets) = network_interface.node_click_targets(&node_id, selection_network_path) else { | ||
| log::error!("Could not get transient metadata for node {node_id}"); | ||
| continue; | ||
| }; | ||
| if click_targets.node_click_target.intersect_path(|| path.iter().cloned(), DAffine2::IDENTITY) { | ||
| if alt { | ||
| nodes.remove(&node_id); | ||
| } else { | ||
| nodes.insert(node_id); | ||
| } | ||
| } | ||
| } | ||
| if nodes != previous_selection { | ||
| responses.add(NodeGraphMessage::SelectedNodesSet { | ||
| nodes: nodes.into_iter().collect::<Vec<_>>(), | ||
| }); | ||
| } | ||
|
Comment on lines
+2037
to
+2041
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is probably easiest to just always send this message (rather than allocating the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't intuit which is better, but I'll take your word for it. When I or whoever makes the change, I assume they should also make the identical analogous change to the box selection code?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it doesn't matter very much either way.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving it for now, then. |
||
| } | ||
| } | ||
| NodeGraphMessage::UpdateImportsExports => { | ||
| let imports = network_interface.frontend_imports(breadcrumb_network_path); | ||
| let exports = network_interface.frontend_exports(breadcrumb_network_path); | ||
|
|
@@ -2711,12 +2796,19 @@ impl NodeGraphMessageHandler { | |
| // Node gragging is in progress (having already moved at least one pixel from the mouse down position) | ||
| let dragging_nodes = self.drag_start.as_ref().is_some_and(|(_, dragged)| *dragged); | ||
|
|
||
| // A box selection is in progress | ||
| let dragging_box_selection = self.box_selection_start.is_some_and(|(_, box_selection_dragged)| box_selection_dragged); | ||
| // A box or lasso selection is in progress | ||
| let dragging_selection = self.box_selection_start.as_ref().is_some_and(|(_, box_selection_dragged)| *box_selection_dragged) | ||
| || self.lasso_selection_curr.as_ref().is_some_and(|lasso_selection| lasso_selection.len() >= 2); | ||
|
|
||
| // Cancel the ongoing action | ||
| if wiring || dragging_nodes || dragging_box_selection { | ||
| let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]); | ||
| if wiring || dragging_nodes || dragging_selection { | ||
| let hint_data = HintData(vec![ | ||
| HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), | ||
| HintGroup(vec![HintInfo::keys([Key::Shift], "Extend"), HintInfo::keys([Key::Alt], "Subtract")]), | ||
| // TODO: Re-select deselected layers during drag when Shift is pressed, and re-deselect if Shift is released before drag ends. | ||
| // TODO: (See https://discord.com/channels/731730685944922173/1216976541947531264/1321360311298818048) | ||
| // TODO: (Also remember to do this for the select tool; grep for these todo comments.) | ||
| ]); | ||
| responses.add(FrontendMessage::UpdateInputHints { hint_data }); | ||
| return; | ||
| } | ||
|
|
@@ -2729,6 +2821,7 @@ impl NodeGraphMessageHandler { | |
| HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"), | ||
| HintInfo::keys([Key::Shift], "Extend").prepend_plus(), | ||
| HintInfo::keys([Key::Alt], "Subtract").prepend_plus(), | ||
| HintInfo::keys([Key::Accel], "Lasso").prepend_plus(), | ||
| ]), | ||
| ]); | ||
| if self.has_selection { | ||
|
|
@@ -2760,6 +2853,7 @@ impl Default for NodeGraphMessageHandler { | |
| node_has_moved_in_drag: false, | ||
| shift_without_push: false, | ||
| box_selection_start: None, | ||
| lasso_selection_curr: None, | ||
| drag_start_chain_nodes: Vec::new(), | ||
| selection_before_pointer_down: Vec::new(), | ||
| disconnecting: None, | ||
|
|
@@ -2790,6 +2884,7 @@ impl PartialEq for NodeGraphMessageHandler { | |
| && self.begin_dragging == other.begin_dragging | ||
| && self.node_has_moved_in_drag == other.node_has_moved_in_drag | ||
| && self.box_selection_start == other.box_selection_start | ||
| && self.lasso_selection_curr == other.lasso_selection_curr | ||
| && self.initial_disconnecting == other.initial_disconnecting | ||
| && self.select_if_not_dragged == other.select_if_not_dragged | ||
| && self.wire_in_progress_from_connector == other.wire_in_progress_from_connector | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.