Flowchart use Egui Scene

This commit is contained in:
Michael Mikovsky
2025-11-27 19:19:11 -07:00
parent dca08b9e97
commit 2f780d36fd
3 changed files with 116 additions and 111 deletions
+20 -12
View File
@@ -60,19 +60,27 @@ impl eframe::App for TemplateApp {
// The top panel is often a good place for a menu bar:
egui::MenuBar::new().ui(ui, |ui| {
if ui
.selectable_label(self.tab == Tab::Flowchart, "Network")
.clicked()
{
self.tab = Tab::Flowchart;
}
ui.menu_button("File", |ui| {
ui.label("File");
});
if ui
.selectable_label(self.tab == Tab::Test, self.config.title())
.clicked()
{
self.tab = Tab::Test;
}
ui.menu_button("View", |ui| {
ui.label("View");
if ui
.selectable_label(self.tab == Tab::Flowchart, "Network")
.clicked()
{
self.tab = Tab::Flowchart;
}
if ui
.selectable_label(self.tab == Tab::Test, self.config.title())
.clicked()
{
self.tab = Tab::Test;
}
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
egui::widgets::global_theme_preference_switch(ui);
+30 -39
View File
@@ -4,6 +4,7 @@ use egui::{Pos2, Rect, UiBuilder, Vec2};
pub struct DraggableContainer {
pub pos: egui::Vec2, // Offset from center of clip_rect
pub size: egui::Vec2,
is_dragging: bool,
drag_offset: egui::Vec2,
drag_id: String,
@@ -34,42 +35,6 @@ impl DraggableContainer {
}
}
// pub fn show<R>(
// &mut self,
// ui: &mut egui::Ui,
// add_contents: impl FnOnce(&mut egui::Ui, &Rect) -> R,
// ) -> R {
// let rect = egui::Rect::from_min_size(self.pos, self.size);
// // Handle dragging logic
// let response = ui.interact(rect, ui.id().with(&self.drag_id), egui::Sense::drag());
// if response.drag_started() {
// self.is_dragging = true;
// if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
// self.drag_offset = self.pos - pointer_pos;
// }
// }
// if response.dragged() && self.is_dragging {
// if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
// self.pos = pointer_pos + self.drag_offset;
// }
// }
// if response.drag_stopped() {
// self.is_dragging = false;
// }
// // Create a child UI at the specified position
// // let mut child_ui = ui.child_ui(rect, egui::Layout::top_down(egui::Align::LEFT), None);
// let mut child_ui = ui.new_child(UiBuilder::new().max_rect(rect));
// // Add contents
// add_contents(&mut child_ui, &rect)
// }
pub fn get_pos(&self, center: &Pos2) -> Pos2 {
center.clone() + self.pos
}
@@ -80,7 +45,7 @@ impl DraggableContainer {
add_contents: impl FnOnce(&mut egui::Ui, &Rect) -> R,
) -> R {
// Calculate center of the clip rect
let clip_center = ui.clip_rect().center();
let clip_center = Pos2::ZERO;
// Calculate actual position from center offset
let center_pos = clip_center + self.pos;
@@ -90,18 +55,44 @@ impl DraggableContainer {
// Handle dragging logic
let response = ui.interact(rect, ui.id().with(&self.drag_id), egui::Sense::drag());
// if response.secondary_clicked() {
// }
if response.drag_started() {
self.is_dragging = true;
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
if let Some(pointer_pos) = ui.input(|i| i.pointer.latest_pos()) {
let pointer_pos = ui
.ctx()
.layer_transform_from_global(ui.painter().layer_id())
.unwrap_or_default()
* pointer_pos;
self.drag_offset = center_pos - pointer_pos;
}
}
if response.dragged() && self.is_dragging {
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
// Pointer code from https://github.com/emilk/egui/pull/7149
if let Some(pointer_pos) = ui.input(|i| i.pointer.latest_pos()) {
let pointer_pos = ui
.ctx()
.layer_transform_from_global(ui.painter().layer_id())
.unwrap_or_default()
* pointer_pos;
let new_center = pointer_pos + self.drag_offset;
self.pos = new_center - clip_center;
}
// egui::Frame::default()
// .stroke(ui.visuals().widgets.noninteractive.bg_stroke)
// .corner_radius(ui.visuals().widgets.noninteractive.corner_radius)
// .show(ui, |ui| {
// ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE));
// // self.frame.show(ui, |ui| {
// // ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
// // ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE));
// // });
// });
}
if response.drag_stopped() {
+66 -60
View File
@@ -1,5 +1,7 @@
use egui::Scene;
use egui::Shape;
use egui::Ui;
use egui::Vec2;
use egui::{Color32, Painter, Pos2, Rect};
use crate::flowchart::CONNECTION_STROKE;
@@ -11,6 +13,7 @@ use crate::flowchart::{BG_STROKE, TARGET_LINE_GAP};
#[derive(serde::Deserialize, serde::Serialize)]
pub struct FlowChart {
scene_rect: Rect,
pub containers: Vec<DraggableContainer>,
pub connections: Vec<(usize, usize)>,
pub groups: Vec<Vec<usize>>,
@@ -19,6 +22,7 @@ pub struct FlowChart {
impl FlowChart {
pub fn new() -> Self {
let mut this = Self {
scene_rect: Rect::ZERO,
containers: vec![
DraggableContainer::new_zero(0),
DraggableContainer::new_zero(1),
@@ -30,7 +34,7 @@ impl FlowChart {
DraggableContainer::new_zero(7),
],
connections: vec![(0, 1), (1, 2), (1, 3), (1, 4), (3, 5), (3, 6), (3, 7)],
groups: vec![],
groups: vec![vec![1, 3, 5, 7]],
};
this.arrange_circle();
@@ -38,28 +42,33 @@ impl FlowChart {
this
}
fn paint_bg(&self, rect: &Rect, painter: &Painter) {
let h_count = (rect.width() / TARGET_LINE_GAP).round() as usize;
let h_spacing = rect.width() / h_count as f32;
for n in 0..h_count {
painter.vline(rect.min.x + n as f32 * h_spacing, rect.y_range(), BG_STROKE);
fn paint_bg(rect: &Rect, painter: &Painter) {
let h_start = (rect.min.x / TARGET_LINE_GAP).round() as i32;
let h_end = ((rect.min.x + rect.width()) / TARGET_LINE_GAP).round() as i32 + 1;
for n in h_start..h_end {
painter.vline(n as f32 * TARGET_LINE_GAP, rect.y_range(), BG_STROKE);
}
let v_count = (rect.height() / TARGET_LINE_GAP).round() as usize;
let v_spacing = rect.height() / v_count as f32;
for n in 0..v_count {
painter.hline(rect.x_range(), rect.min.y + n as f32 * v_spacing, BG_STROKE);
let v_start = (rect.min.y / TARGET_LINE_GAP).round() as i32;
let v_end = ((rect.min.y + rect.height()) / TARGET_LINE_GAP).round() as i32 + 1;
for n in v_start..v_end {
painter.hline(rect.x_range(), n as f32 * TARGET_LINE_GAP, BG_STROKE);
}
}
fn paint_groups(&self, ui: &mut Ui) {
let center = ui.clip_rect().center();
for group in &self.groups {
fn paint_groups(
rect: &Rect,
groups: &Vec<Vec<usize>>,
containers: &Vec<DraggableContainer>,
ui: &mut Ui,
) {
let center = rect.center();
for group in groups {
let mut points = Vec::new();
for n in group {
let container = &self.containers[*n];
let pos = container.get_pos(&center);
let container = &containers[*n];
let pos = container.get_pos(&Pos2::ZERO);
let size = container.size;
points.append(&mut vec![
Pos2 {
@@ -92,58 +101,55 @@ impl FlowChart {
}
pub fn paint(&mut self, ui: &mut Ui) {
self.paint_bg(&ui.clip_rect(), ui.painter());
self.paint_groups(ui);
let scene = Scene::new()
// .max_inner_size([350.0, 1000.0])
.zoom_range(0.1..=2.0);
let center = ui.clip_rect().center();
let containers = &mut self.containers;
let groups = &self.groups;
for (a, b) in &self.connections {
ui.painter().line_segment(
[
self.containers[*a].get_pos(&center),
self.containers[*b].get_pos(&center),
],
CONNECTION_STROKE,
);
let mut inner_rect = Rect::NAN;
let rect = &self.scene_rect.clone();
// let start = self.containers[m.clone()];
// let end = self.containers[n.clone()];
}
let response = scene
.show(ui, &mut self.scene_rect, |mut ui| {
Self::paint_bg(rect, ui.painter());
Self::paint_groups(rect, groups, containers, &mut ui);
for container in &mut self.containers {
container.show(ui, |ui, rect| {
ui.painter().rect(
// ui.top
*rect,
0.,
Color32::PURPLE,
BG_STROKE,
egui::StrokeKind::Outside,
);
// ui.label("Tests");
// let _ = ui.button("Test");
});
let center = Pos2::ZERO;
for (a, b) in &self.connections {
ui.painter().line_segment(
[
containers[*a].get_pos(&center),
containers[*b].get_pos(&center),
],
CONNECTION_STROKE,
);
}
for container in containers {
container.show(&mut ui, |ui, rect| {
ui.painter().rect(
// ui.top
*rect,
0.,
Color32::PURPLE,
BG_STROKE,
egui::StrokeKind::Outside,
);
});
}
inner_rect = ui.min_rect();
})
.response;
if response.double_clicked() {
self.scene_rect = inner_rect;
}
if ui.button("Arrange").clicked() {
// let positions: Vec<Vec2> = (0..num_nodes)
// .map(|i| {
// let angle = (i as f32) * 2.0 * std::f32::consts::PI / (num_nodes as f32);
// Vec2::new(angle.cos() * 100.0, angle.sin() * 100.0)
// })
// .collect();
// let node_count = self.containers.len() as f32;
// for (i, m) in self.containers.iter_mut().enumerate() {
// let ang = -(i as f32 / node_count) * PI * 2.;
// m.pos = Vec2 {
// x: 1000. * ang.sin(),
// y: 1000. * ang.cos(),
// };
// m.vel = Vec2::ZERO;
// }
for _ in 0..1_000 {
self.force(0.1);
}