diff --git a/egui_node_graph/src/editor_ui.rs b/egui_node_graph/src/editor_ui.rs index e168c34..56ff218 100644 --- a/egui_node_graph/src/editor_ui.rs +++ b/egui_node_graph/src/editor_ui.rs @@ -76,7 +76,8 @@ where ValueType = ValueType, >, UserResponse: UserResponseTrait, - ValueType: WidgetValueTrait, + ValueType: + WidgetValueTrait, NodeTemplate: NodeTemplateTrait< NodeData = NodeData, DataType = DataType, @@ -152,10 +153,10 @@ where node_finder_area = node_finder_area.current_pos(pos); } node_finder_area.show(ui.ctx(), |ui| { - if let Some(node_kind) = node_finder.show(ui, all_kinds) { + if let Some(node_kind) = node_finder.show(ui, all_kinds, user_state) { let new_node = self.graph.add_node( - node_kind.node_graph_label(), - node_kind.user_data(), + node_kind.node_graph_label(user_state), + node_kind.user_data(user_state), |graph, node_id| node_kind.build_node(graph, user_state, node_id), ); self.node_positions.insert( @@ -391,7 +392,8 @@ where ValueType = ValueType, >, UserResponse: UserResponseTrait, - ValueType: WidgetValueTrait, + ValueType: + WidgetValueTrait, DataType: DataTypeTrait, { pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0]; @@ -469,13 +471,23 @@ where if self.graph.connection(param_id).is_some() { ui.label(param_name); } else { - responses.extend( - self.graph[param_id] - .value - .value_widget(¶m_name, ui) - .into_iter() - .map(NodeResponse::User), + // NOTE: We want to pass the `user_data` to + // `value_widget`, but we can't since that would require + // borrowing the graph twice. Here, we make the + // assumption that the value is cheaply replaced, and + // use `std::mem::take` to temporarily replace it with a + // dummy value. This requires `ValueType` to implement + // Default, but results in a totally safe alternative. + let mut value = std::mem::take(&mut self.graph[param_id].value); + let node_responses = value.value_widget( + ¶m_name, + self.node_id, + ui, + user_state, + &self.graph[self.node_id].user_data, ); + self.graph[param_id].value = value; + responses.extend(node_responses.into_iter().map(NodeResponse::User)); } let height_after = ui.min_rect().bottom(); input_port_heights.push((height_before + height_after) / 2.0); diff --git a/egui_node_graph/src/node_finder.rs b/egui_node_graph/src/node_finder.rs index e445346..6bb77d7 100644 --- a/egui_node_graph/src/node_finder.rs +++ b/egui_node_graph/src/node_finder.rs @@ -14,9 +14,9 @@ pub struct NodeFinder { _phantom: PhantomData, } -impl NodeFinder +impl NodeFinder where - NodeTemplate: NodeTemplateTrait, + NodeTemplate: NodeTemplateTrait, { pub fn new_at(pos: Pos2) -> Self { NodeFinder { @@ -34,6 +34,7 @@ where &mut self, ui: &mut Ui, all_kinds: impl NodeTemplateIter, + user_state: &mut UserState, ) -> Option { let background_color; let text_color; @@ -68,7 +69,7 @@ where .inner_margin(vec2(10.0, 10.0)) .show(ui, |ui| { for kind in all_kinds.all_kinds() { - let kind_name = kind.node_finder_label().to_string(); + let kind_name = kind.node_finder_label(user_state).to_string(); if kind_name .to_lowercase() .contains(self.query.to_lowercase().as_str()) diff --git a/egui_node_graph/src/traits.rs b/egui_node_graph/src/traits.rs index 8b90191..6f02320 100644 --- a/egui_node_graph/src/traits.rs +++ b/egui_node_graph/src/traits.rs @@ -3,13 +3,28 @@ use super::*; /// This trait must be implemented by the `ValueType` generic parameter of the /// [`Graph`]. The trait allows drawing custom inline widgets for the different /// types of the node graph. -pub trait WidgetValueTrait { +/// +/// The [`Default`] trait bound is required to circumvent borrow checker issues +/// using `std::mem::take` Otherwise, it would be impossible to pass the +/// `node_data` parameter during `value_widget`. The default value is never +/// used, so the implementation is not important, but it should be reasonably +/// cheap to construct. +pub trait WidgetValueTrait: Default { type Response; + type UserState; + type NodeData; /// This method will be called for each input parameter with a widget. The /// return value is a vector of custom response objects which can be used /// to implement handling of side effects. If unsure, the response Vec can /// be empty. - fn value_widget(&mut self, param_name: &str, ui: &mut egui::Ui) -> Vec; + fn value_widget( + &mut self, + param_name: &str, + node_id: NodeId, + ui: &mut egui::Ui, + user_state: &mut Self::UserState, + node_data: &Self::NodeData, + ) -> Vec; } /// This trait must be implemented by the `DataType` generic parameter of the @@ -112,13 +127,17 @@ pub trait NodeTemplateTrait: Clone { type UserState; /// Returns a descriptive name for the node kind, used in the node finder. - fn node_finder_label(&self) -> &str; + /// + /// The return type is Cow to allow returning owned or borrowed values + /// more flexibly. Refer to the documentation for `DataTypeTrait::name` for + /// more information + fn node_finder_label(&self, user_state: &mut Self::UserState) -> std::borrow::Cow; /// Returns a descriptive name for the node kind, used in the graph. - fn node_graph_label(&self) -> String; + fn node_graph_label(&self, user_state: &mut Self::UserState) -> String; /// Returns the user data for this node kind. - fn user_data(&self) -> Self::NodeData; + fn user_data(&self, user_state: &mut Self::UserState) -> Self::NodeData; /// This function is run when this node kind gets added to the graph. The /// node will be empty by default, and this function can be used to fill its diff --git a/egui_node_graph_example/src/app.rs b/egui_node_graph_example/src/app.rs index 0bad5c9..5ad68ca 100644 --- a/egui_node_graph_example/src/app.rs +++ b/egui_node_graph_example/src/app.rs @@ -37,6 +37,14 @@ pub enum MyValueType { Scalar { value: f32 }, } +impl Default for MyValueType { + fn default() -> Self { + // NOTE: This is just a dummy `Default` implementation. The library + // requires it to circumvent some internal borrow checker issues. + Self::Scalar { value: 0.0 } + } +} + impl MyValueType { /// Tries to downcast this value type to a vector pub fn try_to_vec2(self) -> anyhow::Result { @@ -118,8 +126,8 @@ impl NodeTemplateTrait for MyNodeTemplate { type ValueType = MyValueType; type UserState = MyGraphState; - fn node_finder_label(&self) -> &str { - match self { + fn node_finder_label(&self, _user_state: &mut Self::UserState) -> Cow<'_, str> { + Cow::Borrowed(match self { MyNodeTemplate::MakeVector => "New vector", MyNodeTemplate::MakeScalar => "New scalar", MyNodeTemplate::AddScalar => "Scalar add", @@ -127,16 +135,16 @@ impl NodeTemplateTrait for MyNodeTemplate { MyNodeTemplate::AddVector => "Vector add", MyNodeTemplate::SubtractVector => "Vector subtract", MyNodeTemplate::VectorTimesScalar => "Vector times scalar", - } + }) } - fn node_graph_label(&self) -> String { + fn node_graph_label(&self, user_state: &mut Self::UserState) -> String { // It's okay to delegate this to node_finder_label if you don't want to // show different names in the node finder and the node itself. - self.node_finder_label().into() + self.node_finder_label(user_state).into() } - fn user_data(&self) -> Self::NodeData { + fn user_data(&self, _user_state: &mut Self::UserState) -> Self::NodeData { MyNodeData { template: *self } } @@ -258,7 +266,16 @@ impl NodeTemplateIter for AllMyNodeTemplates { impl WidgetValueTrait for MyValueType { type Response = MyResponse; - fn value_widget(&mut self, param_name: &str, ui: &mut egui::Ui) -> Vec { + type UserState = MyGraphState; + type NodeData = MyNodeData; + fn value_widget( + &mut self, + param_name: &str, + _node_id: NodeId, + ui: &mut egui::Ui, + _user_state: &mut MyGraphState, + _node_data: &MyNodeData, + ) -> Vec { // This trait is used to tell the library which UI to display for the // inline parameter widgets. match self {