From 2ddeb92c0b6f11f81bb98537effdf393d50b90d2 Mon Sep 17 00:00:00 2001 From: starlord Date: Sat, 14 Sep 2024 13:07:47 +0300 Subject: [PATCH] feat: implemented example --- Cargo.lock | 10 +++ examples/demo/src/settings.rs | 2 - examples/flex_nodes/src/main.rs | 4 +- examples/rainbow_edges/Cargo.toml | 10 +++ examples/rainbow_edges/README.md | 7 ++ examples/rainbow_edges/src/edge.rs | 107 +++++++++++++++++++++++++++++ examples/rainbow_edges/src/main.rs | 55 +++++++++++++++ src/draw/displays_default/edge.rs | 25 ++++--- 8 files changed, 202 insertions(+), 18 deletions(-) create mode 100644 examples/rainbow_edges/Cargo.toml create mode 100644 examples/rainbow_edges/README.md create mode 100644 examples/rainbow_edges/src/edge.rs create mode 100644 examples/rainbow_edges/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index fb103d2..67130fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4160,6 +4160,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17fd96390ed3feda12e1dfe2645ed587e0bea749e319333f104a33ff62f77a0b" +[[package]] +name = "rainbow_edges" +version = "0.1.0" +dependencies = [ + "eframe", + "egui 0.28.1", + "egui_graphs 0.21.1", + "petgraph", +] + [[package]] name = "rand" version = "0.8.5" diff --git a/examples/demo/src/settings.rs b/examples/demo/src/settings.rs index 0322ba6..49b3de9 100644 --- a/examples/demo/src/settings.rs +++ b/examples/demo/src/settings.rs @@ -26,14 +26,12 @@ pub struct SettingsInteraction { pub struct SettingsNavigation { pub fit_to_screen_enabled: bool, pub zoom_and_pan_enabled: bool, - pub screen_padding: f32, pub zoom_speed: f32, } impl Default for SettingsNavigation { fn default() -> Self { Self { - screen_padding: 0.3, zoom_speed: 0.1, fit_to_screen_enabled: true, zoom_and_pan_enabled: false, diff --git a/examples/flex_nodes/src/main.rs b/examples/flex_nodes/src/main.rs index 6e5c88c..1cd7511 100644 --- a/examples/flex_nodes/src/main.rs +++ b/examples/flex_nodes/src/main.rs @@ -1,8 +1,6 @@ use eframe::{run_native, App, CreationContext}; use egui::{CentralPanel, Context, SidePanel, TextEdit}; -use egui_graphs::{ - DefaultEdgeShape, Graph, GraphView, SettingsInteraction, SettingsNavigation, SettingsStyle, -}; +use egui_graphs::{DefaultEdgeShape, Graph, GraphView, SettingsInteraction, SettingsNavigation}; use node::NodeShapeFlex; use petgraph::{ stable_graph::{DefaultIx, EdgeIndex, NodeIndex, StableGraph}, diff --git a/examples/rainbow_edges/Cargo.toml b/examples/rainbow_edges/Cargo.toml new file mode 100644 index 0000000..ac99f93 --- /dev/null +++ b/examples/rainbow_edges/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rainbow_edges" +version = "0.1.0" +edition = "2021" + +[dependencies] +egui_graphs = { path = "../../" } +egui = "0.28" +eframe = "0.28" +petgraph = "0.6" diff --git a/examples/rainbow_edges/README.md b/examples/rainbow_edges/README.md new file mode 100644 index 0000000..43c4cfa --- /dev/null +++ b/examples/rainbow_edges/README.md @@ -0,0 +1,7 @@ +# Rainbow Edges +Example demonstrates the possibility of drawing custom edges shapes. + +## run +```bash +cargo run --release -p rainbow_edges +``` diff --git a/examples/rainbow_edges/src/edge.rs b/examples/rainbow_edges/src/edge.rs new file mode 100644 index 0000000..5cdc354 --- /dev/null +++ b/examples/rainbow_edges/src/edge.rs @@ -0,0 +1,107 @@ +use egui::{Color32, Pos2, Shape, Stroke, Vec2}; +use egui_graphs::{DefaultEdgeShape, DisplayEdge, DisplayNode, DrawContext, EdgeProps, Node}; +use petgraph::{stable_graph::IndexType, EdgeType}; + +const TIP_ANGLE: f32 = std::f32::consts::TAU / 30.; +const TIP_SIZE: f32 = 15.; +const COLORS: [Color32; 7] = [ + Color32::RED, + Color32::from_rgb(255, 102, 0), + Color32::YELLOW, + Color32::GREEN, + Color32::from_rgb(2, 216, 233), + Color32::BLUE, + Color32::from_rgb(91, 10, 145), +]; + +#[derive(Clone)] +pub struct RainbowEdgeShape { + default_impl: DefaultEdgeShape, +} + +impl From> for RainbowEdgeShape { + fn from(props: EdgeProps) -> Self { + Self { + default_impl: DefaultEdgeShape::from(props), + } + } +} + +impl> + DisplayEdge for RainbowEdgeShape +{ + fn shapes( + &mut self, + start: &Node, + end: &Node, + ctx: &DrawContext, + ) -> Vec { + let mut res = vec![]; + let (start, end) = (start.location(), end.location()); + let (x_dist, y_dist) = (end.x - start.x, end.y - start.y); + let (dx, dy) = (x_dist / COLORS.len() as f32, y_dist / COLORS.len() as f32); + let d_vec = Vec2::new(dx, dy); + + let mut stroke = Stroke::default(); + let mut points_line; + + for (i, color) in COLORS.iter().enumerate() { + stroke = Stroke::new(self.default_impl.width, *color); + points_line = vec![ + start + i as f32 * d_vec, + end - (COLORS.len() - i - 1) as f32 * d_vec, + ]; + + stroke.width = ctx.meta.canvas_to_screen_size(stroke.width); + points_line = points_line + .iter() + .map(|p| ctx.meta.canvas_to_screen_pos(*p)) + .collect(); + res.push(Shape::line_segment( + [points_line[0], points_line[1]], + stroke, + )); + } + + let tip_dir = (end - start).normalized(); + + let arrow_tip_dir_1 = rotate_vector(tip_dir, TIP_ANGLE) * TIP_SIZE; + let arrow_tip_dir_2 = rotate_vector(tip_dir, -TIP_ANGLE) * TIP_SIZE; + + let tip_start_1 = end - arrow_tip_dir_1; + let tip_start_2 = end - arrow_tip_dir_2; + + let mut points_tip = vec![end, tip_start_1, tip_start_2]; + + points_tip = points_tip + .iter() + .map(|p| ctx.meta.canvas_to_screen_pos(*p)) + .collect(); + + res.push(Shape::convex_polygon( + points_tip, + stroke.color, + Stroke::default(), + )); + + res + } + + fn update(&mut self, _: &egui_graphs::EdgeProps) {} + + fn is_inside( + &self, + start: &Node, + end: &Node, + pos: Pos2, + ) -> bool { + self.default_impl.is_inside(start, end, pos) + } +} + +/// rotates vector by angle +fn rotate_vector(vec: Vec2, angle: f32) -> Vec2 { + let cos = angle.cos(); + let sin = angle.sin(); + Vec2::new(cos * vec.x - sin * vec.y, sin * vec.x + cos * vec.y) +} diff --git a/examples/rainbow_edges/src/main.rs b/examples/rainbow_edges/src/main.rs new file mode 100644 index 0000000..7fe30eb --- /dev/null +++ b/examples/rainbow_edges/src/main.rs @@ -0,0 +1,55 @@ +use edge::RainbowEdgeShape; +use eframe::{run_native, App, CreationContext}; +use egui::Context; +use egui_graphs::{DefaultNodeShape, Graph, GraphView}; +use petgraph::{csr::DefaultIx, stable_graph::StableGraph, Directed}; + +mod edge; + +pub struct RainbowEdgesApp { + g: Graph<(), (), Directed, DefaultIx, DefaultNodeShape, RainbowEdgeShape>, +} + +impl RainbowEdgesApp { + fn new(_: &CreationContext<'_>) -> Self { + let g = generate_graph(); + Self { g: Graph::from(&g) } + } +} + +impl App for RainbowEdgesApp { + fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.add( + &mut GraphView::<_, _, _, _, DefaultNodeShape, RainbowEdgeShape>::new(&mut self.g) + .with_interactions( + &egui_graphs::SettingsInteraction::default().with_dragging_enabled(true), + ), + ); + }); + } +} + +fn generate_graph() -> StableGraph<(), ()> { + let mut g = StableGraph::new(); + + let a = g.add_node(()); + let b = g.add_node(()); + let c = g.add_node(()); + + g.add_edge(a, b, ()); + g.add_edge(b, c, ()); + g.add_edge(c, a, ()); + + g +} + +fn main() { + let native_options = eframe::NativeOptions::default(); + run_native( + "egui_graphs_rainbow_edges_demo", + native_options, + Box::new(|cc| Ok(Box::new(RainbowEdgesApp::new(cc)))), + ) + .unwrap(); +} diff --git a/src/draw/displays_default/edge.rs b/src/draw/displays_default/edge.rs index b9f1f17..25b306d 100644 --- a/src/draw/displays_default/edge.rs +++ b/src/draw/displays_default/edge.rs @@ -82,7 +82,7 @@ impl cubic, - _ => panic!("Invalid shape type"), + _ => panic!("invalid shape type"), }; // TODO: export to func @@ -141,7 +141,7 @@ impl curve, - _ => panic!("Invalid shape type"), + _ => panic!("invalid shape type"), }; res.extend(curved_shapes.clone()); - // TODO: export to func if label_visible { - let size = (node_size(start) + node_size(end)) / 2.; + let size = (node_size(start, dir) + node_size(end, dir)) / 2.; let galley = ctx.ctx.fonts(|f| { f.layout_no_wrap( self.label_text.clone(), @@ -231,7 +230,7 @@ impl DefaultEdgeShape { node: &Node, pos: Pos2, ) -> bool { - let node_size = node_size(node); + let node_size = node_size(node, Vec2::new(-1., 0.)); let shape = EdgeShapeBuilder::new(Stroke::new(self.width, Color32::default())) .looped(node.location(), node_size, self.loop_size, self.order) @@ -239,7 +238,7 @@ impl DefaultEdgeShape { match shape.first() { Some(Shape::CubicBezier(cubic)) => is_point_on_curve(pos, cubic.clone()), - _ => panic!("Invalid shape type"), + _ => panic!("invalid shape type"), } } @@ -269,20 +268,20 @@ impl DefaultEdgeShape { .build(); let curved_shape = match curved_shapes.first() { Some(Shape::CubicBezier(curve)) => curve.clone(), - _ => panic!("Invalid shape type"), + _ => panic!("invalid shape type"), }; is_point_on_curve(pos, curved_shape) } } -// TOOD: export this func as common drawing func +// TODO: export this func as common drawing func fn node_size>( node: &Node, + dir: Vec2, ) -> f32 { - let left_dir = Vec2::new(-1., 0.); - let connector_left = node.display().closest_boundary_point(left_dir); - let connector_right = node.display().closest_boundary_point(-left_dir); + let connector_left = node.display().closest_boundary_point(dir); + let connector_right = node.display().closest_boundary_point(-dir); (connector_right.x - connector_left.x) / 2. }