diff --git a/egui_node_graph/src/editor_ui.rs b/egui_node_graph/src/editor_ui.rs index 15618bd..1b0bb7e 100644 --- a/egui_node_graph/src/editor_ui.rs +++ b/egui_node_graph/src/editor_ui.rs @@ -12,8 +12,8 @@ pub type PortLocations = std::collections::HashMap; /// Nodes communicate certain events to the parent graph when drawn. There is /// one special `User` variant which can be used by users as the return value /// when executing some custom actions in the UI of the node. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum NodeResponse { +#[derive(Clone, Debug)] +pub enum NodeResponse { ConnectEventStarted(NodeId, AnyParameterId), ConnectEventEnded { output: OutputId, @@ -21,7 +21,16 @@ pub enum NodeResponse { }, CreatedNode(NodeId), SelectNode(NodeId), - DeleteNode(NodeId), + /// As a user of this library, prefer listening for `DeleteNodeFull` which + /// will also contain the user data for the deleted node. + DeleteNodeUi(NodeId), + /// Emitted when a node is deleted. The node will no longer exist in the + /// graph after this response is returned from the draw function, but its + /// contents are passed along with the event. + DeleteNodeFull { + node_id: NodeId, + node: Node, + }, DisconnectEvent { output: OutputId, input: InputId, @@ -34,8 +43,8 @@ pub enum NodeResponse { /// The return value of [`draw_graph_editor`]. This value can be used to make /// user code react to specific events that happened when drawing the graph. #[derive(Clone, Debug)] -pub struct GraphResponse { - pub node_responses: Vec>, +pub struct GraphResponse { + pub node_responses: Vec>, } pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> { @@ -68,7 +77,7 @@ where &mut self, ui: &mut Ui, all_kinds: impl NodeTemplateIter, - ) -> GraphResponse { + ) -> GraphResponse { // This causes the graph editor to use as much free space as it can. // (so for windows it will use up to the resizeably set limit // and for a Panel it will fill it completely) @@ -83,7 +92,7 @@ where // The responses returned from node drawing have side effects that are best // executed at the end of this function. - let mut delayed_responses: Vec> = vec![]; + let mut delayed_responses: Vec> = vec![]; // Used to detect when the background was clicked, to dismiss certain selfs let mut click_on_background = false; @@ -182,54 +191,63 @@ where // Some responses generate additional responses when processed. These // are stored here to report them back to the user. - let mut extra_responses: Vec> = Vec::new(); + let mut extra_responses: Vec> = Vec::new(); - for response in delayed_responses.iter().copied() { + for response in delayed_responses.iter() { match response { NodeResponse::ConnectEventStarted(node_id, port) => { - self.connection_in_progress = Some((node_id, port)); + self.connection_in_progress = Some((*node_id, *port)); } NodeResponse::ConnectEventEnded { input, output } => { - self.graph.add_connection(output, input) + self.graph.add_connection(*output, *input) } NodeResponse::CreatedNode(_) => { //Convenience NodeResponse for users } NodeResponse::SelectNode(node_id) => { - self.selected_node = Some(node_id); + self.selected_node = Some(*node_id); } - NodeResponse::DeleteNode(node_id) => { - let removed = self.graph.remove_node(node_id); + NodeResponse::DeleteNodeUi(node_id) => { + let (node, disc_events) = self.graph.remove_node(*node_id); + // Pass the full node as a response so library users can + // listen for it and get their user data. + extra_responses.push(NodeResponse::DeleteNodeFull { + node_id: *node_id, + node, + }); extra_responses.extend( - removed + disc_events .into_iter() .map(|(input, output)| NodeResponse::DisconnectEvent { input, output }), ); - self.node_positions.remove(node_id); + self.node_positions.remove(*node_id); // Make sure to not leave references to old nodes hanging - if self.selected_node.map(|x| x == node_id).unwrap_or(false) { + if self.selected_node.map(|x| x == *node_id).unwrap_or(false) { self.selected_node = None; } - self.node_order.retain(|id| *id != node_id); + self.node_order.retain(|id| *id != *node_id); } NodeResponse::DisconnectEvent { input, output } => { - let other_node = self.graph.get_input(input).node(); - self.graph.remove_connection(input); + let other_node = self.graph.get_input(*input).node(); + self.graph.remove_connection(*input); self.connection_in_progress = - Some((other_node, AnyParameterId::Output(output))); + Some((other_node, AnyParameterId::Output(*output))); } NodeResponse::RaiseNode(node_id) => { let old_pos = self .node_order .iter() - .position(|id| *id == node_id) + .position(|id| *id == *node_id) .expect("Node to be raised should be in `node_order`"); self.node_order.remove(old_pos); - self.node_order.push(node_id); + self.node_order.push(*node_id); } NodeResponse::User(_) => { // These are handled by the user code. } + NodeResponse::DeleteNodeFull { .. } => { + unreachable!("The UI should never produce a DeleteNodeFull event.") + } } } @@ -303,7 +321,11 @@ where { pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0]; - pub fn show(self, ui: &mut Ui, user_state: &UserState) -> Vec> { + pub fn show( + self, + ui: &mut Ui, + user_state: &UserState, + ) -> Vec> { let mut child_ui = ui.child_ui_with_id_source( Rect::from_min_size(*self.position + self.pan, Self::MAX_NODE_SIZE.into()), Layout::default(), @@ -319,9 +341,9 @@ where self, ui: &mut Ui, user_state: &UserState, - ) -> Vec> { + ) -> Vec> { let margin = egui::vec2(15.0, 5.0); - let mut responses = Vec::new(); + let mut responses = Vec::>::new(); let background_color; let text_color; @@ -415,7 +437,7 @@ where node_id: NodeId, user_state: &UserState, port_pos: Pos2, - responses: &mut Vec>, + responses: &mut Vec>, param_id: AnyParameterId, port_locations: &mut PortLocations, ongoing_drag: Option<(NodeId, AnyParameterId)>, @@ -423,6 +445,7 @@ where ) where DataType: DataTypeTrait, UserResponse: UserResponseTrait, + NodeData: NodeDataTrait, { let port_type = graph.any_param_type(param_id).unwrap(); @@ -596,7 +619,7 @@ where // Titlebar buttons if Self::close_button(ui, outer_rect).clicked() { - responses.push(NodeResponse::DeleteNode(self.node_id)); + responses.push(NodeResponse::DeleteNodeUi(self.node_id)); }; let window_response = ui.interact( diff --git a/egui_node_graph/src/graph_impls.rs b/egui_node_graph/src/graph_impls.rs index 9c26881..44b5a5d 100644 --- a/egui_node_graph/src/graph_impls.rs +++ b/egui_node_graph/src/graph_impls.rs @@ -53,6 +53,20 @@ impl Graph { input_id } + pub fn remove_input_param(&mut self, param: InputId) { + let node = self[param].node; + self[node].inputs.retain(|(_, id)| *id != param); + self.inputs.remove(param); + self.connections.retain(|i, _| i != param); + } + + pub fn remove_output_param(&mut self, param: OutputId) { + let node = self[param].node; + self[node].outputs.retain(|(_, id)| *id != param); + self.outputs.remove(param); + self.connections.retain(|_, o| *o != param); + } + pub fn add_output_param(&mut self, node_id: NodeId, name: String, typ: DataType) -> OutputId { let output_id = self.outputs.insert_with_key(|output_id| OutputParam { id: output_id, @@ -70,7 +84,7 @@ impl Graph { /// after deleting this node as input-output pairs. Note that one of the two /// ids in the pair (the one on `node_id`'s end) will be invalid after /// calling this function. - pub fn remove_node(&mut self, node_id: NodeId) -> Vec<(InputId, OutputId)> { + pub fn remove_node(&mut self, node_id: NodeId) -> (Node, Vec<(InputId, OutputId)>) { let mut disconnect_events = vec![]; self.connections.retain(|i, o| { @@ -90,9 +104,9 @@ impl Graph { for output in self[node_id].output_ids().collect::>() { self.outputs.remove(output); } - self.nodes.remove(node_id); + let removed_node = self.nodes.remove(node_id).expect("Node should exist"); - disconnect_events + (removed_node, disconnect_events) } pub fn remove_connection(&mut self, input_id: InputId) -> Option { @@ -123,10 +137,18 @@ impl Graph { .ok_or(EguiGraphError::InvalidParameterId(param)) } + pub fn try_get_input(&self, input: InputId) -> Option<&InputParam> { + self.inputs.get(input) + } + pub fn get_input(&self, input: InputId) -> &InputParam { &self.inputs[input] } + pub fn try_get_output(&self, output: OutputId) -> Option<&OutputParam> { + self.outputs.get(output) + } + pub fn get_output(&self, output: OutputId) -> &OutputParam { &self.outputs[output] } diff --git a/egui_node_graph/src/traits.rs b/egui_node_graph/src/traits.rs index 79d1f3e..bfeef0f 100644 --- a/egui_node_graph/src/traits.rs +++ b/egui_node_graph/src/traits.rs @@ -72,7 +72,7 @@ where node_id: NodeId, graph: &Graph, user_state: &Self::UserState, - ) -> Vec> + ) -> Vec> where Self::Response: UserResponseTrait; @@ -130,4 +130,4 @@ pub trait NodeTemplateTrait: Clone { /// The custom user response types when drawing nodes in the graph must /// implement this trait. -pub trait UserResponseTrait: Clone + Copy + std::fmt::Debug + PartialEq + Eq {} +pub trait UserResponseTrait: Clone + std::fmt::Debug {}