diff --git a/egui_node_graph/src/editor_ui.rs b/egui_node_graph/src/editor_ui.rs index 14b74c8..9b0c7a0 100644 --- a/egui_node_graph/src/editor_ui.rs +++ b/egui_node_graph/src/editor_ui.rs @@ -82,6 +82,7 @@ where &mut self, ui: &mut Ui, all_kinds: impl NodeTemplateIter, + user_state: &mut UserState, ) -> 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 @@ -124,7 +125,7 @@ where .unwrap_or(false), pan: self.pan_zoom.pan + editor_rect.min.to_vec2(), } - .show(ui, &self.user_state); + .show(ui, user_state); // Actions executed later delayed_responses.extend(responses); @@ -147,7 +148,7 @@ where let new_node = self.graph.add_node( node_kind.node_graph_label(), node_kind.user_data(), - |graph, node_id| node_kind.build_node(graph, &self.user_state, node_id), + |graph, node_id| node_kind.build_node(graph, user_state, node_id), ); self.node_positions.insert( new_node, @@ -174,7 +175,7 @@ where /* Draw connections */ if let Some((_, ref locator)) = self.connection_in_progress { let port_type = self.graph.any_param_type(*locator).unwrap(); - let connection_color = port_type.data_type_color(&self.user_state); + let connection_color = port_type.data_type_color(user_state); let start_pos = port_locations[locator]; // Find a port to connect to @@ -245,7 +246,7 @@ where .graph .any_param_type(AnyParameterId::Output(output)) .unwrap(); - let connection_color = port_type.data_type_color(&self.user_state); + let connection_color = port_type.data_type_color(user_state); let src_pos = port_locations[&AnyParameterId::Output(output)]; let dst_pos = port_locations[&AnyParameterId::Input(input)]; draw_connection(ui.painter(), src_pos, dst_pos, connection_color); @@ -390,7 +391,7 @@ where pub fn show( self, ui: &mut Ui, - user_state: &UserState, + user_state: &mut 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()), @@ -406,7 +407,7 @@ where fn show_graph_node( self, ui: &mut Ui, - user_state: &UserState, + user_state: &mut UserState, ) -> Vec> { let margin = egui::vec2(15.0, 5.0); let mut responses = Vec::>::new(); @@ -501,7 +502,7 @@ where ui: &mut Ui, graph: &Graph, node_id: NodeId, - user_state: &UserState, + user_state: &mut UserState, port_pos: Pos2, responses: &mut Vec>, param_id: AnyParameterId, diff --git a/egui_node_graph/src/traits.rs b/egui_node_graph/src/traits.rs index ccf4388..8b90191 100644 --- a/egui_node_graph/src/traits.rs +++ b/egui_node_graph/src/traits.rs @@ -17,7 +17,7 @@ pub trait WidgetValueTrait { /// to the user. pub trait DataTypeTrait: PartialEq + Eq { /// The associated port color of this datatype - fn data_type_color(&self, user_state: &UserState) -> egui::Color32; + fn data_type_color(&self, user_state: &mut UserState) -> egui::Color32; /// The name of this datatype. Return type is specified as Cow because /// some implementations will need to allocate a new string to provide an @@ -71,7 +71,7 @@ where ui: &mut egui::Ui, node_id: NodeId, graph: &Graph, - user_state: &Self::UserState, + user_state: &mut Self::UserState, ) -> Vec> where Self::Response: UserResponseTrait; @@ -83,7 +83,7 @@ where _ui: &egui::Ui, _node_id: NodeId, _graph: &Graph, - _user_state: &Self::UserState, + _user_state: &mut Self::UserState, ) -> Option { None } @@ -126,7 +126,7 @@ pub trait NodeTemplateTrait: Clone { fn build_node( &self, graph: &mut Graph, - user_state: &Self::UserState, + user_state: &mut Self::UserState, node_id: NodeId, ); } diff --git a/egui_node_graph/src/ui_state.rs b/egui_node_graph/src/ui_state.rs index 8fc1a81..43ceba5 100644 --- a/egui_node_graph/src/ui_state.rs +++ b/egui_node_graph/src/ui_state.rs @@ -1,4 +1,5 @@ use super::*; +use std::marker::PhantomData; #[cfg(feature = "persistence")] use serde::{Deserialize, Serialize}; @@ -29,13 +30,13 @@ pub struct GraphEditorState>, /// The panning of the graph viewport. pub pan_zoom: PanZoom, - pub user_state: UserState, + pub _user_state: PhantomData UserState>, } impl GraphEditorState { - pub fn new(default_zoom: f32, user_state: UserState) -> Self { + pub fn new(default_zoom: f32) -> Self { Self { graph: Graph::new(), node_order: Vec::new(), @@ -47,7 +48,7 @@ impl pan: egui::Vec2::ZERO, zoom: default_zoom, }, - user_state, + _user_state: PhantomData, } } } diff --git a/egui_node_graph_example/Cargo.toml b/egui_node_graph_example/Cargo.toml index 039e233..7748d93 100644 --- a/egui_node_graph_example/Cargo.toml +++ b/egui_node_graph_example/Cargo.toml @@ -12,9 +12,11 @@ crate-type = ["cdylib", "rlib"] eframe = "0.19.0" egui_node_graph = { path = "../egui_node_graph" } anyhow = "1.0" +serde = { version = "1.0", optional = true } [features] default = [] +persistence = ["serde", "egui_node_graph/persistence", "eframe/persistence"] [profile.release] opt-level = 2 # fast and small wasm diff --git a/egui_node_graph_example/src/app.rs b/egui_node_graph_example/src/app.rs index 3e2551d..395d1aa 100644 --- a/egui_node_graph_example/src/app.rs +++ b/egui_node_graph_example/src/app.rs @@ -8,6 +8,7 @@ use egui_node_graph::*; /// The NodeData holds a custom data struct inside each node. It's useful to /// store additional information that doesn't live in parameters. For this /// example, the node data stores the template (i.e. the "type") of the node. +#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub struct MyNodeData { template: MyNodeTemplate, } @@ -16,6 +17,7 @@ pub struct MyNodeData { /// attaching two ports together. The graph UI will make sure to not allow /// attaching incompatible datatypes. #[derive(PartialEq, Eq)] +#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub enum MyDataType { Scalar, Vec2, @@ -29,6 +31,7 @@ pub enum MyDataType { /// up to the user code in this example to make sure no parameter is created /// with a DataType of Scalar and a ValueType of Vec2. #[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub enum MyValueType { Vec2 { value: egui::Vec2 }, Scalar { value: f32 }, @@ -58,6 +61,7 @@ impl MyValueType { /// will display in the "new node" popup. The user code needs to tell the /// library how to convert a NodeTemplate into a Node. #[derive(Clone, Copy)] +#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub enum MyNodeTemplate { MakeVector, MakeScalar, @@ -82,6 +86,7 @@ pub enum MyResponse { /// parameter drawing callbacks. The contents of this struct are entirely up to /// the user. For this example, we use it to keep track of the 'active' node. #[derive(Default)] +#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))] pub struct MyGraphState { pub active_node: Option, } @@ -90,7 +95,7 @@ pub struct MyGraphState { // A trait for the data types, to tell the library how to display them impl DataTypeTrait for MyDataType { - fn data_type_color(&self, _user_state: &MyGraphState) -> egui::Color32 { + fn data_type_color(&self, _user_state: &mut MyGraphState) -> egui::Color32 { match self { MyDataType::Scalar => egui::Color32::from_rgb(38, 109, 211), MyDataType::Vec2 => egui::Color32::from_rgb(238, 207, 109), @@ -138,7 +143,7 @@ impl NodeTemplateTrait for MyNodeTemplate { fn build_node( &self, graph: &mut Graph, - _user_state: &Self::UserState, + _user_state: &mut Self::UserState, node_id: NodeId, ) { // The nodes are created empty by default. This function needs to take @@ -295,7 +300,7 @@ impl NodeDataTrait for MyNodeData { ui: &mut egui::Ui, node_id: NodeId, _graph: &Graph, - user_state: &Self::UserState, + user_state: &mut Self::UserState, ) -> Vec> where MyResponse: UserResponseTrait, @@ -340,17 +345,44 @@ pub struct NodeGraphExample { // The `GraphEditorState` is the top-level object. You "register" all your // custom types by specifying it as its generic parameters. state: MyEditorState, + + user_state: MyGraphState, } impl Default for NodeGraphExample { fn default() -> Self { Self { - state: GraphEditorState::new(1.0, MyGraphState::default()), + state: GraphEditorState::new(1.0), + user_state: MyGraphState::default(), + } + } +} +#[cfg(feature = "persistence")] +const PERSISTENCE_KEY: &str = "egui_node_graph"; + +#[cfg(feature = "persistence")] +impl NodeGraphExample { + /// If the persistence feature is enabled, Called once before the first frame. + /// Load previous app state (if any). + pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + let state = cc + .storage + .and_then(|storage| eframe::get_value(storage, PERSISTENCE_KEY)) + .unwrap_or_else(|| GraphEditorState::new(0.0)); + Self { + state, + user_state: MyGraphState::default(), } } } impl eframe::App for NodeGraphExample { + #[cfg(feature = "persistence")] + /// If the persistence function is enabled, + /// Called by the frame work to save state before shutdown. + fn save(&mut self, storage: &mut dyn eframe::Storage) { + eframe::set_value(storage, PERSISTENCE_KEY, &self.state); + } /// Called each time the UI needs repainting, which may be many times per second. /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { @@ -361,7 +393,8 @@ impl eframe::App for NodeGraphExample { }); let graph_response = egui::CentralPanel::default() .show(ctx, |ui| { - self.state.draw_graph_editor(ui, AllMyNodeTemplates) + self.state + .draw_graph_editor(ui, AllMyNodeTemplates, &mut self.user_state) }) .inner; for node_response in graph_response.node_responses { @@ -370,15 +403,13 @@ impl eframe::App for NodeGraphExample { // connection is created if let NodeResponse::User(user_event) = node_response { match user_event { - MyResponse::SetActiveNode(node) => { - self.state.user_state.active_node = Some(node) - } - MyResponse::ClearActiveNode => self.state.user_state.active_node = None, + MyResponse::SetActiveNode(node) => self.user_state.active_node = Some(node), + MyResponse::ClearActiveNode => self.user_state.active_node = None, } } } - if let Some(node) = self.state.user_state.active_node { + if let Some(node) = self.user_state.active_node { if self.state.graph.nodes.contains_key(node) { let text = match evaluate_node(&self.state.graph, node, &mut HashMap::new()) { Ok(value) => format!("The result is: {:?}", value), @@ -392,7 +423,7 @@ impl eframe::App for NodeGraphExample { egui::Color32::WHITE, ); } else { - self.state.user_state.active_node = None; + self.user_state.active_node = None; } } } diff --git a/egui_node_graph_example/src/main.rs b/egui_node_graph_example/src/main.rs index 489443a..02d1528 100644 --- a/egui_node_graph_example/src/main.rs +++ b/egui_node_graph_example/src/main.rs @@ -12,6 +12,11 @@ fn main() { eframe::NativeOptions::default(), Box::new(|cc| { cc.egui_ctx.set_visuals(Visuals::dark()); + #[cfg(feature = "persistence")] + { + Box::new(egui_node_graph_example::NodeGraphExample::new(cc)) + } + #[cfg(not(feature = "persistence"))] Box::new(egui_node_graph_example::NodeGraphExample::default()) }), );