mirror of
https://github.com/eliasstepanik/egui_node_graph.git
synced 2026-01-11 22:08:28 +00:00
Handle selecting multiple nodes
This commit is contained in:
parent
c2310e5f9b
commit
1fb512b8ae
@ -8,6 +8,8 @@ use egui::epaint::{CubicBezierShape, RectShape};
|
|||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
|
pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
|
||||||
|
pub type NodeRects = std::collections::HashMap<NodeId, Rect>;
|
||||||
|
|
||||||
const DISTANCE_TO_CONNECT: f32 = 10.0;
|
const DISTANCE_TO_CONNECT: f32 = 10.0;
|
||||||
|
|
||||||
/// Nodes communicate certain events to the parent graph when drawn. There is
|
/// Nodes communicate certain events to the parent graph when drawn. There is
|
||||||
@ -60,6 +62,7 @@ pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> {
|
|||||||
pub position: &'a mut Pos2,
|
pub position: &'a mut Pos2,
|
||||||
pub graph: &'a mut Graph<NodeData, DataType, ValueType>,
|
pub graph: &'a mut Graph<NodeData, DataType, ValueType>,
|
||||||
pub port_locations: &'a mut PortLocations,
|
pub port_locations: &'a mut PortLocations,
|
||||||
|
pub node_rects: &'a mut NodeRects,
|
||||||
pub node_id: NodeId,
|
pub node_id: NodeId,
|
||||||
pub ongoing_drag: Option<(NodeId, AnyParameterId)>,
|
pub ongoing_drag: Option<(NodeId, AnyParameterId)>,
|
||||||
pub selected: bool,
|
pub selected: bool,
|
||||||
@ -103,16 +106,21 @@ where
|
|||||||
let mut cursor_in_editor = editor_rect.contains(cursor_pos);
|
let mut cursor_in_editor = editor_rect.contains(cursor_pos);
|
||||||
let mut cursor_in_finder = false;
|
let mut cursor_in_finder = false;
|
||||||
|
|
||||||
// Gets filled with the port locations as nodes are drawn
|
// Gets filled with the node metrics as they are drawn
|
||||||
let mut port_locations = PortLocations::new();
|
let mut port_locations = PortLocations::new();
|
||||||
|
let mut node_sizes = NodeRects::new();
|
||||||
|
|
||||||
// The responses returned from node drawing have side effects that are best
|
// The responses returned from node drawing have side effects that are best
|
||||||
// executed at the end of this function.
|
// executed at the end of this function.
|
||||||
let mut delayed_responses: Vec<NodeResponse<UserResponse, NodeData>> = vec![];
|
let mut delayed_responses: Vec<NodeResponse<UserResponse, NodeData>> = vec![];
|
||||||
|
|
||||||
// Used to detect when the background was clicked, to dismiss certain selfs
|
// Used to detect when the background was clicked
|
||||||
let mut click_on_background = false;
|
let mut click_on_background = false;
|
||||||
|
|
||||||
|
// Used to detect drag events in the background
|
||||||
|
let mut drag_started_on_background = false;
|
||||||
|
let mut drag_released_on_background = false;
|
||||||
|
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
self.node_order.iter().copied().collect::<HashSet<_>>(),
|
self.node_order.iter().copied().collect::<HashSet<_>>(),
|
||||||
self.graph.iter_nodes().collect::<HashSet<_>>(),
|
self.graph.iter_nodes().collect::<HashSet<_>>(),
|
||||||
@ -126,12 +134,13 @@ where
|
|||||||
position: self.node_positions.get_mut(node_id).unwrap(),
|
position: self.node_positions.get_mut(node_id).unwrap(),
|
||||||
graph: &mut self.graph,
|
graph: &mut self.graph,
|
||||||
port_locations: &mut port_locations,
|
port_locations: &mut port_locations,
|
||||||
|
node_rects: &mut node_sizes,
|
||||||
node_id,
|
node_id,
|
||||||
ongoing_drag: self.connection_in_progress,
|
ongoing_drag: self.connection_in_progress,
|
||||||
selected: self
|
selected: self
|
||||||
.selected_node
|
.selected_nodes
|
||||||
.map(|selected| selected == node_id)
|
.iter()
|
||||||
.unwrap_or(false),
|
.any(|selected| *selected == node_id),
|
||||||
pan: self.pan_zoom.pan + editor_rect.min.to_vec2(),
|
pan: self.pan_zoom.pan + editor_rect.min.to_vec2(),
|
||||||
}
|
}
|
||||||
.show(ui, user_state);
|
.show(ui, user_state);
|
||||||
@ -143,6 +152,10 @@ where
|
|||||||
let r = ui.allocate_rect(ui.min_rect(), Sense::click().union(Sense::drag()));
|
let r = ui.allocate_rect(ui.min_rect(), Sense::click().union(Sense::drag()));
|
||||||
if r.clicked() {
|
if r.clicked() {
|
||||||
click_on_background = true;
|
click_on_background = true;
|
||||||
|
} else if r.drag_started() {
|
||||||
|
drag_started_on_background = true;
|
||||||
|
} else if r.drag_released() {
|
||||||
|
drag_released_on_background = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draw the node finder, if open */
|
/* Draw the node finder, if open */
|
||||||
@ -279,7 +292,7 @@ where
|
|||||||
//Convenience NodeResponse for users
|
//Convenience NodeResponse for users
|
||||||
}
|
}
|
||||||
NodeResponse::SelectNode(node_id) => {
|
NodeResponse::SelectNode(node_id) => {
|
||||||
self.selected_node = Some(*node_id);
|
self.selected_nodes = Vec::from([*node_id]);
|
||||||
}
|
}
|
||||||
NodeResponse::DeleteNodeUi(node_id) => {
|
NodeResponse::DeleteNodeUi(node_id) => {
|
||||||
let (node, disc_events) = self.graph.remove_node(*node_id);
|
let (node, disc_events) = self.graph.remove_node(*node_id);
|
||||||
@ -298,9 +311,7 @@ where
|
|||||||
});
|
});
|
||||||
self.node_positions.remove(*node_id);
|
self.node_positions.remove(*node_id);
|
||||||
// Make sure to not leave references to old nodes hanging
|
// Make sure to not leave references to old nodes hanging
|
||||||
if self.selected_node.map(|x| x == *node_id).unwrap_or(false) {
|
self.selected_nodes.retain(|id| *id != *node_id);
|
||||||
self.selected_node = None;
|
|
||||||
}
|
|
||||||
self.node_order.retain(|id| *id != *node_id);
|
self.node_order.retain(|id| *id != *node_id);
|
||||||
}
|
}
|
||||||
NodeResponse::DisconnectEvent { input, output } => {
|
NodeResponse::DisconnectEvent { input, output } => {
|
||||||
@ -327,6 +338,30 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle box selection
|
||||||
|
if let Some(box_start) = self.ongoing_box_selection {
|
||||||
|
let selection_rect = Rect::from_two_pos(cursor_pos, box_start);
|
||||||
|
let bg_color = Color32::from_rgba_unmultiplied(200, 200, 200, 20);
|
||||||
|
let stroke_color = Color32::from_rgba_unmultiplied(200, 200, 200, 180);
|
||||||
|
ui.painter().rect(
|
||||||
|
selection_rect,
|
||||||
|
2.0,
|
||||||
|
bg_color,
|
||||||
|
Stroke::new(3.0, stroke_color),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.selected_nodes = node_sizes
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(node_id, rect)| {
|
||||||
|
if selection_rect.intersects(rect) {
|
||||||
|
Some(node_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
// Push any responses that were generated during response handling.
|
// Push any responses that were generated during response handling.
|
||||||
// These are only informative for the end-user and need no special
|
// These are only informative for the end-user and need no special
|
||||||
// treatment here.
|
// treatment here.
|
||||||
@ -355,10 +390,17 @@ where
|
|||||||
// Deselect and deactivate finder if the editor backround is clicked,
|
// Deselect and deactivate finder if the editor backround is clicked,
|
||||||
// *or* if the the mouse clicks off the ui
|
// *or* if the the mouse clicks off the ui
|
||||||
if click_on_background || (mouse.any_click() && !cursor_in_editor) {
|
if click_on_background || (mouse.any_click() && !cursor_in_editor) {
|
||||||
self.selected_node = None;
|
self.selected_nodes = Vec::new();
|
||||||
self.node_finder = None;
|
self.node_finder = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dbg!(drag_started_on_background) {
|
||||||
|
self.ongoing_box_selection = Some(cursor_pos);
|
||||||
|
}
|
||||||
|
if mouse.primary_released() || drag_released_on_background {
|
||||||
|
self.ongoing_box_selection = None;
|
||||||
|
}
|
||||||
|
|
||||||
GraphResponse {
|
GraphResponse {
|
||||||
node_responses: delayed_responses,
|
node_responses: delayed_responses,
|
||||||
}
|
}
|
||||||
@ -690,12 +732,10 @@ where
|
|||||||
stroke: Stroke::none(),
|
stroke: Stroke::none(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let node_rect = titlebar_rect.union(body_rect).union(bottom_body_rect);
|
||||||
let outline = if self.selected {
|
let outline = if self.selected {
|
||||||
Shape::Rect(RectShape {
|
Shape::Rect(RectShape {
|
||||||
rect: titlebar_rect
|
rect: node_rect.expand(1.0),
|
||||||
.union(body_rect)
|
|
||||||
.union(bottom_body_rect)
|
|
||||||
.expand(1.0),
|
|
||||||
rounding,
|
rounding,
|
||||||
fill: Color32::WHITE.lighten(0.8),
|
fill: Color32::WHITE.lighten(0.8),
|
||||||
stroke: Stroke::none(),
|
stroke: Stroke::none(),
|
||||||
@ -704,6 +744,9 @@ where
|
|||||||
Shape::Noop
|
Shape::Noop
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Take note of the node rect, so the editor can use it later to compute intersections.
|
||||||
|
self.node_rects.insert(self.node_id, node_rect);
|
||||||
|
|
||||||
(Shape::Vec(vec![titlebar, body, bottom_body]), outline)
|
(Shape::Vec(vec![titlebar, body, bottom_body]), outline)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,9 @@ pub struct GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserSta
|
|||||||
pub connection_in_progress: Option<(NodeId, AnyParameterId)>,
|
pub connection_in_progress: Option<(NodeId, AnyParameterId)>,
|
||||||
/// The currently selected node. Some interface actions depend on the
|
/// The currently selected node. Some interface actions depend on the
|
||||||
/// currently selected node.
|
/// currently selected node.
|
||||||
pub selected_node: Option<NodeId>,
|
pub selected_nodes: Vec<NodeId>,
|
||||||
|
/// The mouse drag start position for an ongoing box selection.
|
||||||
|
pub ongoing_box_selection: Option<egui::Pos2>,
|
||||||
/// The position of each node.
|
/// The position of each node.
|
||||||
pub node_positions: SecondaryMap<NodeId, egui::Pos2>,
|
pub node_positions: SecondaryMap<NodeId, egui::Pos2>,
|
||||||
/// The node finder is used to create new nodes.
|
/// The node finder is used to create new nodes.
|
||||||
@ -54,7 +56,8 @@ impl<NodeData, DataType, ValueType, NodeKind, UserState> Default
|
|||||||
graph: Default::default(),
|
graph: Default::default(),
|
||||||
node_order: Default::default(),
|
node_order: Default::default(),
|
||||||
connection_in_progress: Default::default(),
|
connection_in_progress: Default::default(),
|
||||||
selected_node: Default::default(),
|
selected_nodes: Default::default(),
|
||||||
|
ongoing_box_selection: Default::default(),
|
||||||
node_positions: Default::default(),
|
node_positions: Default::default(),
|
||||||
node_finder: Default::default(),
|
node_finder: Default::default(),
|
||||||
pan_zoom: Default::default(),
|
pan_zoom: Default::default(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user