diff --git a/Cargo.lock b/Cargo.lock index 67130fa..01176ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,7 @@ dependencies = [ name = "animated_nodes" version = "0.1.0" dependencies = [ + "anyhow", "eframe", "egui 0.28.1", "egui_graphs 0.21.1", @@ -263,6 +264,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + [[package]] name = "approx" version = "0.5.1" @@ -2213,6 +2220,7 @@ dependencies = [ name = "egui_graphs" version = "0.21.1" dependencies = [ + "anyhow", "crossbeam", "egui 0.28.1", "petgraph", diff --git a/Cargo.toml b/Cargo.toml index deffe19..a892971 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ petgraph = { version = "0.6", default-features = false, features = [ crossbeam = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } +anyhow = "1.0.89" [features] egui_persistence = ["dep:serde"] @@ -37,7 +38,7 @@ pedantic = { level = "deny", priority = 0 } enum_glob_use = { level = "deny", priority = 1 } perf = { level = "deny", priority = 2 } style = { level = "deny", priority = 3 } -# unwrap_used = { level = "deny", priority = 4 } These should enabled in the future +unwrap_used = { level = "deny", priority = 4 } # expect_used = { level = "deny", priority = 5 } module_name_repetitions = { level = "allow", priority = 6 } cast_precision_loss = { level = "allow", priority = 7 } diff --git a/examples/animated_nodes/Cargo.toml b/examples/animated_nodes/Cargo.toml index f45f4c3..8a4a77c 100644 --- a/examples/animated_nodes/Cargo.toml +++ b/examples/animated_nodes/Cargo.toml @@ -6,6 +6,7 @@ license = "MIT" edition = "2021" [dependencies] +anyhow = "1.0.89" egui_graphs = { path = "../../" } egui = "0.28" eframe = "0.28" diff --git a/examples/animated_nodes/src/main.rs b/examples/animated_nodes/src/main.rs index 538b2c6..344c19e 100644 --- a/examples/animated_nodes/src/main.rs +++ b/examples/animated_nodes/src/main.rs @@ -1,5 +1,5 @@ +use anyhow::Context; use eframe::{run_native, App, CreationContext}; -use egui::Context; use egui_graphs::{ default_edge_transform, default_node_transform, to_graph_custom, DefaultEdgeShape, Graph, GraphView, SettingsInteraction, SettingsNavigation, @@ -33,13 +33,14 @@ impl AnimatedNodesApp { } }, default_edge_transform, - ), + ) + .expect("Failed to convert graph"), } } } impl App for AnimatedNodesApp { - fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.add( &mut GraphView::<_, _, _, _, NodeShapeAnimated, DefaultEdgeShape>::new(&mut self.g) @@ -73,12 +74,13 @@ fn generate_graph() -> StableGraph { g } -fn main() { +fn main() -> anyhow::Result<()> { let native_options = eframe::NativeOptions::default(); run_native( "egui_graphs_animated_nodes_demo", native_options, Box::new(|cc| Ok(Box::new(AnimatedNodesApp::new(cc)))), ) - .unwrap(); + .map_err(|e| anyhow::anyhow!(e.to_string())) + .context("Failed to run native") } diff --git a/examples/demo/src/main.rs b/examples/demo/src/main.rs index 73ee499..4c9cf67 100644 --- a/examples/demo/src/main.rs +++ b/examples/demo/src/main.rs @@ -184,7 +184,10 @@ impl DemoApp { random_n.location().y + 10. + rng.gen_range(0. ..50.), ); - let g_idx = self.g.add_node_with_location((), location); + let g_idx = self + .g + .add_node_with_location((), location) + .expect("Could not add node"); let sim_node = egui_graphs::Node::new(()); let sim_node_loc = fdg::nalgebra::Point2::new(location.x, location.y); @@ -195,7 +198,7 @@ impl DemoApp { } fn remove_node(&mut self, idx: NodeIndex) { - self.g.remove_node(idx); + let _ = self.g.remove_node(idx); self.sim.remove_node(idx).unwrap(); @@ -211,7 +214,7 @@ impl DemoApp { } fn add_edge(&mut self, start: NodeIndex, end: NodeIndex) { - self.g.add_edge(start, end, ()); + let _ = self.g.add_edge(start, end, ()); self.sim.add_edge(start, end, egui_graphs::Edge::new(())); } @@ -541,7 +544,7 @@ fn generate_random_graph(node_count: usize, edge_count: usize) -> Graph { graph.add_edge(NodeIndex::new(source), NodeIndex::new(target), ()); } - to_graph(&graph) + to_graph(&graph).expect("Could not convert graph") } fn init_force(settings: &settings::SettingsSimulation) -> FruchtermanReingold { diff --git a/src/draw/displays_default/edge.rs b/src/draw/displays_default/edge.rs index 65ee1c9..52b7f14 100644 --- a/src/draw/displays_default/edge.rs +++ b/src/draw/displays_default/edge.rs @@ -89,9 +89,13 @@ impl EdgeShapeBuilder<'a> { let tip_start_2 = end - arrow_tip_dir_2; // replace end of an edge with start of tip - *points_line.get_mut(1).unwrap() = end - tip_props.size * tip_dir; + *points_line + .get_mut(1) + .expect("Could not get end of the edge") = end - tip_props.size * tip_dir; vec![end, tip_start_1, tip_start_2] } @@ -223,7 +225,9 @@ impl<'a> EdgeShapeBuilder<'a> { let tip_start_2 = end - arrow_tip_dir_2; // replace end of an edge with start of tip - *points_curve.get_mut(3).unwrap() = end - tip_props.size * tip_dir; + *points_curve + .get_mut(3) + .expect("Could not get end of the edge") = end - tip_props.size * tip_dir; vec![end, tip_start_1, tip_start_2] } diff --git a/src/draw/drawer.rs b/src/draw/drawer.rs index 18e028b..76171c8 100644 --- a/src/draw/drawer.rs +++ b/src/draw/drawer.rs @@ -1,6 +1,7 @@ use std::marker::PhantomData; -use egui::{Context, Painter, Shape}; +use anyhow::Context; +use egui::{Painter, Shape}; use petgraph::graph::IndexType; use petgraph::EdgeType; @@ -10,7 +11,7 @@ use super::{DisplayEdge, DisplayNode}; /// Contains all the data about current widget state which is needed for custom drawing functions. pub struct DrawContext<'a> { - pub ctx: &'a Context, + pub ctx: &'a egui::Context, pub painter: &'a Painter, pub style: &'a SettingsStyle, pub is_directed: bool, @@ -50,10 +51,12 @@ where } } - pub fn draw(mut self) { - self.draw_edges(); - self.draw_nodes(); + pub fn draw(mut self) -> anyhow::Result<()> { + self.draw_edges()?; + self.draw_nodes()?; self.draw_postponed(); + + Ok(()) } fn draw_postponed(&mut self) { @@ -62,14 +65,14 @@ where }); } - fn draw_nodes(&mut self) { + fn draw_nodes(&mut self) -> anyhow::Result<()> { self.g .g .node_indices() .collect::>() .into_iter() - .for_each(|idx| { - let n = self.g.node_mut(idx).unwrap(); + .try_for_each(|idx| -> anyhow::Result<()> { + let n = self.g.node_mut(idx)?; let props = n.props().clone(); let display = n.display_mut(); @@ -85,23 +88,33 @@ where self.ctx.painter.add(s); } } - }); + Ok(()) + })?; + Ok(()) } - fn draw_edges(&mut self) { + fn draw_edges(&mut self) -> anyhow::Result<()> { self.g .g .edge_indices() .collect::>() .into_iter() - .for_each(|idx| { - let (idx_start, idx_end) = self.g.edge_endpoints(idx).unwrap(); + .try_for_each(|idx| -> anyhow::Result<()> { + let (idx_start, idx_end) = self.g.edge_endpoints(idx)?; // FIXME: not a good decision to clone nodes for every edge - let start = self.g.node(idx_start).cloned().unwrap(); - let end = self.g.node(idx_end).cloned().unwrap(); - - let e = self.g.edge_mut(idx).unwrap(); + let start = self + .g + .node(idx_start) + .cloned() + .context("Start node not found")?; + let end = self + .g + .node(idx_end) + .cloned() + .context("End node not found")?; + + let e = self.g.edge_mut(idx).context("Edge not found")?; let props = e.props().clone(); let display = e.display_mut(); @@ -117,6 +130,9 @@ where self.ctx.painter.add(s); } } - }); + + Ok(()) + })?; + Ok(()) } } diff --git a/src/elements/edge.rs b/src/elements/edge.rs index f86a45f..ad06266 100644 --- a/src/elements/edge.rs +++ b/src/elements/edge.rs @@ -84,7 +84,7 @@ impl< #[allow(clippy::missing_panics_doc)] // TODO: Add panic message pub fn id(&self) -> EdgeIndex { - self.id.unwrap() + self.id.expect("Edge is not bound to the graph") } pub fn order(&self) -> usize { diff --git a/src/elements/node.rs b/src/elements/node.rs index ef5887c..f105d33 100644 --- a/src/elements/node.rs +++ b/src/elements/node.rs @@ -123,7 +123,7 @@ where #[allow(clippy::missing_panics_doc)] // TODO: Add panic message pub fn id(&self) -> NodeIndex { - self.id.unwrap() + self.id.expect("Node is not bound to the graph") } pub fn payload(&self) -> &N { diff --git a/src/graph.rs b/src/graph.rs index 8686e7c..fd40225 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use egui::Pos2; use petgraph::stable_graph::DefaultIx; use petgraph::Directed; @@ -44,7 +45,7 @@ impl< > From<&StableGraph> for Graph { fn from(g: &StableGraph) -> Self { - transform::to_graph(g) + transform::to_graph(g).expect("Could not convert graph") } } @@ -86,8 +87,12 @@ impl< let Some((idx_start, idx_end)) = self.g.edge_endpoints(e.id()) else { continue; }; - let start = self.g.node_weight(idx_start).unwrap(); - let end = self.g.node_weight(idx_end).unwrap(); + let Some(start) = self.g.node_weight(idx_start) else { + continue; + }; + let Some(end) = self.g.node_weight(idx_end) else { + continue; + }; if e.display().is_inside(start, end, pos_in_graph) { return Some(idx); } @@ -101,127 +106,144 @@ impl< } /// Adds node to graph setting default location and default label values - #[allow(clippy::missing_panics_doc)] // TODO: add panics doc - pub fn add_node(&mut self, payload: N) -> NodeIndex { + #[allow(clippy::missing_errors_doc)] // TODO: add errors doc + pub fn add_node(&mut self, payload: N) -> anyhow::Result> { let node = Node::new(payload); let idx = self.g.add_node(node); - let graph_node = self.g.node_weight_mut(idx).unwrap(); + let graph_node = self.g.node_weight_mut(idx).context("Node not found")?; graph_node.bind(idx, Pos2::default()); graph_node.set_label(idx.index().to_string()); - idx + Ok(idx) } /// Adds node to graph setting custom location and default label value - #[allow(clippy::missing_panics_doc)] // TODO: add panics doc - pub fn add_node_with_location(&mut self, payload: N, location: Pos2) -> NodeIndex { + #[allow(clippy::missing_errors_doc)] // TODO: add errors doc + pub fn add_node_with_location( + &mut self, + payload: N, + location: Pos2, + ) -> anyhow::Result> { let node = Node::new(payload); let idx = self.g.add_node(node); - let graph_node = self.g.node_weight_mut(idx).unwrap(); + let graph_node = self.g.node_weight_mut(idx).context("Node not found")?; graph_node.bind(idx, location); graph_node.set_label(format!("node {}", idx.index())); - idx + Ok(idx) } /// Adds node to graph setting default location and custom label value - pub fn add_node_with_label(&mut self, payload: N, label: String) -> NodeIndex { + #[allow(clippy::missing_errors_doc)] // TODO: add errors doc + pub fn add_node_with_label( + &mut self, + payload: N, + label: String, + ) -> anyhow::Result> { self.add_node_with_label_and_location(payload, label, Pos2::default()) } /// Adds node to graph setting custom location and custom label value - #[allow(clippy::missing_panics_doc)] // TODO: add panics doc + #[allow(clippy::missing_errors_doc)] // TODO: add errors doc pub fn add_node_with_label_and_location( &mut self, payload: N, label: String, location: Pos2, - ) -> NodeIndex { + ) -> anyhow::Result> { let node = Node::new(payload); let idx = self.g.add_node(node); - let graph_node = self.g.node_weight_mut(idx).unwrap(); + let graph_node = self.g.node_weight_mut(idx).context("Node not found")?; graph_node.bind(idx, location); graph_node.set_label(label); - idx + Ok(idx) } /// Removes node by index. Returns removed node and None if it does not exist. - pub fn remove_node(&mut self, idx: NodeIndex) -> Option> { + #[allow(clippy::missing_errors_doc, clippy::type_complexity)] // TODO: add errors doc + pub fn remove_node( + &mut self, + idx: NodeIndex, + ) -> anyhow::Result>> { // before removing nodes we need to remove all edges connected to it let neighbors = self.g.neighbors_undirected(idx).collect::>(); for n in &neighbors { - self.remove_edges_between(idx, *n); - self.remove_edges_between(*n, idx); + self.remove_edges_between(idx, *n)?; + self.remove_edges_between(*n, idx)?; } - self.g.remove_node(idx) + Ok(self.g.remove_node(idx)) } /// Removes all edges between start and end node. Returns removed edges count. - #[allow(clippy::missing_panics_doc)] // TODO: add panics doc - pub fn remove_edges_between(&mut self, start: NodeIndex, end: NodeIndex) -> usize { + #[allow(clippy::missing_errors_doc)] // TODO: add errors doc + pub fn remove_edges_between( + &mut self, + start: NodeIndex, + end: NodeIndex, + ) -> anyhow::Result { let idxs = self .g .edges_connecting(start, end) .map(|e| e.id()) .collect::>(); if idxs.is_empty() { - return 0; + return Ok(0); } let mut removed = 0; for e in &idxs { - self.g.remove_edge(*e).unwrap(); + self.g.remove_edge(*e).context("Edge not found")?; removed += 1; } - removed + Ok(removed) } /// Adds edge between start and end node with default label. - #[allow(clippy::missing_panics_doc)] // TODO: add panics doc + #[allow(clippy::missing_errors_doc)] // TODO: add error doc pub fn add_edge( &mut self, start: NodeIndex, end: NodeIndex, payload: E, - ) -> EdgeIndex { + ) -> anyhow::Result> { let order = self.g.edges_connecting(start, end).count(); let idx = self.g.add_edge(start, end, Edge::new(payload)); - let e = self.g.edge_weight_mut(idx).unwrap(); + let e = self.g.edge_weight_mut(idx).context("Could not get Edge")?; e.bind(idx, order); e.set_label(format!("edge {}", e.id().index())); - idx + Ok(idx) } /// Adds edge between start and end node with custom label setting correct order. - #[allow(clippy::missing_panics_doc)] // TODO: add panics doc + #[allow(clippy::missing_errors_doc)] // TODO: add error doc pub fn add_edge_with_label( &mut self, start: NodeIndex, end: NodeIndex, payload: E, label: String, - ) -> EdgeIndex { + ) -> anyhow::Result> { let order = self.g.edges_connecting(start, end).count(); let idx = self.g.add_edge(start, end, Edge::new(payload)); - let e = self.g.edge_weight_mut(idx).unwrap(); + let e = self.g.edge_weight_mut(idx).context("Could not get Edge")?; e.bind(idx, order); e.set_label(label); - idx + Ok(idx) } /// Removes edge by index and updates order of the siblings. @@ -279,12 +301,17 @@ impl< self.g.edge_weight(i) } - pub fn edge_endpoints(&self, i: EdgeIndex) -> Option<(NodeIndex, NodeIndex)> { - self.g.edge_endpoints(i) + #[allow(clippy::missing_errors_doc)] // TODO: add errors doc + pub fn edge_endpoints( + &self, + i: EdgeIndex, + ) -> anyhow::Result<(NodeIndex, NodeIndex)> { + self.g.edge_endpoints(i).context("Edge not found") } - pub fn node_mut(&mut self, i: NodeIndex) -> Option<&mut Node> { - self.g.node_weight_mut(i) + #[allow(clippy::missing_errors_doc)] // TODO: add errors doc + pub fn node_mut(&mut self, i: NodeIndex) -> anyhow::Result<&mut Node> { + self.g.node_weight_mut(i).context("Node not found") } pub fn edge_mut(&mut self, i: EdgeIndex) -> Option<&mut Edge> { diff --git a/src/graph_view.rs b/src/graph_view.rs index 53faa28..7e5e607 100644 --- a/src/graph_view.rs +++ b/src/graph_view.rs @@ -17,6 +17,7 @@ use crate::{ draw::{DefaultEdgeShape, DefaultNodeShape, DrawContext}, DisplayEdge, DisplayNode, }; +use anyhow::Context; #[cfg(feature = "events")] use crossbeam::channel::Sender; use egui::{PointerButton, Pos2, Rect, Response, Sense, Ui, Vec2, Widget}; @@ -76,6 +77,7 @@ where Nd: DisplayNode, Ed: DisplayEdge, { + #[allow(unused_must_use)] // TODO: handle errors fn ui(self, ui: &mut Ui) -> Response { let (resp, p) = ui.allocate_painter(ui.available_size(), Sense::click_and_drag()); @@ -203,9 +205,9 @@ where self.fit_to_screen(&r.rect, meta); } - fn handle_click(&mut self, resp: &Response, meta: &mut Metadata) { + fn handle_click(&mut self, resp: &Response, meta: &mut Metadata) -> anyhow::Result<()> { if !resp.clicked() && !resp.double_clicked() { - return; + return Ok(()); } let clickable = self.settings_interaction.node_clicking_enabled @@ -216,11 +218,11 @@ where || self.settings_interaction.edge_selection_multi_enabled; if !(clickable) { - return; + return Ok(()); } let Some(cursor_pos) = resp.hover_pos() else { - return; + return Ok(()); }; let found_edge = self.g.edge_by_screen_pos(meta, cursor_pos); let found_node = self.g.node_by_screen_pos(meta, cursor_pos); @@ -229,15 +231,15 @@ where let nodes_selectable = self.settings_interaction.node_selection_enabled || self.settings_interaction.node_selection_multi_enabled; if nodes_selectable { - self.deselect_all_nodes(); + 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(); + self.deselect_all_edges()?; } - return; + return Ok(()); } if let Some(idx) = found_node { @@ -246,15 +248,16 @@ where // and only after as double click if resp.double_clicked() { self.handle_node_double_click(idx); - return; + return Ok(()); } - self.handle_node_click(idx); - return; + self.handle_node_click(idx)?; + return Ok(()); } if let Some(edge_idx) = found_edge { - self.handle_edge_click(edge_idx); + self.handle_edge_click(edge_idx)?; } + Ok(()) } fn handle_node_double_click(&mut self, idx: NodeIndex) { @@ -267,11 +270,11 @@ where } } - fn handle_node_click(&mut self, idx: NodeIndex) { + fn handle_node_click(&mut self, idx: NodeIndex) -> anyhow::Result<()> { if !self.settings_interaction.node_clicking_enabled && !self.settings_interaction.node_selection_enabled { - return; + return Ok(()); } if self.settings_interaction.node_clicking_enabled { @@ -279,27 +282,28 @@ where } if !self.settings_interaction.node_selection_enabled { - return; + return Ok(()); } - let n = self.g.node(idx).unwrap(); + let n = self.g.node(idx).context("Could not get node")?; if n.selected() { - self.deselect_node(idx); - return; + self.deselect_node(idx)?; + return Ok(()); } if !self.settings_interaction.node_selection_multi_enabled { - self.deselect_all(); + self.deselect_all()?; } - self.select_node(idx); + self.select_node(idx)?; + Ok(()) } - fn handle_edge_click(&mut self, idx: EdgeIndex) { + fn handle_edge_click(&mut self, idx: EdgeIndex) -> anyhow::Result<()> { if !self.settings_interaction.edge_clicking_enabled && !self.settings_interaction.edge_selection_enabled { - return; + return Ok(()); } if self.settings_interaction.edge_clicking_enabled { @@ -307,37 +311,41 @@ where } if !self.settings_interaction.edge_selection_enabled { - return; + return Ok(()); } - let e = self.g.edge(idx).unwrap(); + let e = self.g.edge(idx).context("Could not get edge")?; if e.selected() { - self.deselect_edge(idx); - return; + self.deselect_edge(idx)?; + return Ok(()); } if !self.settings_interaction.edge_selection_multi_enabled { - self.deselect_all(); + self.deselect_all()?; } - self.select_edge(idx); + self.select_edge(idx)?; + Ok(()) } - fn handle_node_drag(&mut self, resp: &Response, meta: &mut Metadata) { + fn handle_node_drag(&mut self, resp: &Response, meta: &mut Metadata) -> anyhow::Result<()> { if !self.settings_interaction.dragging_enabled { - return; + return Ok(()); } if !resp.dragged_by(PointerButton::Primary) && !resp.drag_started_by(PointerButton::Primary) && !resp.drag_stopped_by(PointerButton::Primary) { - return; + return Ok(()); } if resp.drag_started() { - if let Some(idx) = self.g.node_by_screen_pos(meta, resp.hover_pos().unwrap()) { - self.set_drag_start(idx); + if let Some(idx) = self + .g + .node_by_screen_pos(meta, resp.hover_pos().context("Could not get hover pos")?) + { + self.set_drag_start(idx)?; } } @@ -345,15 +353,22 @@ where && self.g.dragged_node().is_some() && (resp.drag_delta().x.abs() > 0. || resp.drag_delta().y.abs() > 0.) { - let n_idx_dragged = self.g.dragged_node().unwrap(); + let n_idx_dragged = self + .g + .dragged_node() + .context("Could not get dragged node")?; let delta_in_graph_coords = resp.drag_delta() / meta.zoom; - self.move_node(n_idx_dragged, delta_in_graph_coords); + self.move_node(n_idx_dragged, delta_in_graph_coords)?; } if resp.drag_stopped() && self.g.dragged_node().is_some() { - let n_idx = self.g.dragged_node().unwrap(); - self.set_drag_end(n_idx); + let n_idx = self + .g + .dragged_node() + .context("Could not get dragged node")?; + self.set_drag_end(n_idx)?; } + Ok(()) } fn fit_to_screen(&self, rect: &Rect, meta: &mut Metadata) { @@ -446,20 +461,24 @@ where self.set_zoom(new_zoom, meta); } - fn select_node(&mut self, idx: NodeIndex) { - let n = self.g.node_mut(idx).unwrap(); + fn select_node(&mut self, idx: NodeIndex) -> anyhow::Result<()> { + let n = self.g.node_mut(idx)?; n.set_selected(true); #[cfg(feature = "events")] self.publish_event(Event::NodeSelect(PayloadNodeSelect { id: idx.index() })); + + Ok(()) } - fn deselect_node(&mut self, idx: NodeIndex) { - let n = self.g.node_mut(idx).unwrap(); + fn deselect_node(&mut self, idx: NodeIndex) -> anyhow::Result<()> { + let n = self.g.node_mut(idx)?; n.set_selected(false); #[cfg(feature = "events")] self.publish_event(Event::NodeDeselect(PayloadNodeDeselect { id: idx.index() })); + + Ok(()) } #[allow(unused_variables, clippy::unused_self)] @@ -482,44 +501,52 @@ where self.publish_event(Event::EdgeClick(PayloadEdgeClick { id: idx.index() })); } - fn select_edge(&mut self, idx: EdgeIndex) { - let e = self.g.edge_mut(idx).unwrap(); + fn select_edge(&mut self, idx: EdgeIndex) -> anyhow::Result<()> { + let e = self.g.edge_mut(idx).context("Could not get edge")?; e.set_selected(true); #[cfg(feature = "events")] self.publish_event(Event::EdgeSelect(PayloadEdgeSelect { id: idx.index() })); + + Ok(()) } - fn deselect_edge(&mut self, idx: EdgeIndex) { - let e = self.g.edge_mut(idx).unwrap(); + fn deselect_edge(&mut self, idx: EdgeIndex) -> anyhow::Result<()> { + let e = self.g.edge_mut(idx).context("Could not get edge")?; e.set_selected(false); #[cfg(feature = "events")] self.publish_event(Event::EdgeDeselect(PayloadEdgeDeselect { id: idx.index() })); + + Ok(()) } /// Deselects all nodes AND edges. - fn deselect_all(&mut self) { - self.deselect_all_nodes(); - self.deselect_all_edges(); + fn deselect_all(&mut self) -> anyhow::Result<()> { + self.deselect_all_nodes()?; + self.deselect_all_edges()?; + Ok(()) } - fn deselect_all_nodes(&mut self) { + fn deselect_all_nodes(&mut self) -> anyhow::Result<()> { let selected_nodes = self.g.selected_nodes().to_vec(); for idx in selected_nodes { - self.deselect_node(idx); + self.deselect_node(idx)?; } + + Ok(()) } - fn deselect_all_edges(&mut self) { + fn deselect_all_edges(&mut self) -> anyhow::Result<()> { let selected_edges = self.g.selected_edges().to_vec(); for idx in selected_edges { - self.deselect_edge(idx); + self.deselect_edge(idx)?; } + Ok(()) } - fn move_node(&mut self, idx: NodeIndex, delta: Vec2) { - let n = self.g.node_mut(idx).unwrap(); + fn move_node(&mut self, idx: NodeIndex, delta: Vec2) -> anyhow::Result<()> { + let n = self.g.node_mut(idx)?; let new_loc = n.location() + delta; n.set_location(new_loc); @@ -529,24 +556,30 @@ where diff: delta.into(), new_pos: [new_loc.x, new_loc.y], })); + + Ok(()) } - fn set_drag_start(&mut self, idx: NodeIndex) { - let n = self.g.node_mut(idx).unwrap(); + fn set_drag_start(&mut self, idx: NodeIndex) -> anyhow::Result<()> { + let n = self.g.node_mut(idx)?; n.set_dragged(true); #[cfg(feature = "events")] self.publish_event(Event::NodeDragStart(PayloadNodeDragStart { id: idx.index(), })); + + Ok(()) } - fn set_drag_end(&mut self, idx: NodeIndex) { - let n = self.g.node_mut(idx).unwrap(); + fn set_drag_end(&mut self, idx: NodeIndex) -> anyhow::Result<()> { + let n = self.g.node_mut(idx)?; n.set_dragged(false); #[cfg(feature = "events")] self.publish_event(Event::NodeDragEnd(PayloadNodeDragEnd { id: idx.index() })); + + Ok(()) } #[allow(unused_variables, clippy::unused_self)] @@ -573,7 +606,7 @@ where #[cfg(feature = "events")] fn publish_event(&self, event: Event) { if let Some(sender) = self.events_publisher { - sender.send(event).unwrap(); + sender.send(event).expect("Could not send event"); } } } diff --git a/src/transform.rs b/src/transform.rs index ff055c6..91359b6 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -1,4 +1,5 @@ use crate::{DisplayEdge, DisplayNode, Edge, Graph, Node}; +use anyhow::Context; use egui::Pos2; use petgraph::{ graph::IndexType, @@ -133,6 +134,7 @@ pub fn add_edge_custom< /// assert!(loc_1 != Pos2::ZERO); /// assert!(loc_2 != Pos2::ZERO); /// ``` +#[allow(clippy::missing_errors_doc)] // TODO: add error docs pub fn to_graph< N: Clone, E: Clone, @@ -142,11 +144,12 @@ pub fn to_graph< De: DisplayEdge, >( g: &StableGraph, -) -> Graph { +) -> anyhow::Result> { transform(g, &mut default_node_transform, &mut default_edge_transform) } /// The same as [`to_graph`], but allows to define custom transformation procedures for nodes and edges. +#[allow(clippy::missing_errors_doc)] // TODO: add error docs pub fn to_graph_custom< N: Clone, E: Clone, @@ -158,7 +161,7 @@ pub fn to_graph_custom< g: &StableGraph, mut node_transform: impl FnMut(NodeIndex, &N) -> Node, mut edge_transform: impl FnMut(EdgeIndex, &E, usize) -> Edge, -) -> Graph { +) -> anyhow::Result> { transform(g, &mut node_transform, &mut edge_transform) } @@ -217,7 +220,7 @@ fn transform< g: &StableGraph, node_transform: &mut impl FnMut(NodeIndex, &N) -> Node, edge_transform: &mut impl FnMut(EdgeIndex, &E, usize) -> Edge, -) -> Graph { +) -> anyhow::Result> { let mut input_g = StableGraph::, Edge, Ty, Ix>::default(); @@ -229,24 +232,34 @@ fn transform< }) .collect::, NodeIndex>>(); - g.edge_indices().for_each(|user_e_idx| { - let (user_source_n_idx, user_target_n_idx) = g.edge_endpoints(user_e_idx).unwrap(); - let user_e = g.edge_weight(user_e_idx).unwrap(); - - let input_source_n = *input_by_user.get(&user_source_n_idx).unwrap(); - let input_target_n = *input_by_user.get(&user_target_n_idx).unwrap(); - - let order = input_g - .edges_connecting(input_source_n, input_target_n) - .count(); - input_g.add_edge( - input_source_n, - input_target_n, - edge_transform(user_e_idx, user_e, order), - ); - }); - - Graph::new(input_g) + g.edge_indices() + .try_for_each(|user_e_idx| -> anyhow::Result<()> { + let (user_source_n_idx, user_target_n_idx) = g + .edge_endpoints(user_e_idx) + .context("Failed to get edge endpoints")?; + let user_e = g + .edge_weight(user_e_idx) + .context("Failed to get edge weight")?; + + let input_source_n = *input_by_user + .get(&user_source_n_idx) + .context("Failed to get source node")?; + let input_target_n = *input_by_user + .get(&user_target_n_idx) + .context("Failed to get target node")?; + + let order = input_g + .edges_connecting(input_source_n, input_target_n) + .count(); + input_g.add_edge( + input_source_n, + input_target_n, + edge_transform(user_e_idx, user_e, order), + ); + Ok(()) + })?; + + Ok(Graph::new(input_g)) } #[cfg(test)] @@ -265,15 +278,21 @@ mod tests { let n2 = user_g.add_node("Node2"); user_g.add_edge(n1, n2, "Edge1"); - let input_g = to_graph::<_, _, _, _, DefaultNodeShape, DefaultEdgeShape>(&user_g); + let input_g = to_graph::<_, _, _, _, DefaultNodeShape, DefaultEdgeShape>(&user_g) + .expect("Failed to transform graph"); assert_eq!(user_g.node_count(), input_g.g.node_count()); assert_eq!(user_g.edge_count(), input_g.g.edge_count()); assert_eq!(user_g.is_directed(), input_g.is_directed()); for (user_idx, input_idx) in input_g.g.node_indices().zip(user_g.node_indices()) { - let user_n = user_g.node_weight(user_idx).unwrap(); - let input_n = input_g.g.node_weight(input_idx).unwrap(); + let user_n = user_g + .node_weight(user_idx) + .expect("Failed to get user node"); + let input_n = input_g + .g + .node_weight(input_idx) + .expect("Failed to get input node"); assert_eq!(*input_n.payload(), *user_n); @@ -294,15 +313,21 @@ mod tests { let n2 = user_g.add_node("Node2"); user_g.add_edge(n1, n2, "Edge1"); - let input_g = to_graph::<_, _, _, _, DefaultNodeShape, DefaultEdgeShape>(&user_g); + let input_g = to_graph::<_, _, _, _, DefaultNodeShape, DefaultEdgeShape>(&user_g) + .expect("Failed to transform graph"); assert_eq!(user_g.node_count(), input_g.g.node_count()); assert_eq!(user_g.edge_count(), input_g.g.edge_count()); assert_eq!(user_g.is_directed(), input_g.is_directed()); for (user_idx, input_idx) in input_g.g.node_indices().zip(user_g.node_indices()) { - let user_n = user_g.node_weight(user_idx).unwrap(); - let input_n = input_g.g.node_weight(input_idx).unwrap(); + let user_n = user_g + .node_weight(user_idx) + .expect("Failed to get user node"); + let input_n = input_g + .g + .node_weight(input_idx) + .expect("Failed to get input node"); assert_eq!(*input_n.payload(), *user_n);