From 2f780d36fd4cb035e89da75ce56e6d76dce13072 Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Thu, 27 Nov 2025 19:19:11 -0700 Subject: [PATCH] Flowchart use Egui Scene --- unshell-gui/src/app.rs | 32 ++++--- unshell-gui/src/flowchart/container.rs | 69 ++++++-------- unshell-gui/src/flowchart/flowchart.rs | 126 +++++++++++++------------ 3 files changed, 116 insertions(+), 111 deletions(-) diff --git a/unshell-gui/src/app.rs b/unshell-gui/src/app.rs index 1d503bd..d9479b7 100644 --- a/unshell-gui/src/app.rs +++ b/unshell-gui/src/app.rs @@ -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); diff --git a/unshell-gui/src/flowchart/container.rs b/unshell-gui/src/flowchart/container.rs index ea3a987..836ebfe 100644 --- a/unshell-gui/src/flowchart/container.rs +++ b/unshell-gui/src/flowchart/container.rs @@ -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( - // &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() { diff --git a/unshell-gui/src/flowchart/flowchart.rs b/unshell-gui/src/flowchart/flowchart.rs index 58159ac..7533037 100644 --- a/unshell-gui/src/flowchart/flowchart.rs +++ b/unshell-gui/src/flowchart/flowchart.rs @@ -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, pub connections: Vec<(usize, usize)>, pub groups: Vec>, @@ -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>, + containers: &Vec, + 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(¢er); + 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(¢er), - self.containers[*b].get_pos(¢er), - ], - 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(¢er), + containers[*b].get_pos(¢er), + ], + 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 = (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); }