diff --git a/Cargo.lock b/Cargo.lock index 13c1bec..eaecdb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,15 +161,16 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom", "once_cell", "serde", "version_check", + "zerocopy", ] [[package]] @@ -1586,7 +1587,6 @@ dependencies = [ "eframe", "egui", "egui_graphs", - "egui_plot", "fdg-sim", "petgraph", "rand", @@ -1983,15 +1983,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "egui_plot" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f33a00fe8eb1ba56535b3dbacdecc7a1365a328908a97c5f3c81bb466be72b" -dependencies = [ - "egui", -] - [[package]] name = "emath" version = "0.23.0" @@ -5227,6 +5218,26 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "zvariant" version = "3.15.0" diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index 1b0b681..218261f 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" [dependencies] egui_graphs = { path = "../../" } -egui = "0.23" -eframe = "0.23" +egui = "0.23.0" +eframe = "0.23.0" petgraph = "0.6" diff --git a/examples/configurable/Cargo.toml b/examples/configurable/Cargo.toml index 3bc0693..22842d2 100644 --- a/examples/configurable/Cargo.toml +++ b/examples/configurable/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] egui_graphs = { path = "../../", features = ["events"] } egui = "0.23" -egui_plot = "0.23" serde_json = "1.0" eframe = "0.23" petgraph = "0.6" diff --git a/examples/configurable/src/main.rs b/examples/configurable/src/main.rs index ee1c32d..06058ba 100644 --- a/examples/configurable/src/main.rs +++ b/examples/configurable/src/main.rs @@ -27,8 +27,6 @@ pub struct ConfigurableApp { settings_navigation: SettingsNavigation, settings_style: SettingsStyle, - selected_nodes: Vec, - selected_edges: Vec, last_events: Vec, simulation_stopped: bool, @@ -62,8 +60,6 @@ impl ConfigurableApp { settings_navigation: SettingsNavigation::default(), settings_style: SettingsStyle::default(), - selected_nodes: Vec::default(), - selected_edges: Vec::default(), last_events: Vec::default(), simulation_stopped: false, @@ -127,9 +123,6 @@ impl ConfigurableApp { /// /// If node or edge is selected it is added to the corresponding selected field in `self`. fn sync_graph_with_simulation(&mut self) { - self.selected_nodes = vec![]; - self.selected_edges = vec![]; - let g_indices = self.g.g.node_indices().collect::>(); for g_n_idx in &g_indices { let g_n = self.g.g.node_weight_mut(*g_n_idx).unwrap(); @@ -137,17 +130,6 @@ impl ConfigurableApp { let loc = sim_n.location; g_n.set_location(Pos2::new(loc.x, loc.y)); - - if g_n.selected() { - self.selected_nodes.push(*g_n_idx); - } - } - - for g_e_idx in self.g.g.edge_indices() { - let g_e = self.g.g.edge_weight(g_e_idx).unwrap(); - if g_e.selected() { - self.selected_edges.push(g_e_idx); - } } // reset the weights of the edges @@ -261,7 +243,8 @@ impl ConfigurableApp { self.g.g[idx].bind(idx, location); let n = self.g.g.node_weight_mut(idx).unwrap(); - n.set_label(format!("{idx:?}")); + let idx_string = idx.index().to_string(); + n.set_label(idx_string); let mut sim_node = fdg_sim::Node::new(idx.index().to_string().as_str(), ()); sim_node.location = Vec3::new(location.x, location.y, 0.); @@ -496,10 +479,10 @@ impl ConfigurableApp { CollapsingHeader::new("Selected").default_open(true).show(ui, |ui| { ScrollArea::vertical().auto_shrink([false, true]).max_height(200.).show(ui, |ui| { - self.selected_nodes.iter().for_each(|node| { + self.g.selected_nodes().iter().for_each(|node| { ui.label(format!("{node:?}")); }); - self.selected_edges.iter().for_each(|edge| { + self.g.selected_edges().iter().for_each(|edge| { ui.label(format!("{edge:?}")); }); }); diff --git a/examples/interactive/src/main.rs b/examples/interactive/src/main.rs index 80cef49..637c4f7 100644 --- a/examples/interactive/src/main.rs +++ b/examples/interactive/src/main.rs @@ -3,13 +3,10 @@ use egui::Context; use egui_graphs::{ DefaultEdgeShape, DefaultNodeShape, Graph, GraphView, SettingsInteraction, SettingsStyle, }; -use petgraph::{ - stable_graph::{DefaultIx, StableGraph}, - Directed, -}; +use petgraph::stable_graph::StableGraph; pub struct InteractiveApp { - g: Graph<(), (), Directed, DefaultIx>, + g: Graph<(), ()>, } impl InteractiveApp { diff --git a/src/computed.rs b/src/computed.rs deleted file mode 100644 index a8e94e6..0000000 --- a/src/computed.rs +++ /dev/null @@ -1,63 +0,0 @@ -use egui::{Rect, Vec2}; -use petgraph::graph::{EdgeIndex, IndexType}; -use petgraph::stable_graph::NodeIndex; -use petgraph::EdgeType; - -use crate::{DisplayNode, Node}; - -/// The struct stores selections, dragged node and computed elements states. -#[derive(Debug, Clone)] -pub struct ComputedState { - pub dragged: Option>, - pub selected_nodes: Vec>, - pub selected_edges: Vec>, - - min: Vec2, - max: Vec2, -} - -impl Default for ComputedState -where - Ix: IndexType, -{ - fn default() -> Self { - Self { - dragged: None, - - selected_nodes: Vec::new(), - selected_edges: Vec::new(), - - min: Vec2::new(f32::MAX, f32::MAX), - max: Vec2::new(f32::MIN, f32::MIN), - } - } -} - -// TODO: take into account node and edges sizes -impl ComputedState -where - Ix: IndexType, -{ - pub fn comp_iter_bounds>( - &mut self, - n: &Node, - ) { - let loc = n.location(); - if loc.x < self.min.x { - self.min.x = loc.x; - }; - if loc.x > self.max.x { - self.max.x = loc.x; - }; - if loc.y < self.min.y { - self.min.y = loc.y; - }; - if loc.y > self.max.y { - self.max.y = loc.y; - }; - } - - pub fn graph_bounds(&self) -> Rect { - Rect::from_min_max(self.min.to_pos2(), self.max.to_pos2()) - } -} diff --git a/src/elements/edge.rs b/src/elements/edge.rs index fa87d91..b746d22 100644 --- a/src/elements/edge.rs +++ b/src/elements/edge.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use petgraph::{ stable_graph::{DefaultIx, EdgeIndex, IndexType}, - EdgeType, + Directed, EdgeType, }; use crate::{DefaultEdgeShape, DefaultNodeShape, DisplayEdge, DisplayNode}; @@ -22,7 +22,7 @@ pub struct EdgeProps { pub struct Edge< N: Clone, E: Clone, - Ty: EdgeType, + Ty: EdgeType = Directed, Ix: IndexType = DefaultIx, Dn: DisplayNode = DefaultNodeShape, D: DisplayEdge = DefaultEdgeShape, diff --git a/src/elements/node.rs b/src/elements/node.rs index 537a16f..bf2e7e7 100644 --- a/src/elements/node.rs +++ b/src/elements/node.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; use egui::Pos2; use petgraph::{ stable_graph::{DefaultIx, IndexType, NodeIndex}, - EdgeType, + EdgeType, Directed, }; use crate::{DefaultNodeShape, DisplayNode}; @@ -21,7 +21,7 @@ pub struct NodeProps { } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Node +pub struct Node where N: Clone, E: Clone, diff --git a/src/graph.rs b/src/graph.rs index 723ca03..7e526db 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -16,7 +16,8 @@ use crate::{DefaultEdgeShape, DefaultNodeShape}; type StableGraphType = StableGraph, Edge, Ty, Ix>; -/// Graph type compatible with [`super::GraphView`]. +/// Wrapper around [`petgraph::stable_graph::StableGraph`] compatible with [`super::GraphView`]. +/// It is used to store graph data and provide access to it. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone)] pub struct Graph< @@ -28,6 +29,9 @@ pub struct Graph< De: DisplayEdge = DefaultEdgeShape, > { pub g: StableGraphType, + selected_nodes: Vec>, + selected_edges: Vec>, + dragged_node: Option>, } impl> @@ -48,7 +52,12 @@ impl< > Graph { pub fn new(g: StableGraphType) -> Self { - Self { g } + Self { + g, + selected_nodes: Vec::default(), + selected_edges: Vec::default(), + dragged_node: Option::default(), + } } /// Finds node by position. Can be optimized by using a spatial index like quad-tree if needed. @@ -127,4 +136,28 @@ impl< ) -> impl Iterator, Ix>> { self.g.edges_directed(idx, dir) } + + pub fn selected_nodes(&self) -> &[NodeIndex] { + &self.selected_nodes + } + + pub fn set_selected_nodes(&mut self, nodes: Vec>) { + self.selected_nodes = nodes; + } + + pub fn selected_edges(&self) -> &[EdgeIndex] { + &self.selected_edges + } + + pub fn set_selected_edges(&mut self, edges: Vec>) { + self.selected_edges = edges; + } + + pub fn dragged_node(&self) -> Option> { + self.dragged_node + } + + pub fn set_dragged_node(&mut self, node: Option>) { + self.dragged_node = node; + } } diff --git a/src/graph_view.rs b/src/graph_view.rs index 73191a0..bb8eb9b 100644 --- a/src/graph_view.rs +++ b/src/graph_view.rs @@ -7,7 +7,6 @@ use crate::events::{ PayloadNodeMove, PayloadNodeSelect, PayloadPan, PayloadZoom, }; use crate::{ - computed::ComputedState, draw::Drawer, metadata::Metadata, settings::SettingsNavigation, @@ -81,13 +80,13 @@ where let (resp, p) = ui.allocate_painter(ui.available_size(), Sense::click_and_drag()); let mut meta = Metadata::get(ui); - let mut computed = self.compute_state(); + self.sync_state(&mut meta); - self.handle_fit_to_screen(&resp, &mut meta, &computed); - self.handle_navigation(ui, &resp, &mut meta, &computed); + self.handle_fit_to_screen(&resp, &mut meta); + self.handle_navigation(ui, &resp, &mut meta); - self.handle_node_drag(&resp, &mut computed, &mut meta); - self.handle_click(&resp, &mut meta, &computed); + self.handle_node_drag(&resp, &mut meta); + self.handle_click(&resp, &mut meta); let is_directed = self.g.is_directed(); Drawer::::new( @@ -166,40 +165,44 @@ where self } - fn compute_state(&mut self) -> ComputedState { - let mut computed = ComputedState::::default(); + fn sync_state(&mut self, meta: &mut Metadata) { + let mut selected_nodes = Vec::new(); + let mut selected_edges = Vec::new(); + let mut dragged = None; - self.g.g.node_indices().for_each(|idx| { - let n = self.g.node(idx).unwrap(); + self.g.nodes_iter().for_each(|(idx, n)| { if n.dragged() { - computed.dragged = Some(idx); + dragged = Some(idx); } if n.selected() { - computed.selected_nodes.push(idx); + selected_nodes.push(idx); } - computed.comp_iter_bounds(n); + + meta.comp_iter_bounds(n); }); self.g.edges_iter().for_each(|(idx, e)| { if e.selected() { - computed.selected_edges.push(idx); + selected_edges.push(idx); } }); - computed + self.g.set_selected_nodes(selected_nodes); + self.g.set_selected_edges(selected_edges); + self.g.set_dragged_node(dragged); } /// Fits the graph to the screen if it is the first frame or /// fit to screen setting is enabled; - fn handle_fit_to_screen(&self, r: &Response, meta: &mut Metadata, comp: &ComputedState) { + fn handle_fit_to_screen(&self, r: &Response, meta: &mut Metadata) { if !meta.first_frame && !self.settings_navigation.fit_to_screen_enabled { return; } - self.fit_to_screen(&r.rect, meta, comp); + self.fit_to_screen(&r.rect, meta); } - fn handle_click(&mut self, resp: &Response, meta: &mut Metadata, comp: &ComputedState) { + fn handle_click(&mut self, resp: &Response, meta: &mut Metadata) { if !resp.clicked() && !resp.double_clicked() { return; } @@ -222,13 +225,13 @@ where let nodes_selectable = self.settings_interaction.node_selection_enabled || self.settings_interaction.node_selection_multi_enabled; if nodes_selectable { - self.deselect_all_nodes(comp); + self.deselect_all_nodes(); } let edges_selectable = self.settings_interaction.edge_selection_enabled || self.settings_interaction.edge_selection_multi_enabled; if edges_selectable { - self.deselect_all_edges(comp); + self.deselect_all_edges(); } return; } @@ -241,12 +244,12 @@ where self.handle_node_double_click(idx); return; } - self.handle_node_click(idx, comp); + self.handle_node_click(idx); return; } if let Some(edge_idx) = found_edge { - self.handle_edge_click(edge_idx, comp); + self.handle_edge_click(edge_idx); } } @@ -260,7 +263,7 @@ where } } - fn handle_node_click(&mut self, idx: NodeIndex, comp: &ComputedState) { + fn handle_node_click(&mut self, idx: NodeIndex) { if !self.settings_interaction.node_clicking_enabled && !self.settings_interaction.node_selection_enabled { @@ -282,13 +285,13 @@ where } if !self.settings_interaction.node_selection_multi_enabled { - self.deselect_all(comp); + self.deselect_all(); } self.select_node(idx); } - fn handle_edge_click(&mut self, idx: EdgeIndex, comp: &ComputedState) { + fn handle_edge_click(&mut self, idx: EdgeIndex) { if !self.settings_interaction.edge_clicking_enabled && !self.settings_interaction.edge_selection_enabled { @@ -310,18 +313,13 @@ where } if !self.settings_interaction.edge_selection_multi_enabled { - self.deselect_all(comp); + self.deselect_all(); } self.select_edge(idx); } - fn handle_node_drag( - &mut self, - resp: &Response, - comp: &mut ComputedState, - meta: &mut Metadata, - ) { + fn handle_node_drag(&mut self, resp: &Response, meta: &mut Metadata) { if !self.settings_interaction.dragging_enabled { return; } @@ -333,23 +331,23 @@ where } if resp.dragged() - && comp.dragged.is_some() + && self.g.dragged_node().is_some() && (resp.drag_delta().x.abs() > 0. || resp.drag_delta().y.abs() > 0.) { - let n_idx_dragged = comp.dragged.unwrap(); + let n_idx_dragged = self.g.dragged_node().unwrap(); let delta_in_graph_coords = resp.drag_delta() / meta.zoom; self.move_node(n_idx_dragged, delta_in_graph_coords); } - if resp.drag_released() && comp.dragged.is_some() { - let n_idx = comp.dragged.unwrap(); + if resp.drag_released() && self.g.dragged_node().is_some() { + let n_idx = self.g.dragged_node().unwrap(); self.set_drag_end(n_idx); } } - fn fit_to_screen(&self, rect: &Rect, meta: &mut Metadata, comp: &ComputedState) { + fn fit_to_screen(&self, rect: &Rect, meta: &mut Metadata) { // calculate graph dimensions with decorative padding - let bounds = comp.graph_bounds(); + let bounds = meta.graph_bounds(); let mut diag = bounds.max - bounds.min; // if the graph is empty or consists from one node, use a default size @@ -383,20 +381,14 @@ where self.set_pan(new_pan, meta); } - fn handle_navigation( - &self, - ui: &Ui, - resp: &Response, - meta: &mut Metadata, - comp: &ComputedState, - ) { + fn handle_navigation(&self, ui: &Ui, resp: &Response, meta: &mut Metadata) { if !meta.first_frame { - meta.pan += resp.rect.left_top() - meta.left_top; + meta.pan += resp.rect.left_top() - meta.top_left; } - meta.left_top = resp.rect.left_top(); + meta.top_left = resp.rect.left_top(); self.handle_zoom(ui, resp, meta); - self.handle_pan(resp, meta, comp); + self.handle_pan(resp, meta); } fn handle_zoom(&self, ui: &Ui, resp: &Response, meta: &mut Metadata) { @@ -415,13 +407,13 @@ where }); } - fn handle_pan(&self, resp: &Response, meta: &mut Metadata, comp: &ComputedState) { + fn handle_pan(&self, resp: &Response, meta: &mut Metadata) { if !self.settings_navigation.zoom_and_pan_enabled { return; } if resp.dragged() - && comp.dragged.is_none() + && self.g.dragged_node().is_none() && (resp.drag_delta().x.abs() > 0. || resp.drag_delta().y.abs() > 0.) { let new_pan = meta.pan + resp.drag_delta(); @@ -496,20 +488,22 @@ where } /// Deselects all nodes AND edges. - fn deselect_all(&mut self, comp: &ComputedState) { - self.deselect_all_nodes(comp); - self.deselect_all_edges(comp); + fn deselect_all(&mut self) { + self.deselect_all_nodes(); + self.deselect_all_edges(); } - fn deselect_all_nodes(&mut self, comp: &ComputedState) { - comp.selected_nodes.iter().for_each(|idx| { - self.deselect_node(*idx); + fn deselect_all_nodes(&mut self) { + let selected_nodes = self.g.selected_nodes().to_vec(); + selected_nodes.into_iter().for_each(|idx| { + self.deselect_node(idx); }); } - fn deselect_all_edges(&mut self, comp: &ComputedState) { - comp.selected_edges.iter().for_each(|idx| { - self.deselect_edge(*idx); + fn deselect_all_edges(&mut self) { + let selected_edges = self.g.selected_edges().to_vec(); + selected_edges.into_iter().for_each(|idx| { + self.deselect_edge(idx); }); } diff --git a/src/lib.rs b/src/lib.rs index a582323..5acc8ec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -mod computed; mod draw; mod elements; mod graph; diff --git a/src/metadata.rs b/src/metadata.rs index 302f4d3..6a1c37a 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -1,9 +1,53 @@ -use egui::{Id, Pos2, Vec2}; +use egui::{Id, Pos2, Rect, Vec2}; +use petgraph::{stable_graph::IndexType, EdgeType}; -#[cfg(feature = "egui_persistence")] -use serde::{Deserialize, Serialize}; +use crate::{DisplayNode, Node}; +#[derive(Clone, Debug)] +struct BoundsIterator { + min: Vec2, + max: Vec2, +} + +impl Default for BoundsIterator { + fn default() -> Self { + Self { + min: Vec2::new(f32::MAX, f32::MAX), + max: Vec2::new(f32::MIN, f32::MIN), + } + } +} -#[cfg_attr(feature = "egui_persistence", derive(Serialize, Deserialize))] +impl BoundsIterator { + pub fn comp_iter< + N: Clone, + E: Clone, + Ty: EdgeType, + Ix: IndexType, + D: DisplayNode, + >( + &mut self, + n: &Node, + ) { + let loc = n.location(); + if loc.x < self.min.x { + self.min.x = loc.x; + }; + if loc.x > self.max.x { + self.max.x = loc.x; + }; + if loc.y < self.min.y { + self.min.y = loc.y; + }; + if loc.y > self.max.y { + self.max.y = loc.y; + }; + } +} + +#[cfg_attr( + feature = "egui_persistence", + derive(serde::Serialize, serde::Deserialize) +)] #[derive(Clone, Debug)] pub struct Metadata { /// Whether the frame is the first one @@ -13,7 +57,10 @@ pub struct Metadata { /// Current pan offset pub pan: Vec2, /// Top left position of widget - pub left_top: Pos2, + pub top_left: Pos2, + + /// State of bounds iteration + bounds_iterator: BoundsIterator, } impl Default for Metadata { @@ -22,7 +69,8 @@ impl Default for Metadata { first_frame: true, zoom: 1., pan: Default::default(), - left_top: Default::default(), + top_left: Default::default(), + bounds_iterator: Default::default(), } } } @@ -52,4 +100,25 @@ impl Metadata { pub fn screen_to_canvas_pos(&self, pos: Pos2) -> Pos2 { ((pos.to_vec2() - self.pan) / self.zoom).to_pos2() } + + pub fn comp_iter_bounds< + N: Clone, + E: Clone, + Ty: EdgeType, + Ix: IndexType, + D: DisplayNode, + >( + &mut self, + n: &Node, + ) { + self.bounds_iterator.comp_iter(n); + } + + /// Returns bounding rect of the graph. + pub fn graph_bounds(&self) -> Rect { + Rect::from_min_max( + self.bounds_iterator.min.to_pos2(), + self.bounds_iterator.max.to_pos2(), + ) + } }