Merge pull request #41 from setzer22/feature/various_improvements

Various little improvements
This commit is contained in:
setzer22 2022-06-21 11:02:01 +02:00 committed by GitHub
commit d2689108a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 142 additions and 72 deletions

View File

@ -12,14 +12,29 @@ pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
/// Nodes communicate certain events to the parent graph when drawn. There is /// 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 /// 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. /// when executing some custom actions in the UI of the node.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Debug)]
pub enum NodeResponse<UserResponse: UserResponseTrait> { pub enum NodeResponse<UserResponse: UserResponseTrait, NodeData: NodeDataTrait> {
ConnectEventStarted(NodeId, AnyParameterId), ConnectEventStarted(NodeId, AnyParameterId),
ConnectEventEnded(AnyParameterId), ConnectEventEnded {
output: OutputId,
input: InputId,
},
CreatedNode(NodeId), CreatedNode(NodeId),
SelectNode(NodeId), SelectNode(NodeId),
DeleteNode(NodeId), /// As a user of this library, prefer listening for `DeleteNodeFull` which
DisconnectEvent(InputId), /// 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<NodeData>,
},
DisconnectEvent {
output: OutputId,
input: InputId,
},
/// Emitted when a node is interacted with, and should be raised /// Emitted when a node is interacted with, and should be raised
RaiseNode(NodeId), RaiseNode(NodeId),
User(UserResponse), User(UserResponse),
@ -28,8 +43,8 @@ pub enum NodeResponse<UserResponse: UserResponseTrait> {
/// The return value of [`draw_graph_editor`]. This value can be used to make /// 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. /// user code react to specific events that happened when drawing the graph.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GraphResponse<UserResponse: UserResponseTrait> { pub struct GraphResponse<UserResponse: UserResponseTrait, NodeData: NodeDataTrait> {
pub node_responses: Vec<NodeResponse<UserResponse>>, pub node_responses: Vec<NodeResponse<UserResponse, NodeData>>,
} }
pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> { pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> {
@ -62,7 +77,7 @@ where
&mut self, &mut self,
ui: &mut Ui, ui: &mut Ui,
all_kinds: impl NodeTemplateIter<Item = NodeTemplate>, all_kinds: impl NodeTemplateIter<Item = NodeTemplate>,
) -> GraphResponse<UserResponse> { ) -> GraphResponse<UserResponse, NodeData> {
// This causes the graph editor to use as much free space as it can. // 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 // (so for windows it will use up to the resizeably set limit
// and for a Panel it will fill it completely) // and for a Panel it will fill it completely)
@ -77,7 +92,7 @@ where
// 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>> = 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, to dismiss certain selfs
let mut click_on_background = false; let mut click_on_background = false;
@ -176,76 +191,63 @@ where
// Some responses generate additional responses when processed. These // Some responses generate additional responses when processed. These
// are stored here to report them back to the user. // are stored here to report them back to the user.
let mut extra_responses: Vec<NodeResponse<UserResponse>> = Vec::new(); let mut extra_responses: Vec<NodeResponse<UserResponse, NodeData>> = Vec::new();
for response in delayed_responses.iter().copied() { for response in delayed_responses.iter() {
match response { match response {
NodeResponse::ConnectEventStarted(node_id, port) => { NodeResponse::ConnectEventStarted(node_id, port) => {
self.connection_in_progress = Some((node_id, port)); self.connection_in_progress = Some((*node_id, *port));
} }
NodeResponse::ConnectEventEnded(locator) => { NodeResponse::ConnectEventEnded { input, output } => {
let in_out = match ( self.graph.add_connection(*output, *input)
self.connection_in_progress
.map(|(_node, param)| param)
.take()
.expect("Cannot end drag without in-progress connection."),
locator,
) {
(AnyParameterId::Input(input), AnyParameterId::Output(output))
| (AnyParameterId::Output(output), AnyParameterId::Input(input)) => {
Some((input, output))
}
_ => None,
};
if let Some((input, output)) = in_out {
self.graph.add_connection(output, input)
}
} }
NodeResponse::CreatedNode(_) => { NodeResponse::CreatedNode(_) => {
//Convenience NodeResponse for users //Convenience NodeResponse for users
} }
NodeResponse::SelectNode(node_id) => { NodeResponse::SelectNode(node_id) => {
self.selected_node = Some(node_id); self.selected_node = Some(*node_id);
} }
NodeResponse::DeleteNode(node_id) => { NodeResponse::DeleteNodeUi(node_id) => {
let removed = self.graph.remove_node(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( extra_responses.extend(
removed disc_events
.into_iter() .into_iter()
.map(|x| x.0) .map(|(input, output)| NodeResponse::DisconnectEvent { input, output }),
.into_iter()
.map(NodeResponse::DisconnectEvent),
); );
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) { if self.selected_node.map(|x| x == *node_id).unwrap_or(false) {
self.selected_node = None; self.selected_node = None;
} }
self.node_order.retain(|id| *id != node_id); self.node_order.retain(|id| *id != *node_id);
} }
NodeResponse::DisconnectEvent(input_id) => { NodeResponse::DisconnectEvent { input, output } => {
let corresp_output = self let other_node = self.graph.get_input(*input).node();
.graph self.graph.remove_connection(*input);
.connection(input_id)
.expect("Connection data should be valid");
let other_node = self.graph.get_input(input_id).node();
self.graph.remove_connection(input_id);
self.connection_in_progress = self.connection_in_progress =
Some((other_node, AnyParameterId::Output(corresp_output))); Some((other_node, AnyParameterId::Output(*output)));
} }
NodeResponse::RaiseNode(node_id) => { NodeResponse::RaiseNode(node_id) => {
let old_pos = self let old_pos = self
.node_order .node_order
.iter() .iter()
.position(|id| *id == node_id) .position(|id| *id == *node_id)
.expect("Node to be raised should be in `node_order`"); .expect("Node to be raised should be in `node_order`");
self.node_order.remove(old_pos); self.node_order.remove(old_pos);
self.node_order.push(node_id); self.node_order.push(*node_id);
} }
NodeResponse::User(_) => { NodeResponse::User(_) => {
// These are handled by the user code. // These are handled by the user code.
} }
NodeResponse::DeleteNodeFull { .. } => {
unreachable!("The UI should never produce a DeleteNodeFull event.")
}
} }
} }
@ -319,7 +321,11 @@ where
{ {
pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0]; pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0];
pub fn show(self, ui: &mut Ui, user_state: &UserState) -> Vec<NodeResponse<UserResponse>> { pub fn show(
self,
ui: &mut Ui,
user_state: &UserState,
) -> Vec<NodeResponse<UserResponse, NodeData>> {
let mut child_ui = ui.child_ui_with_id_source( let mut child_ui = ui.child_ui_with_id_source(
Rect::from_min_size(*self.position + self.pan, Self::MAX_NODE_SIZE.into()), Rect::from_min_size(*self.position + self.pan, Self::MAX_NODE_SIZE.into()),
Layout::default(), Layout::default(),
@ -335,9 +341,9 @@ where
self, self,
ui: &mut Ui, ui: &mut Ui,
user_state: &UserState, user_state: &UserState,
) -> Vec<NodeResponse<UserResponse>> { ) -> Vec<NodeResponse<UserResponse, NodeData>> {
let margin = egui::vec2(15.0, 5.0); let margin = egui::vec2(15.0, 5.0);
let mut responses = Vec::new(); let mut responses = Vec::<NodeResponse<UserResponse, NodeData>>::new();
let background_color; let background_color;
let text_color; let text_color;
@ -375,6 +381,7 @@ where
.text_style(TextStyle::Button) .text_style(TextStyle::Button)
.color(text_color), .color(text_color),
)); ));
ui.add_space(8.0); // The size of the little cross icon
}); });
ui.add_space(margin.y); ui.add_space(margin.y);
title_height = ui.min_size().y; title_height = ui.min_size().y;
@ -430,7 +437,7 @@ where
node_id: NodeId, node_id: NodeId,
user_state: &UserState, user_state: &UserState,
port_pos: Pos2, port_pos: Pos2,
responses: &mut Vec<NodeResponse<UserResponse>>, responses: &mut Vec<NodeResponse<UserResponse, NodeData>>,
param_id: AnyParameterId, param_id: AnyParameterId,
port_locations: &mut PortLocations, port_locations: &mut PortLocations,
ongoing_drag: Option<(NodeId, AnyParameterId)>, ongoing_drag: Option<(NodeId, AnyParameterId)>,
@ -438,6 +445,7 @@ where
) where ) where
DataType: DataTypeTrait<UserState>, DataType: DataTypeTrait<UserState>,
UserResponse: UserResponseTrait, UserResponse: UserResponseTrait,
NodeData: NodeDataTrait,
{ {
let port_type = graph.any_param_type(param_id).unwrap(); let port_type = graph.any_param_type(param_id).unwrap();
@ -460,7 +468,14 @@ where
if resp.drag_started() { if resp.drag_started() {
if is_connected_input { if is_connected_input {
responses.push(NodeResponse::DisconnectEvent(param_id.assume_input())); let input = param_id.assume_input();
let corresp_output = graph
.connection(input)
.expect("Connection data should be valid");
responses.push(NodeResponse::DisconnectEvent {
input: param_id.assume_input(),
output: corresp_output,
});
} else { } else {
responses.push(NodeResponse::ConnectEventStarted(node_id, param_id)); responses.push(NodeResponse::ConnectEventStarted(node_id, param_id));
} }
@ -473,7 +488,13 @@ where
&& resp.hovered() && resp.hovered()
&& ui.input().pointer.any_released() && ui.input().pointer.any_released()
{ {
responses.push(NodeResponse::ConnectEventEnded(param_id)); match (param_id, origin_param) {
(AnyParameterId::Input(input), AnyParameterId::Output(output))
| (AnyParameterId::Output(output), AnyParameterId::Input(input)) => {
responses.push(NodeResponse::ConnectEventEnded { input, output });
}
_ => { /* Ignore in-in or out-out connections */ }
}
} }
} }
} }
@ -532,7 +553,7 @@ where
} }
// Draw the background shape. // Draw the background shape.
// NOTE: This code is a bit more involve than it needs to be because egui // NOTE: This code is a bit more involved than it needs to be because egui
// does not support drawing rectangles with asymmetrical round corners. // does not support drawing rectangles with asymmetrical round corners.
let (shape, outline) = { let (shape, outline) = {
@ -598,7 +619,7 @@ where
// Titlebar buttons // Titlebar buttons
if Self::close_button(ui, outer_rect).clicked() { 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( let window_response = ui.interact(

View File

@ -53,6 +53,20 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
input_id 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 { 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 { let output_id = self.outputs.insert_with_key(|output_id| OutputParam {
id: output_id, id: output_id,
@ -70,7 +84,7 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
/// after deleting this node as input-output pairs. Note that one of the two /// 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 /// ids in the pair (the one on `node_id`'s end) will be invalid after
/// calling this function. /// 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<NodeData>, Vec<(InputId, OutputId)>) {
let mut disconnect_events = vec![]; let mut disconnect_events = vec![];
self.connections.retain(|i, o| { self.connections.retain(|i, o| {
@ -90,9 +104,9 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
for output in self[node_id].output_ids().collect::<SVec<_>>() { for output in self[node_id].output_ids().collect::<SVec<_>>() {
self.outputs.remove(output); 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<OutputId> { pub fn remove_connection(&mut self, input_id: InputId) -> Option<OutputId> {
@ -123,10 +137,18 @@ impl<NodeData, DataType, ValueType> Graph<NodeData, DataType, ValueType> {
.ok_or(EguiGraphError::InvalidParameterId(param)) .ok_or(EguiGraphError::InvalidParameterId(param))
} }
pub fn try_get_input(&self, input: InputId) -> Option<&InputParam<DataType, ValueType>> {
self.inputs.get(input)
}
pub fn get_input(&self, input: InputId) -> &InputParam<DataType, ValueType> { pub fn get_input(&self, input: InputId) -> &InputParam<DataType, ValueType> {
&self.inputs[input] &self.inputs[input]
} }
pub fn try_get_output(&self, output: OutputId) -> Option<&OutputParam<DataType>> {
self.outputs.get(output)
}
pub fn get_output(&self, output: OutputId) -> &OutputParam<DataType> { pub fn get_output(&self, output: OutputId) -> &OutputParam<DataType> {
&self.outputs[output] &self.outputs[output]
} }

View File

@ -16,11 +16,38 @@ pub trait WidgetValueTrait {
/// [`Graph`]. This trait tells the library how to visually expose data types /// [`Graph`]. This trait tells the library how to visually expose data types
/// to the user. /// to the user.
pub trait DataTypeTrait<UserState>: PartialEq + Eq { pub trait DataTypeTrait<UserState>: PartialEq + Eq {
// The associated port color of this datatype /// The associated port color of this datatype
fn data_type_color(&self, user_state: &UserState) -> egui::Color32; fn data_type_color(&self, user_state: &UserState) -> egui::Color32;
// The name of this datatype /// The name of this datatype. Return type is specified as Cow<str> because
fn name(&self) -> &str; /// some implementations will need to allocate a new string to provide an
/// answer while others won't.
///
/// ## Example (borrowed value)
/// Use this when you can get the name of the datatype from its fields or as
/// a &'static str. Prefer this method when possible.
/// ```ignore
/// pub struct DataType { name: String }
///
/// impl DataTypeTrait<()> for DataType {
/// fn name(&self) -> std::borrow::Cow<str> {
/// Cow::Borrowed(&self.name)
/// }
/// }
/// ```
///
/// ## Example (owned value)
/// Use this when you can't derive the name of the datatype from its fields.
/// ```ignore
/// pub struct DataType { some_tag: i32 }
///
/// impl DataTypeTrait<()> for DataType {
/// fn name(&self) -> std::borrow::Cow<str> {
/// Cow::Owned(format!("Super amazing type #{}", self.some_tag))
/// }
/// }
/// ```
fn name(&self) -> std::borrow::Cow<str>;
} }
/// This trait must be implemented for the `NodeData` generic parameter of the /// This trait must be implemented for the `NodeData` generic parameter of the
@ -45,7 +72,7 @@ where
node_id: NodeId, node_id: NodeId,
graph: &Graph<Self, Self::DataType, Self::ValueType>, graph: &Graph<Self, Self::DataType, Self::ValueType>,
user_state: &Self::UserState, user_state: &Self::UserState,
) -> Vec<NodeResponse<Self::Response>> ) -> Vec<NodeResponse<Self::Response, Self>>
where where
Self::Response: UserResponseTrait; Self::Response: UserResponseTrait;
@ -103,4 +130,4 @@ pub trait NodeTemplateTrait: Clone {
/// The custom user response types when drawing nodes in the graph must /// The custom user response types when drawing nodes in the graph must
/// implement this trait. /// implement this trait.
pub trait UserResponseTrait: Clone + Copy + std::fmt::Debug + PartialEq + Eq {} pub trait UserResponseTrait: Clone + std::fmt::Debug {}

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::{borrow::Cow, collections::HashMap};
use eframe::egui::{self, DragValue, TextStyle}; use eframe::egui::{self, DragValue, TextStyle};
use egui_node_graph::*; use egui_node_graph::*;
@ -97,10 +97,10 @@ impl DataTypeTrait<MyGraphState> for MyDataType {
} }
} }
fn name(&self) -> &str { fn name(&self) -> Cow<'_, str> {
match self { match self {
MyDataType::Scalar => "scalar", MyDataType::Scalar => Cow::Borrowed("scalar"),
MyDataType::Vec2 => "2d vector", MyDataType::Vec2 => Cow::Borrowed("2d vector"),
} }
} }
} }
@ -294,7 +294,7 @@ impl NodeDataTrait for MyNodeData {
node_id: NodeId, node_id: NodeId,
_graph: &Graph<MyNodeData, MyDataType, MyValueType>, _graph: &Graph<MyNodeData, MyDataType, MyValueType>,
user_state: &Self::UserState, user_state: &Self::UserState,
) -> Vec<NodeResponse<MyResponse>> ) -> Vec<NodeResponse<MyResponse, MyNodeData>>
where where
MyResponse: UserResponseTrait, MyResponse: UserResponseTrait,
{ {
@ -383,7 +383,7 @@ impl eframe::App for NodeGraphExample {
Err(err) => format!("Execution error: {}", err), Err(err) => format!("Execution error: {}", err),
}; };
ctx.debug_painter().text( ctx.debug_painter().text(
egui::pos2(10.0, 10.0), egui::pos2(10.0, 35.0),
egui::Align2::LEFT_TOP, egui::Align2::LEFT_TOP,
text, text,
TextStyle::Button.resolve(&ctx.style()), TextStyle::Button.resolve(&ctx.style()),