From e0b51e03e82714d83a6280c8ee2510f15855b384 Mon Sep 17 00:00:00 2001 From: starlord Date: Wed, 3 May 2023 00:55:31 +0400 Subject: [PATCH] Extracted drawer (#30) --- Cargo.lock | 8 +- Cargo.toml | 2 +- examples/basic/Cargo.toml | 2 +- examples/configurable/Cargo.toml | 2 +- examples/interactive/Cargo.toml | 2 +- src/change.rs | 16 +- src/drawer.rs | 418 +++++++++++++++++++++++++++++++ src/frame_state.rs | 10 +- src/graph_view.rs | 397 +---------------------------- src/lib.rs | 1 + src/selections.rs | 6 +- src/settings.rs | 4 +- 12 files changed, 455 insertions(+), 413 deletions(-) create mode 100644 src/drawer.rs diff --git a/Cargo.lock b/Cargo.lock index 2d00fd5..aeade59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,7 +339,7 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "basic" -version = "0.4.0" +version = "0.4.1" dependencies = [ "eframe", "egui", @@ -521,7 +521,7 @@ dependencies = [ [[package]] name = "configurable" -version = "0.4.0" +version = "0.4.1" dependencies = [ "eframe", "egui", @@ -751,7 +751,7 @@ dependencies = [ [[package]] name = "egui_graphs" -version = "0.4.0" +version = "0.4.1" dependencies = [ "egui", "petgraph", @@ -1145,7 +1145,7 @@ dependencies = [ [[package]] name = "interactive" -version = "0.4.0" +version = "0.4.1" dependencies = [ "eframe", "egui", diff --git a/Cargo.toml b/Cargo.toml index ea47405..6ba1a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egui_graphs" -version = "0.4.0" +version = "0.4.1" authors = ["Dmitrii Samsonov "] license = "MIT" homepage = "https://github.com/blitzarx1/egui_graphs" diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index af9904f..458e5ee 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "basic" -version = "0.4.0" +version = "0.4.1" authors = ["Dmitrii Samsonov "] license = "MIT" edition = "2021" diff --git a/examples/configurable/Cargo.toml b/examples/configurable/Cargo.toml index 5ba8525..00cce24 100644 --- a/examples/configurable/Cargo.toml +++ b/examples/configurable/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "configurable" -version = "0.4.0" +version = "0.4.1" authors = ["Dmitrii Samsonov "] license = "MIT" edition = "2021" diff --git a/examples/interactive/Cargo.toml b/examples/interactive/Cargo.toml index e0717cb..649dd70 100644 --- a/examples/interactive/Cargo.toml +++ b/examples/interactive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "interactive" -version = "0.4.0" +version = "0.4.1" authors = ["Dmitrii Samsonov "] license = "MIT" edition = "2021" diff --git a/src/change.rs b/src/change.rs index 11908f3..5a71403 100644 --- a/src/change.rs +++ b/src/change.rs @@ -1,5 +1,5 @@ use egui::Vec2; -use petgraph::stable_graph::{NodeIndex, EdgeIndex}; +use petgraph::stable_graph::{EdgeIndex, NodeIndex}; /// `ChangeNode` is a enum that stores the changes to `Node` properties. #[derive(Debug, Clone)] @@ -67,12 +67,20 @@ mod tests { let old_node_location = vec2(0.0, 0.0); let new_node_location = vec2(10.0, 10.0); - let node_change = Change::node(ChangeNode::change_location(node_id, old_node_location, new_node_location)); + let node_change = Change::node(ChangeNode::change_location( + node_id, + old_node_location, + new_node_location, + )); let node_selected_old = false; let node_selected_new = true; - let edge_change = Change::edge(ChangeEdge::change_selected(edge_id, node_selected_old, node_selected_new)); + let edge_change = Change::edge(ChangeEdge::change_selected( + edge_id, + node_selected_old, + node_selected_new, + )); match node_change { Change::Node(ChangeNode::Location { id, old, new }) => { @@ -92,4 +100,4 @@ mod tests { _ => panic!("Unexpected edge change type"), } } -} \ No newline at end of file +} diff --git a/src/drawer.rs b/src/drawer.rs new file mode 100644 index 0000000..d26e8ef --- /dev/null +++ b/src/drawer.rs @@ -0,0 +1,418 @@ +use std::f32::{MAX, MIN}; + +use egui::{ + epaint::{CircleShape, CubicBezierShape, QuadraticBezierShape}, + Color32, Painter, Pos2, Rect, Shape, Stroke, Vec2, +}; +use petgraph::{ + stable_graph::{NodeIndex, StableGraph}, + visit::IntoNodeReferences, +}; + +use crate::{frame_state::FrameState, metadata::Metadata, Edge, Node, SettingsStyle}; + +pub struct Drawer<'a, N: Clone, E: Clone> { + g: &'a StableGraph, Edge>, + p: &'a Painter, + settings_style: &'a SettingsStyle, +} + +impl<'a, N: Clone, E: Clone> Drawer<'a, N, E> { + pub fn new( + g: &'a StableGraph, Edge>, + p: &'a Painter, + settings_style: &'a SettingsStyle, + ) -> Self { + Drawer { + g, + p, + settings_style, + } + } + + pub fn draw(&self, state: &mut FrameState, metadata: &mut Metadata) { + let edges_shapes = self.draw_edges(state, metadata); + let nodes_shapes = self.draw_nodes(metadata, state); + + self.draw_edges_shapes(edges_shapes); + self.draw_nodes_shapes(nodes_shapes); + } + + fn draw_nodes(&self, meta: &mut Metadata, frame_state: &mut FrameState) -> Vec { + let mut shapes = vec![]; + let (mut min_x, mut min_y, mut max_x, mut max_y) = (MAX, MAX, MIN, MIN); + self.g.node_references().for_each(|(idx, n)| { + // update graph bounds on the fly + // we shall account for the node radius + // so that the node is fully visible + + let x_minus_rad = n.location.x - n.radius; + if x_minus_rad < min_x { + min_x = x_minus_rad; + }; + + let y_minus_rad = n.location.y - n.radius; + if y_minus_rad < min_y { + min_y = y_minus_rad; + }; + + let x_plus_rad = n.location.x + n.radius; + if x_plus_rad > max_x { + max_x = x_plus_rad; + }; + + let y_plus_rad = n.location.y + n.radius; + if y_plus_rad > max_y { + max_y = y_plus_rad; + }; + + if n.dragged { + frame_state.dragged = Some(idx); + } + + let selected = self.draw_node(n, meta); + shapes.extend(selected); + }); + + meta.graph_bounds = Rect::from_min_max(Pos2::new(min_x, min_y), Pos2::new(max_x, max_y)); + + shapes + } + + fn draw_edges( + &self, + state: &mut FrameState, + meta: &Metadata, + ) -> (Vec, Vec, Vec) { + let mut shapes = (Vec::new(), Vec::new(), Vec::new()); + state + .edges_by_nodes(self.g) + .iter() + .for_each(|((start, end), edges)| { + let mut order = edges.len(); + edges.iter().for_each(|(_, e)| { + order -= 1; + + let edge = e.screen_transform(meta.zoom); + + let selected = self.draw_edge(&edge, start, end, meta, order); + shapes.0.extend(selected.0); + shapes.1.extend(selected.1); + shapes.2.extend(selected.2); + }); + }); + + shapes + } + + fn draw_edges_shapes( + &self, + shapes: (Vec, Vec, Vec), + ) { + shapes.0.into_iter().for_each(|shape| { + self.p.add(shape); + }); + shapes.1.into_iter().for_each(|shape| { + self.p.add(shape); + }); + shapes.2.into_iter().for_each(|shape| { + self.p.add(shape); + }); + } + + fn draw_nodes_shapes(&self, shapes: Vec) { + shapes.into_iter().for_each(|shape| { + self.p.add(shape); + }); + } + + fn draw_edge( + &self, + edge: &Edge, + start: &usize, + end: &usize, + meta: &Metadata, + order: usize, + ) -> (Vec, Vec, Vec) { + let idx_start = NodeIndex::new(*start); + let idx_end = NodeIndex::new(*end); + + let start_node = self + .g + .node_weight(idx_start) + .unwrap() + .screen_transform(meta.zoom, meta.pan); + let end_node = self + .g + .node_weight(idx_end) + .unwrap() + .screen_transform(meta.zoom, meta.pan); + + let mut selected_shapes = vec![]; + let mut selected_quadratic = vec![]; + let mut selected_cubic = vec![]; + + if start == end { + self.draw_edge_looped(&start_node, edge, order) + .into_iter() + .for_each(|c| { + selected_cubic.push(c); + }); + } + + let shapes = self.draw_edge_basic(&start_node, &end_node, edge, order); + shapes + .0 + .into_iter() + .for_each(|shape| selected_shapes.push(shape)); + shapes + .1 + .into_iter() + .for_each(|shape| selected_quadratic.push(shape)); + + (selected_shapes, selected_cubic, selected_quadratic) + } + + fn draw_edge_looped(&self, n: &Node, e: &Edge, order: usize) -> Vec { + let pos_start_and_end = n.location.to_pos2(); + let loop_size = n.radius * (4. + 1. + order as f32); + + let control_point1 = Pos2::new( + pos_start_and_end.x + loop_size, + pos_start_and_end.y - loop_size, + ); + let control_point2 = Pos2::new( + pos_start_and_end.x - loop_size, + pos_start_and_end.y - loop_size, + ); + + let stroke = Stroke::new(e.width, self.settings_style.color_edge(self.p.ctx(), e)); + let shape_basic = CubicBezierShape::from_points_stroke( + [ + pos_start_and_end, + control_point1, + control_point2, + pos_start_and_end, + ], + true, + Color32::TRANSPARENT, + stroke, + ); + + if !e.selected() { + self.p.add(shape_basic); + return vec![]; + } + + let mut shapes = vec![shape_basic]; + + let highlighted_stroke = Stroke::new( + e.width * 2., + self.settings_style.color_edge_highlight(e).unwrap(), + ); + shapes.push(CubicBezierShape::from_points_stroke( + [ + pos_start_and_end, + control_point1, + control_point2, + pos_start_and_end, + ], + true, + Color32::TRANSPARENT, + highlighted_stroke, + )); + + shapes + } + + fn draw_edge_basic( + &self, + n_start: &Node, + n_end: &Node, + e: &Edge, + order: usize, + ) -> (Vec, Vec) { + let pos_start = n_start.location.to_pos2(); + let pos_end = n_end.location.to_pos2(); + + let vec = pos_end - pos_start; + let l = vec.length(); + let dir = vec / l; + + let start_node_radius_vec = Vec2::new(n_start.radius, n_start.radius) * dir; + let end_node_radius_vec = Vec2::new(n_end.radius, n_end.radius) * dir; + + let tip_point = pos_start + vec - end_node_radius_vec; + let start_point = pos_start + start_node_radius_vec; + + let stroke = Stroke::new(e.width, self.settings_style.color_edge(self.p.ctx(), e)); + + // draw straight edge + if order == 0 { + let mut shapes = vec![]; + let head_point_1 = tip_point - e.tip_size * rotate_vector(dir, e.tip_angle); + let head_point_2 = tip_point - e.tip_size * rotate_vector(dir, -e.tip_angle); + + shapes.push(Shape::line_segment([start_point, tip_point], stroke)); + shapes.push(Shape::line_segment([tip_point, head_point_1], stroke)); + shapes.push(Shape::line_segment([tip_point, head_point_2], stroke)); + + if !e.selected() { + shapes.into_iter().for_each(|shape| { + self.p.add(shape); + }); + + return (vec![], vec![]); + } + + let highlighted_stroke = Stroke::new( + e.width * 2., + self.settings_style.color_edge_highlight(e).unwrap(), + ); + shapes.push(Shape::line_segment( + [start_point, tip_point], + highlighted_stroke, + )); + shapes.push(Shape::line_segment( + [tip_point, head_point_1], + highlighted_stroke, + )); + shapes.push(Shape::line_segment( + [tip_point, head_point_2], + highlighted_stroke, + )); + + return (shapes, vec![]); + } + + let mut shapes = vec![]; + let mut quadratic_shapes = vec![]; + let dir_perpendicular = Vec2::new(-dir.y, dir.x); + let center_point = (start_point + tip_point.to_vec2()).to_vec2() / 2.0; + let control_point = + (center_point + dir_perpendicular * e.curve_size * order as f32).to_pos2(); + + let tip_vec = control_point - tip_point; + let tip_dir = tip_vec / tip_vec.length(); + let tip_size = e.tip_size; + + let arrow_tip_dir_1 = rotate_vector(tip_dir, e.tip_angle) * tip_size; + let arrow_tip_dir_2 = rotate_vector(tip_dir, -e.tip_angle) * tip_size; + + let head_point_1 = tip_point + arrow_tip_dir_1; + let head_point_2 = tip_point + arrow_tip_dir_2; + + quadratic_shapes.push(QuadraticBezierShape::from_points_stroke( + [start_point, control_point, tip_point], + false, + Color32::TRANSPARENT, + stroke, + )); + shapes.push(Shape::line_segment([tip_point, head_point_1], stroke)); + shapes.push(Shape::line_segment([tip_point, head_point_2], stroke)); + + if !e.selected { + quadratic_shapes.into_iter().for_each(|shape| { + self.p.add(shape); + }); + shapes.into_iter().for_each(|shape| { + self.p.add(shape); + }); + + return (vec![], vec![]); + } + + let highlighted_stroke = Stroke::new( + e.width * 2., + self.settings_style.color_edge_highlight(e).unwrap(), + ); + quadratic_shapes.push(QuadraticBezierShape::from_points_stroke( + [start_point, control_point, tip_point], + false, + Color32::TRANSPARENT, + highlighted_stroke, + )); + shapes.push(Shape::line_segment( + [tip_point, head_point_1], + highlighted_stroke, + )); + shapes.push(Shape::line_segment( + [tip_point, head_point_2], + highlighted_stroke, + )); + + (shapes, quadratic_shapes) + } + + fn draw_node(&self, n: &Node, meta: &Metadata) -> Vec { + let node = &n.screen_transform(meta.zoom, meta.pan); + let loc = node.location.to_pos2(); + + self.draw_node_basic(loc, node) + .into_iter() + .chain(self.draw_node_interacted(loc, node).into_iter()) + .collect() + } + + fn draw_node_basic(&self, loc: Pos2, node: &Node) -> Vec { + let color = self.settings_style.color_node(self.p.ctx(), node); + if !(node.selected() || node.dragged) { + // draw the node in place + self.p.circle_filled( + loc, + node.radius, + self.settings_style.color_node(self.p.ctx(), node), + ); + return vec![]; + } + + // draw the node later if it's selected or dragged to make sure it's on top + vec![CircleShape { + center: loc, + radius: node.radius, + fill: color, + stroke: Stroke::new(1., color), + }] + } + + fn draw_node_interacted(&self, loc: Pos2, node: &Node) -> Vec { + if !(node.selected() || node.dragged) { + return vec![]; + } + + let mut shapes = vec![]; + let highlight_radius = node.radius * 1.5; + + shapes.push(CircleShape { + center: loc, + radius: highlight_radius, + fill: Color32::TRANSPARENT, + stroke: Stroke::new( + node.radius, + self.settings_style.color_node_highlight(node).unwrap(), + ), + }); + + shapes + } +} + +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) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rotate_vector() { + let vec = Vec2::new(1.0, 0.0); + let angle = std::f32::consts::PI / 2.0; + let rotated = rotate_vector(vec, angle); + assert!((rotated.x - 0.0).abs() < 1e-6); + assert!((rotated.y - 1.0).abs() < 1e-6); + } +} \ No newline at end of file diff --git a/src/frame_state.rs b/src/frame_state.rs index fcd5a29..ca65d02 100644 --- a/src/frame_state.rs +++ b/src/frame_state.rs @@ -4,13 +4,13 @@ use petgraph::stable_graph::{EdgeIndex, NodeIndex, StableGraph}; use crate::{selections::Selections, Edge, Node}; -pub(crate) type EdgesByNodes = Vec<((usize, usize), Vec<(EdgeIndex, Edge)>)>; +pub type EdgesByNodes = Vec<((usize, usize), Vec<(EdgeIndex, Edge)>)>; /// `FrameState` is a utility struct for managing ephemerial state which is created and destroyed in one frame. /// /// The struct stores the selected nodes, dragged node, and cached edges by nodes. #[derive(Debug, Clone)] -pub(crate) struct FrameState { +pub struct FrameState { pub dragged: Option, pub selections: Option, edges_by_nodes: Option>, @@ -27,14 +27,14 @@ impl Default for FrameState { } impl FrameState { - /// Helper method to get the edges by nodes. This is cached for performance. + /// Helper method to get edges by nodes. pub fn edges_by_nodes( &mut self, g: &StableGraph, Edge>, ) -> &EdgesByNodes { let mut edge_map: HashMap<(usize, usize), Vec<(EdgeIndex, Edge)>> = HashMap::new(); - for edge_idx in g.edge_indices() { + g.edge_indices().for_each(|edge_idx| { let (source_idx, target_idx) = g.edge_endpoints(edge_idx).unwrap(); let source = source_idx.index(); let target = target_idx.index(); @@ -44,7 +44,7 @@ impl FrameState { .entry((source, target)) .or_insert_with(Vec::new) .push((edge_idx, edge)); - } + }); let res = edge_map .iter() diff --git a/src/graph_view.rs b/src/graph_view.rs index 2b2425c..babbd42 100644 --- a/src/graph_view.rs +++ b/src/graph_view.rs @@ -7,6 +7,7 @@ use crate::{ change::Change, change::ChangeEdge, change::ChangeNode, + drawer::Drawer, elements::Node, frame_state::FrameState, metadata::Metadata, @@ -14,10 +15,7 @@ use crate::{ settings::{SettingsInteraction, SettingsStyle}, Edge, SettingsNavigation, }; -use egui::{ - epaint::{CircleShape, CubicBezierShape, QuadraticBezierShape}, - Color32, Painter, Pos2, Rect, Response, Sense, Shape, Stroke, Ui, Vec2, Widget, -}; +use egui::{Painter, Pos2, Rect, Response, Sense, Ui, Vec2, Widget}; use petgraph::{ stable_graph::{EdgeIndex, NodeIndex, StableGraph}, visit::IntoNodeReferences, @@ -49,13 +47,12 @@ pub struct GraphView<'a, N: Clone, E: Clone> { impl<'a, N: Clone, E: Clone> Widget for &mut GraphView<'a, N, E> { fn ui(self, ui: &mut Ui) -> Response { let mut meta = Metadata::get(ui); + let mut frame_state = self.precompute_state(); let (resp, p) = ui.allocate_painter(ui.available_size(), Sense::click_and_drag()); self.fit_if_first(&resp, &mut meta); - let mut frame_state = self.precompute_state(); - self.draw(&p, &mut frame_state, &mut meta); self.handle_nodes_drags(&resp, &mut frame_state, &mut meta); @@ -436,385 +433,9 @@ impl<'a, N: Clone, E: Clone> GraphView<'a, N, E> { state } - fn draw(&mut self, p: &Painter, state: &mut FrameState, metadata: &mut Metadata) { - let edges_shapes = self.draw_edges(p, state, metadata); - let nodes_shapes = self.draw_nodes(p, metadata, state); - - self.draw_edges_shapes(p, edges_shapes); - self.draw_nodes_shapes(p, nodes_shapes); - } - - fn draw_nodes( - &self, - p: &Painter, - meta: &mut Metadata, - frame_state: &mut FrameState, - ) -> Vec { - let mut shapes = vec![]; - let (mut min_x, mut min_y, mut max_x, mut max_y) = (MAX, MAX, MIN, MIN); - self.g.node_references().for_each(|(idx, n)| { - // update graph bounds on the fly - // we shall account for the node radius - // so that the node is fully visible - - let x_minus_rad = n.location.x - n.radius; - if x_minus_rad < min_x { - min_x = x_minus_rad; - }; - - let y_minus_rad = n.location.y - n.radius; - if y_minus_rad < min_y { - min_y = y_minus_rad; - }; - - let x_plus_rad = n.location.x + n.radius; - if x_plus_rad > max_x { - max_x = x_plus_rad; - }; - - let y_plus_rad = n.location.y + n.radius; - if y_plus_rad > max_y { - max_y = y_plus_rad; - }; - - if n.dragged { - frame_state.dragged = Some(idx); - } - - let selected = self.draw_node(p, n, meta); - shapes.extend(selected); - }); - - meta.graph_bounds = Rect::from_min_max(Pos2::new(min_x, min_y), Pos2::new(max_x, max_y)); - - shapes - } - - fn draw_edges( - &mut self, - p: &Painter, - state: &mut FrameState, - meta: &Metadata, - ) -> (Vec, Vec, Vec) { - let mut shapes = (Vec::new(), Vec::new(), Vec::new()); - state - .edges_by_nodes(self.g) - .iter() - .for_each(|((start, end), edges)| { - let mut order = edges.len(); - edges.iter().for_each(|(_, e)| { - order -= 1; - - let edge = e.screen_transform(meta.zoom); - - let selected = self.draw_edge(&edge, p, start, end, meta, order); - shapes.0.extend(selected.0); - shapes.1.extend(selected.1); - shapes.2.extend(selected.2); - }); - }); - - shapes - } - - fn draw_edges_shapes( - &self, - p: &Painter, - shapes: (Vec, Vec, Vec), - ) { - shapes.0.into_iter().for_each(|shape| { - p.add(shape); - }); - shapes.1.into_iter().for_each(|shape| { - p.add(shape); - }); - shapes.2.into_iter().for_each(|shape| { - p.add(shape); - }); - } - - fn draw_nodes_shapes(&self, p: &Painter, shapes: Vec) { - shapes.into_iter().for_each(|shape| { - p.add(shape); - }); - } - - fn draw_edge( - &self, - edge: &Edge, - p: &Painter, - start: &usize, - end: &usize, - meta: &Metadata, - order: usize, - ) -> (Vec, Vec, Vec) { - let idx_start = NodeIndex::new(*start); - let idx_end = NodeIndex::new(*end); - - let start_node = self - .g - .node_weight(idx_start) - .unwrap() - .screen_transform(meta.zoom, meta.pan); - let end_node = self - .g - .node_weight(idx_end) - .unwrap() - .screen_transform(meta.zoom, meta.pan); - - let mut selected_shapes = vec![]; - let mut selected_quadratic = vec![]; - let mut selected_cubic = vec![]; - - if start == end { - self.draw_edge_looped(p, &start_node, edge, order) - .into_iter() - .for_each(|c| { - selected_cubic.push(c); - }); - } - - let shapes = self.draw_edge_basic(p, &start_node, &end_node, edge, order); - shapes - .0 - .into_iter() - .for_each(|shape| selected_shapes.push(shape)); - shapes - .1 - .into_iter() - .for_each(|shape| selected_quadratic.push(shape)); - - (selected_shapes, selected_cubic, selected_quadratic) - } - - fn draw_edge_looped( - &self, - p: &Painter, - n: &Node, - e: &Edge, - order: usize, - ) -> Vec { - let pos_start_and_end = n.location.to_pos2(); - let loop_size = n.radius * (4. + 1. + order as f32); - - let control_point1 = Pos2::new( - pos_start_and_end.x + loop_size, - pos_start_and_end.y - loop_size, - ); - let control_point2 = Pos2::new( - pos_start_and_end.x - loop_size, - pos_start_and_end.y - loop_size, - ); - - let stroke = Stroke::new(e.width, self.settings_style.color_edge(p.ctx(), e)); - let shape_basic = CubicBezierShape::from_points_stroke( - [ - pos_start_and_end, - control_point1, - control_point2, - pos_start_and_end, - ], - true, - Color32::TRANSPARENT, - stroke, - ); - - if !e.selected() { - p.add(shape_basic); - return vec![]; - } - - let mut shapes = vec![shape_basic]; - - let highlighted_stroke = Stroke::new( - e.width * 2., - self.settings_style.color_edge_highlight(e).unwrap(), - ); - shapes.push(CubicBezierShape::from_points_stroke( - [ - pos_start_and_end, - control_point1, - control_point2, - pos_start_and_end, - ], - true, - Color32::TRANSPARENT, - highlighted_stroke, - )); - - shapes - } - - fn draw_edge_basic( - &self, - p: &Painter, - n_start: &Node, - n_end: &Node, - e: &Edge, - order: usize, - ) -> (Vec, Vec) { - let pos_start = n_start.location.to_pos2(); - let pos_end = n_end.location.to_pos2(); - - let vec = pos_end - pos_start; - let l = vec.length(); - let dir = vec / l; - - let start_node_radius_vec = Vec2::new(n_start.radius, n_start.radius) * dir; - let end_node_radius_vec = Vec2::new(n_end.radius, n_end.radius) * dir; - - let tip_point = pos_start + vec - end_node_radius_vec; - let start_point = pos_start + start_node_radius_vec; - - let stroke = Stroke::new(e.width, self.settings_style.color_edge(p.ctx(), e)); - - // draw straight edge - if order == 0 { - let mut shapes = vec![]; - let head_point_1 = tip_point - e.tip_size * rotate_vector(dir, e.tip_angle); - let head_point_2 = tip_point - e.tip_size * rotate_vector(dir, -e.tip_angle); - - shapes.push(Shape::line_segment([start_point, tip_point], stroke)); - shapes.push(Shape::line_segment([tip_point, head_point_1], stroke)); - shapes.push(Shape::line_segment([tip_point, head_point_2], stroke)); - - if !e.selected() { - shapes.into_iter().for_each(|shape| { - p.add(shape); - }); - - return (vec![], vec![]); - } - - let highlighted_stroke = Stroke::new( - e.width * 2., - self.settings_style.color_edge_highlight(e).unwrap(), - ); - shapes.push(Shape::line_segment( - [start_point, tip_point], - highlighted_stroke, - )); - shapes.push(Shape::line_segment( - [tip_point, head_point_1], - highlighted_stroke, - )); - shapes.push(Shape::line_segment( - [tip_point, head_point_2], - highlighted_stroke, - )); - - return (shapes, vec![]); - } - - let mut shapes = vec![]; - let mut quadratic_shapes = vec![]; - let dir_perpendicular = Vec2::new(-dir.y, dir.x); - let center_point = (start_point + tip_point.to_vec2()).to_vec2() / 2.0; - let control_point = - (center_point + dir_perpendicular * e.curve_size * order as f32).to_pos2(); - - let tip_vec = control_point - tip_point; - let tip_dir = tip_vec / tip_vec.length(); - let tip_size = e.tip_size; - - let arrow_tip_dir_1 = rotate_vector(tip_dir, e.tip_angle) * tip_size; - let arrow_tip_dir_2 = rotate_vector(tip_dir, -e.tip_angle) * tip_size; - - let head_point_1 = tip_point + arrow_tip_dir_1; - let head_point_2 = tip_point + arrow_tip_dir_2; - - quadratic_shapes.push(QuadraticBezierShape::from_points_stroke( - [start_point, control_point, tip_point], - false, - Color32::TRANSPARENT, - stroke, - )); - shapes.push(Shape::line_segment([tip_point, head_point_1], stroke)); - shapes.push(Shape::line_segment([tip_point, head_point_2], stroke)); - - if !e.selected { - quadratic_shapes.into_iter().for_each(|shape| { - p.add(shape); - }); - shapes.into_iter().for_each(|shape| { - p.add(shape); - }); - - return (vec![], vec![]); - } - - let highlighted_stroke = Stroke::new( - e.width * 2., - self.settings_style.color_edge_highlight(e).unwrap(), - ); - quadratic_shapes.push(QuadraticBezierShape::from_points_stroke( - [start_point, control_point, tip_point], - false, - Color32::TRANSPARENT, - highlighted_stroke, - )); - shapes.push(Shape::line_segment( - [tip_point, head_point_1], - highlighted_stroke, - )); - shapes.push(Shape::line_segment( - [tip_point, head_point_2], - highlighted_stroke, - )); - - (shapes, quadratic_shapes) - } - - fn draw_node(&self, p: &Painter, n: &Node, meta: &Metadata) -> Vec { - let node = &n.screen_transform(meta.zoom, meta.pan); - let loc = node.location.to_pos2(); - - self.draw_node_basic(loc, p, node) - .into_iter() - .chain(self.draw_node_interacted(loc, node).into_iter()) - .collect() - } - - fn draw_node_basic(&self, loc: Pos2, p: &Painter, node: &Node) -> Vec { - let color = self.settings_style.color_node(p.ctx(), node); - if !(node.selected() || node.dragged) { - // draw the node in place - p.circle_filled( - loc, - node.radius, - self.settings_style.color_node(p.ctx(), node), - ); - return vec![]; - } - - // draw the node later if it's selected or dragged to make sure it's on top - vec![CircleShape { - center: loc, - radius: node.radius, - fill: color, - stroke: Stroke::new(1., color), - }] - } - - fn draw_node_interacted(&self, loc: Pos2, node: &Node) -> Vec { - if !(node.selected() || node.dragged) { - return vec![]; - } - - let mut shapes = vec![]; - let highlight_radius = node.radius * 1.5; - - shapes.push(CircleShape { - center: loc, - radius: highlight_radius, - fill: Color32::TRANSPARENT, - stroke: Stroke::new( - node.radius, - self.settings_style.color_node_highlight(node).unwrap(), - ), - }); - - shapes + fn draw(&self, p: &Painter, state: &mut FrameState, metadata: &mut Metadata) { + let drawer = Drawer::new(self.g, p, &self.settings_style); + drawer.draw(state, metadata); } fn send_changes(&self, changes: Change) { @@ -824,12 +445,6 @@ impl<'a, N: Clone, E: Clone> GraphView<'a, N, E> { } } -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) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 829d296..eccb1fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod change; +mod drawer; mod elements; mod frame_state; mod graph_view; diff --git a/src/selections.rs b/src/selections.rs index accb62b..244f314 100644 --- a/src/selections.rs +++ b/src/selections.rs @@ -7,11 +7,11 @@ use petgraph::{ use crate::{Edge, Node}; -pub(crate) type Selection = Graph; -pub(crate) type Elements = (Vec, Vec); +pub type Selection = Graph; +pub type Elements = (Vec, Vec); #[derive(Default, Debug, Clone)] -pub(crate) struct Selections { +pub struct Selections { data: HashMap, } diff --git a/src/settings.rs b/src/settings.rs index a11aa40..2855d3e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -128,7 +128,7 @@ impl SettingsStyle { None } - pub fn color_edge(&self, ctx: &egui::Context, e: &Edge) -> Color32 { + pub(crate) fn color_edge(&self, ctx: &egui::Context, e: &Edge) -> Color32 { if e.color.is_some() { return e.color.unwrap(); } @@ -139,7 +139,7 @@ impl SettingsStyle { self.color_node } - pub fn color_edge_highlight(&self, e: &Edge) -> Option { + pub(crate) fn color_edge_highlight(&self, e: &Edge) -> Option { if e.selected { return Some(self.color_selection); }