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
+8
View File
@@ -60,6 +60,13 @@ impl eframe::App for TemplateApp {
// The top panel is often a good place for a menu bar: // The top panel is often a good place for a menu bar:
egui::MenuBar::new().ui(ui, |ui| { egui::MenuBar::new().ui(ui, |ui| {
ui.menu_button("File", |ui| {
ui.label("File");
});
ui.menu_button("View", |ui| {
ui.label("View");
if ui if ui
.selectable_label(self.tab == Tab::Flowchart, "Network") .selectable_label(self.tab == Tab::Flowchart, "Network")
.clicked() .clicked()
@@ -73,6 +80,7 @@ impl eframe::App for TemplateApp {
{ {
self.tab = Tab::Test; self.tab = Tab::Test;
} }
});
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
egui::widgets::global_theme_preference_switch(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 struct DraggableContainer {
pub pos: egui::Vec2, // Offset from center of clip_rect pub pos: egui::Vec2, // Offset from center of clip_rect
pub size: egui::Vec2, pub size: egui::Vec2,
is_dragging: bool, is_dragging: bool,
drag_offset: egui::Vec2, drag_offset: egui::Vec2,
drag_id: String, 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 { pub fn get_pos(&self, center: &Pos2) -> Pos2 {
center.clone() + self.pos center.clone() + self.pos
} }
@@ -80,7 +45,7 @@ impl DraggableContainer {
add_contents: impl FnOnce(&mut egui::Ui, &Rect) -> R, add_contents: impl FnOnce(&mut egui::Ui, &Rect) -> R,
) -> R { ) -> R {
// Calculate center of the clip rect // Calculate center of the clip rect
let clip_center = ui.clip_rect().center(); let clip_center = Pos2::ZERO;
// Calculate actual position from center offset // Calculate actual position from center offset
let center_pos = clip_center + self.pos; let center_pos = clip_center + self.pos;
@@ -90,18 +55,44 @@ impl DraggableContainer {
// Handle dragging logic // Handle dragging logic
let response = ui.interact(rect, ui.id().with(&self.drag_id), egui::Sense::drag()); let response = ui.interact(rect, ui.id().with(&self.drag_id), egui::Sense::drag());
// if response.secondary_clicked() {
// }
if response.drag_started() { if response.drag_started() {
self.is_dragging = true; 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; self.drag_offset = center_pos - pointer_pos;
} }
} }
if response.dragged() && self.is_dragging { 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; let new_center = pointer_pos + self.drag_offset;
self.pos = new_center - clip_center; 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() { if response.drag_stopped() {
+51 -45
View File
@@ -1,5 +1,7 @@
use egui::Scene;
use egui::Shape; use egui::Shape;
use egui::Ui; use egui::Ui;
use egui::Vec2;
use egui::{Color32, Painter, Pos2, Rect}; use egui::{Color32, Painter, Pos2, Rect};
use crate::flowchart::CONNECTION_STROKE; use crate::flowchart::CONNECTION_STROKE;
@@ -11,6 +13,7 @@ use crate::flowchart::{BG_STROKE, TARGET_LINE_GAP};
#[derive(serde::Deserialize, serde::Serialize)] #[derive(serde::Deserialize, serde::Serialize)]
pub struct FlowChart { pub struct FlowChart {
scene_rect: Rect,
pub containers: Vec<DraggableContainer>, pub containers: Vec<DraggableContainer>,
pub connections: Vec<(usize, usize)>, pub connections: Vec<(usize, usize)>,
pub groups: Vec<Vec<usize>>, pub groups: Vec<Vec<usize>>,
@@ -19,6 +22,7 @@ pub struct FlowChart {
impl FlowChart { impl FlowChart {
pub fn new() -> Self { pub fn new() -> Self {
let mut this = Self { let mut this = Self {
scene_rect: Rect::ZERO,
containers: vec![ containers: vec![
DraggableContainer::new_zero(0), DraggableContainer::new_zero(0),
DraggableContainer::new_zero(1), DraggableContainer::new_zero(1),
@@ -30,7 +34,7 @@ impl FlowChart {
DraggableContainer::new_zero(7), DraggableContainer::new_zero(7),
], ],
connections: vec![(0, 1), (1, 2), (1, 3), (1, 4), (3, 5), (3, 6), (3, 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(); this.arrange_circle();
@@ -38,28 +42,33 @@ impl FlowChart {
this this
} }
fn paint_bg(&self, rect: &Rect, painter: &Painter) { fn paint_bg(rect: &Rect, painter: &Painter) {
let h_count = (rect.width() / TARGET_LINE_GAP).round() as usize; let h_start = (rect.min.x / TARGET_LINE_GAP).round() as i32;
let h_spacing = rect.width() / h_count as f32; let h_end = ((rect.min.x + rect.width()) / TARGET_LINE_GAP).round() as i32 + 1;
for n in 0..h_count { for n in h_start..h_end {
painter.vline(rect.min.x + n as f32 * h_spacing, rect.y_range(), BG_STROKE); 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_start = (rect.min.y / TARGET_LINE_GAP).round() as i32;
let v_spacing = rect.height() / v_count as f32; let v_end = ((rect.min.y + rect.height()) / TARGET_LINE_GAP).round() as i32 + 1;
for n in 0..v_count { for n in v_start..v_end {
painter.hline(rect.x_range(), rect.min.y + n as f32 * v_spacing, BG_STROKE); painter.hline(rect.x_range(), n as f32 * TARGET_LINE_GAP, BG_STROKE);
} }
} }
fn paint_groups(&self, ui: &mut Ui) { fn paint_groups(
let center = ui.clip_rect().center(); rect: &Rect,
for group in &self.groups { groups: &Vec<Vec<usize>>,
containers: &Vec<DraggableContainer>,
ui: &mut Ui,
) {
let center = rect.center();
for group in groups {
let mut points = Vec::new(); let mut points = Vec::new();
for n in group { for n in group {
let container = &self.containers[*n]; let container = &containers[*n];
let pos = container.get_pos(&center); let pos = container.get_pos(&Pos2::ZERO);
let size = container.size; let size = container.size;
points.append(&mut vec![ points.append(&mut vec![
Pos2 { Pos2 {
@@ -92,26 +101,35 @@ impl FlowChart {
} }
pub fn paint(&mut self, ui: &mut Ui) { pub fn paint(&mut self, ui: &mut Ui) {
self.paint_bg(&ui.clip_rect(), ui.painter()); let scene = Scene::new()
self.paint_groups(ui); // .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;
let mut inner_rect = Rect::NAN;
let rect = &self.scene_rect.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);
let center = Pos2::ZERO;
for (a, b) in &self.connections { for (a, b) in &self.connections {
ui.painter().line_segment( ui.painter().line_segment(
[ [
self.containers[*a].get_pos(&center), containers[*a].get_pos(&center),
self.containers[*b].get_pos(&center), containers[*b].get_pos(&center),
], ],
CONNECTION_STROKE, CONNECTION_STROKE,
); );
// let start = self.containers[m.clone()];
// let end = self.containers[n.clone()];
} }
for container in &mut self.containers { for container in containers {
container.show(ui, |ui, rect| { container.show(&mut ui, |ui, rect| {
ui.painter().rect( ui.painter().rect(
// ui.top // ui.top
*rect, *rect,
@@ -120,30 +138,18 @@ impl FlowChart {
BG_STROKE, BG_STROKE,
egui::StrokeKind::Outside, egui::StrokeKind::Outside,
); );
// ui.label("Tests");
// let _ = ui.button("Test");
}); });
} }
inner_rect = ui.min_rect();
})
.response;
if response.double_clicked() {
self.scene_rect = inner_rect;
}
if ui.button("Arrange").clicked() { 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 { for _ in 0..1_000 {
self.force(0.1); self.force(0.1);
} }