Skip to content

Commit

Permalink
Clickable + Selectable Edges (#101)
Browse files Browse the repository at this point in the history
Feature: Selectable edges

---------

Co-authored-by: starlord <[email protected]>
  • Loading branch information
Schabolon and blitzarx1 authored Nov 12, 2023
1 parent 4a75118 commit 0a46071
Show file tree
Hide file tree
Showing 12 changed files with 588 additions and 86 deletions.
65 changes: 47 additions & 18 deletions examples/configurable/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,36 +407,62 @@ impl ConfigurableApp {
});

CollapsingHeader::new("Interaction").show(ui, |ui| {
ui.add_enabled_ui(!(self.settings_interaction.dragging_enabled || self.settings_interaction.selection_enabled || self.settings_interaction.selection_multi_enabled), |ui| {
if ui.checkbox(&mut self.settings_interaction.dragging_enabled, "dragging_enabled").clicked() && self.settings_interaction.dragging_enabled {
self.settings_interaction.node_clicking_enabled = true;
};
ui.label("To drag use LMB click + drag on a node.");

ui.add_space(5.);

ui.add_enabled_ui(!(self.settings_interaction.dragging_enabled || self.settings_interaction.node_selection_enabled || self.settings_interaction.node_selection_multi_enabled), |ui| {
ui.vertical(|ui| {
ui.checkbox(&mut self.settings_interaction.clicking_enabled, "clicking_enabled");
ui.checkbox(&mut self.settings_interaction.node_clicking_enabled, "node_clicking_enabled");
ui.label("Check click events in last events");
}).response.on_disabled_hover_text("node click is enabled when any of the interaction is also enabled");
});

ui.add_space(5.);

if ui.checkbox(&mut self.settings_interaction.dragging_enabled, "dragging_enabled").clicked() && self.settings_interaction.dragging_enabled {
self.settings_interaction.clicking_enabled = true;
};
ui.label("To drag use LMB click + drag on a node.");
ui.add_enabled_ui(!self.settings_interaction.node_selection_multi_enabled, |ui| {
ui.vertical(|ui| {
if ui.checkbox(&mut self.settings_interaction.node_selection_enabled, "node_selection_enabled").clicked() && self.settings_interaction.node_selection_enabled {
self.settings_interaction.node_clicking_enabled = true;
};
ui.label("Enable select to select nodes with LMB click. If node is selected clicking on it again will deselect it.");
}).response.on_disabled_hover_text("node_selection_multi_enabled enables select");
});

if ui.checkbox(&mut self.settings_interaction.node_selection_multi_enabled, "node_selection_multi_enabled").changed() && self.settings_interaction.node_selection_multi_enabled {
self.settings_interaction.node_clicking_enabled = true;
self.settings_interaction.node_selection_enabled = true;
}
ui.label("Enable multiselect to select multiple nodes.");

ui.add_space(5.);

ui.add_enabled_ui(!(self.settings_interaction.edge_selection_enabled || self.settings_interaction.edge_selection_multi_enabled), |ui| {
ui.vertical(|ui| {
ui.checkbox(&mut self.settings_interaction.edge_clicking_enabled, "edge_clicking_enabled");
ui.label("Check click events in last events");
}).response.on_disabled_hover_text("edge click is enabled when any of the interaction is also enabled");
});

ui.add_space(5.);

ui.add_enabled_ui(!self.settings_interaction.selection_multi_enabled, |ui| {
ui.add_enabled_ui(!self.settings_interaction.edge_selection_multi_enabled, |ui| {
ui.vertical(|ui| {
if ui.checkbox(&mut self.settings_interaction.selection_enabled, "selection_enabled").clicked() && self.settings_interaction.selection_enabled {
self.settings_interaction.clicking_enabled = true;
if ui.checkbox(&mut self.settings_interaction.edge_selection_enabled, "edge_selection_enabled").clicked() && self.settings_interaction.edge_selection_enabled {
self.settings_interaction.edge_clicking_enabled = true;
};
ui.label("Enable select to select nodes with LMB click. If node is selected clicking on it again will deselect it.");
}).response.on_disabled_hover_text("selection_multi_enabled enables select");
ui.label("Enable select to select edges with LMB click. If edge is selected clicking on it again will deselect it.");
}).response.on_disabled_hover_text("edge_selection_multi_enabled enables select");
});

if ui.checkbox(&mut self.settings_interaction.selection_multi_enabled, "selection_multi_enabled").changed() && self.settings_interaction.selection_multi_enabled {
self.settings_interaction.clicking_enabled = true;
self.settings_interaction.selection_enabled = true;
if ui.checkbox(&mut self.settings_interaction.edge_selection_multi_enabled, "edge_selection_multi_enabled").changed() && self.settings_interaction.edge_selection_multi_enabled {
self.settings_interaction.edge_clicking_enabled = true;
self.settings_interaction.edge_selection_enabled = true;
}
ui.label("Enable multiselect to select multiple nodes.");
ui.label("Enable multiselect to select multiple edges.");
});

CollapsingHeader::new("Selected").default_open(true).show(ui, |ui| {
Expand Down Expand Up @@ -524,10 +550,13 @@ impl App for ConfigurableApp {

egui::CentralPanel::default().show(ctx, |ui| {
let settings_interaction = &egui_graphs::SettingsInteraction::new()
.with_selection_enabled(self.settings_interaction.selection_enabled)
.with_selection_multi_enabled(self.settings_interaction.selection_multi_enabled)
.with_node_selection_enabled(self.settings_interaction.node_selection_enabled)
.with_node_selection_multi_enabled(self.settings_interaction.node_selection_multi_enabled)
.with_dragging_enabled(self.settings_interaction.dragging_enabled)
.with_clicking_enabled(self.settings_interaction.clicking_enabled);
.with_node_clicking_enabled(self.settings_interaction.node_clicking_enabled)
.with_edge_clicking_enabled(self.settings_interaction.edge_clicking_enabled)
.with_edge_selection_enabled(self.settings_interaction.edge_selection_enabled)
.with_edge_selection_multi_enabled(self.settings_interaction.edge_selection_multi_enabled);
let settings_navigation = &egui_graphs::SettingsNavigation::new()
.with_zoom_and_pan_enabled(self.settings_navigation.zoom_and_pan_enabled)
.with_fit_to_screen_enabled(self.settings_navigation.fit_to_screen_enabled)
Expand Down
9 changes: 6 additions & 3 deletions examples/configurable/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ impl Default for SettingsGraph {
#[derive(Default)]
pub struct SettingsInteraction {
pub dragging_enabled: bool,
pub clicking_enabled: bool,
pub selection_enabled: bool,
pub selection_multi_enabled: bool,
pub node_clicking_enabled: bool,
pub node_selection_enabled: bool,
pub node_selection_multi_enabled: bool,
pub edge_clicking_enabled: bool,
pub edge_selection_enabled: bool,
pub edge_selection_multi_enabled: bool,
}

pub struct SettingsNavigation {
Expand Down
2 changes: 1 addition & 1 deletion examples/custom_draw/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl App for CustomDrawApp {
.with_interactions(
&SettingsInteraction::default()
.with_dragging_enabled(true)
.with_selection_enabled(true),
.with_node_selection_enabled(true),
)
.with_custom_node_draw(|ctx, n, state, l| {
// lets draw a rect with label in the center for every node
Expand Down
11 changes: 8 additions & 3 deletions examples/interactive/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ impl App for InteractiveApp {
egui::CentralPanel::default().show(ctx, |ui| {
let interaction_settings = &SettingsInteraction::new()
.with_dragging_enabled(true)
.with_clicking_enabled(true)
.with_selection_enabled(true)
.with_selection_multi_enabled(true);
.with_node_clicking_enabled(true)
.with_node_selection_enabled(true)
.with_node_selection_multi_enabled(true)
.with_edge_clicking_enabled(true)
.with_edge_selection_enabled(true)
.with_edge_selection_multi_enabled(true);
let style_settings = &SettingsStyle::new().with_labels_always(true);
ui.add(
&mut GraphView::new(&mut self.g)
Expand All @@ -42,6 +45,8 @@ fn generate_graph() -> Graph<(), ()> {
let b = g.add_node(());
let c = g.add_node(());

g.add_edge(a, a, ());
g.add_edge(a, b, ());
g.add_edge(a, b, ());
g.add_edge(b, c, ());
g.add_edge(c, a, ());
Expand Down
10 changes: 6 additions & 4 deletions src/computed.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use egui::{Rect, Vec2};
use petgraph::graph::IndexType;
use petgraph::graph::{EdgeIndex, IndexType};
use petgraph::{stable_graph::NodeIndex, EdgeType};

use crate::{Graph, Node, SettingsStyle};
Expand All @@ -8,7 +8,8 @@ use crate::{Graph, Node, SettingsStyle};
#[derive(Debug, Clone)]
pub struct ComputedState<Ix: IndexType> {
pub dragged: Option<NodeIndex<Ix>>,
pub selected: Vec<NodeIndex<Ix>>,
pub selected_nodes: Vec<NodeIndex<Ix>>,
pub selected_edges: Vec<EdgeIndex<Ix>>,

min: Vec2,
max: Vec2,
Expand All @@ -23,7 +24,8 @@ where
Self {
dragged: None,

selected: Vec::new(),
selected_nodes: Vec::new(),
selected_edges: Vec::new(),

min: Vec2::new(f32::MAX, f32::MAX),
max: Vec2::new(f32::MIN, f32::MIN),
Expand All @@ -47,7 +49,7 @@ where
self.dragged = Some(idx);
}
if n.selected() {
self.selected.push(idx);
self.selected_nodes.push(idx);
}

ComputedNode {
Expand Down
36 changes: 27 additions & 9 deletions src/draw/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,23 @@ fn draw_edge_basic<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType>(
tip_end - e.tip_size() * state.meta.zoom * rotate_vector(dir, -e.tip_angle());

let shape = Shape::line_segment([edge_start, edge_end], stroke_edge);
l.add(shape);
match e.selected() {
true => l.add_top(shape),
false => l.add(shape),
}

// draw tips for directed edges
if state.g.is_directed() {
let shape_tip =
Shape::convex_polygon(vec![tip_end, tip_start_1, tip_start_2], color, stroke_tip);
l.add(shape_tip);
if !state.g.is_directed() {
return;
}

// draw tips for directed edges
let shape_tip =
Shape::convex_polygon(vec![tip_end, tip_start_1, tip_start_2], color, stroke_tip);
match e.selected() {
true => l.add_top(shape_tip),
false => l.add(shape_tip),
};

return;
}

Expand Down Expand Up @@ -113,11 +121,18 @@ fn draw_edge_basic<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType>(
Color32::TRANSPARENT,
stroke_edge,
);
l.add(shape_curved);

match e.selected() {
true => l.add_top(shape_curved),
false => l.add(shape_curved),
}

let shape_tip_curved =
Shape::convex_polygon(vec![tip_end, tip_start_1, tip_start_2], color, stroke_tip);
l.add(shape_tip_curved);
match e.selected() {
true => l.add_top(shape_tip_curved),
false => l.add(shape_tip_curved),
};
}

fn draw_edge_looped<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType>(
Expand Down Expand Up @@ -149,7 +164,10 @@ fn draw_edge_looped<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType>(
stroke,
);

l.add(shape);
match e.selected() {
true => l.add_top(shape),
false => l.add(shape),
}
}

/// rotates vector by angle
Expand Down
19 changes: 17 additions & 2 deletions src/elements/edge.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use egui::{Color32, Context};

use super::StyleEdge;
use egui::{Color32, Context};

/// Stores properties of an edge that can be changed. Used to apply changes to the graph.
#[derive(Clone, Debug)]
Expand All @@ -9,6 +8,8 @@ pub struct Edge<E: Clone> {
data: Option<E>,

style: StyleEdge,

selected: bool,
}

impl<E: Clone> Default for Edge<E> {
Expand All @@ -17,6 +18,8 @@ impl<E: Clone> Default for Edge<E> {
style: Default::default(),

data: Default::default(),

selected: Default::default(),
}
}
}
Expand All @@ -39,6 +42,10 @@ impl<E: Clone> Edge<E> {
}

pub fn color(&self, ctx: &Context) -> Color32 {
if self.selected {
return ctx.style().visuals.widgets.hovered.fg_stroke.color;
}

ctx.style()
.visuals
.gray_out(ctx.style().visuals.widgets.inactive.fg_stroke.color)
Expand All @@ -61,4 +68,12 @@ impl<E: Clone> Edge<E> {
pub fn tip_size(&self) -> f32 {
self.style.tip_size
}

pub fn set_selected(&mut self, selected: bool) {
self.selected = selected;
}

pub fn selected(&self) -> bool {
self.selected
}
}
22 changes: 20 additions & 2 deletions src/events/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct PayloadPan {
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PyaloadZoom {
pub struct PayloadZoom {
pub diff: f32,
}

Expand Down Expand Up @@ -46,15 +46,33 @@ pub struct PayloadNodeDoubleClick {
pub id: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PayloadEdgeClick {
pub id: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PayloadEdgeSelect {
pub id: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PayloadEdgeDeselect {
pub id: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Event {
Pan(PayloadPan),
Zoom(PyaloadZoom),
Zoom(PayloadZoom),
NodeMove(PayloadNodeMove),
NodeDragStart(PayloadNodeDragStart),
NodeDragEnd(PayloadNodeDragEnd),
NodeSelect(PayloadNodeSelect),
NodeDeselect(PayloadNodeDeselect),
NodeClick(PayloadNodeClick),
NodeDoubleClick(PayloadNodeDoubleClick),
EdgeClick(PayloadEdgeClick),
EdgeSelect(PayloadEdgeSelect),
EdgeDeselect(PayloadEdgeDeselect),
}
5 changes: 3 additions & 2 deletions src/events/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod event;

pub use self::event::{
Event, PayloadNodeClick, PayloadNodeDeselect, PayloadNodeDoubleClick, PayloadNodeDragEnd,
PayloadNodeDragStart, PayloadNodeMove, PayloadNodeSelect, PayloadPan, PyaloadZoom,
Event, PayloadEdgeClick, PayloadEdgeDeselect, PayloadEdgeSelect, PayloadNodeClick,
PayloadNodeDeselect, PayloadNodeDoubleClick, PayloadNodeDragEnd, PayloadNodeDragStart,
PayloadNodeMove, PayloadNodeSelect, PayloadPan, PayloadZoom,
};
Loading

0 comments on commit 0a46071

Please sign in to comment.