From 88e0fc7d23bb9ccfe1cda488e68caa19a891dde7 Mon Sep 17 00:00:00 2001 From: Federico Lolli <federico.lolli@skywarder.eu> Date: Mon, 11 Nov 2024 20:17:38 +0100 Subject: [PATCH] Added some experimentation with window and settings --- Cargo.toml | 11 ++-- src/ui/composable_view.rs | 48 +++++++++-------- src/ui/panes.rs | 40 ++++++++++++--- src/ui/panes/default.rs | 8 +-- src/ui/panes/messages_viewer.rs | 4 -- src/ui/panes/plot_2d.rs | 91 +++++++++++++++++++++++++++++---- 6 files changed, 150 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e129123..6a26a05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,16 +8,17 @@ description = "Skyward Enhanced Ground Station" license = "MIT" [dependencies] -# GUI & Rendering +# ======= GUI & Rendering ======= +egui_tiles = "0.10" eframe = "0.29" egui = { version = "0.29", features = ["log"] } egui_plot = "0.29" -egui_tiles = "0.10" -# Persistency +# ========= Persistency ========= serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -# Logging +# =========== Logging =========== env_logger = "0.11" log = "0.4" -# Utility +# =========== Utility =========== +# for dynamic dispatch enum_dispatch = "0.3" diff --git a/src/ui/composable_view.rs b/src/ui/composable_view.rs index cbada0e..5b308d6 100644 --- a/src/ui/composable_view.rs +++ b/src/ui/composable_view.rs @@ -4,10 +4,10 @@ use super::{ }; use egui::{Key, Modifiers}; -use egui_tiles::{Behavior, Container, Linear, LinearDir, Tile, Tiles, Tree}; +use egui_tiles::{Behavior, Container, Linear, LinearDir, Tile, TileId, Tiles, Tree}; pub struct ComposableView { - tree: Tree<Pane>, + panes_tree: Tree<Pane>, behavior: ComposableBehavior, } @@ -16,10 +16,10 @@ impl Default for ComposableView { fn default() -> Self { let mut tiles = Tiles::default(); let root = tiles.insert_pane(Pane::default()); - let tree = egui_tiles::Tree::new("my_tree", root, tiles); + let panes_tree = egui_tiles::Tree::new("my_tree", root, tiles); Self { - tree, + panes_tree, behavior: Default::default(), } } @@ -31,7 +31,7 @@ impl eframe::App for ComposableView { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // get the id of the hovered pane, in order to apply actions to it let hovered_pane = self - .tree + .panes_tree .tiles .iter() .find(|(_, tile)| matches!(tile, Tile::Pane(pane) if pane.contains_pointer())) @@ -56,10 +56,10 @@ impl eframe::App for ComposableView { if let Some((action, hovered_tile)) = pane_action.take() { match action { PaneAction::SplitH => { - let hovered_tile_pane = self.tree.tiles.remove(hovered_tile).unwrap(); - let left_pane = self.tree.tiles.insert_new(hovered_tile_pane); - let right_pane = self.tree.tiles.insert_pane(Pane::default()); - self.tree.tiles.insert( + let hovered_tile_pane = self.panes_tree.tiles.remove(hovered_tile).unwrap(); + let left_pane = self.panes_tree.tiles.insert_new(hovered_tile_pane); + let right_pane = self.panes_tree.tiles.insert_pane(Pane::default()); + self.panes_tree.tiles.insert( hovered_tile, Tile::Container(Container::Linear(Linear::new_binary( LinearDir::Horizontal, @@ -69,10 +69,10 @@ impl eframe::App for ComposableView { ); } PaneAction::SplitV => { - let hovered_tile_pane = self.tree.tiles.remove(hovered_tile).unwrap(); - let replaced = self.tree.tiles.insert_new(hovered_tile_pane); - let lower_pane = self.tree.tiles.insert_pane(Pane::default()); - self.tree.tiles.insert( + let hovered_tile_pane = self.panes_tree.tiles.remove(hovered_tile).unwrap(); + let replaced = self.panes_tree.tiles.insert_new(hovered_tile_pane); + let lower_pane = self.panes_tree.tiles.insert_pane(Pane::default()); + self.panes_tree.tiles.insert( hovered_tile, Tile::Container(Container::Linear(Linear::new_binary( LinearDir::Vertical, @@ -83,19 +83,26 @@ impl eframe::App for ComposableView { } PaneAction::Close => { // Ignore if the root pane is the only one - if self.tree.tiles.len() != 1 { - self.tree.remove_recursively(hovered_tile); + if self.panes_tree.tiles.len() != 1 { + self.panes_tree.remove_recursively(hovered_tile); } } PaneAction::Replace(new_pane) => { - self.tree.tiles.insert(hovered_tile, Tile::Pane(*new_pane)); + self.panes_tree + .tiles + .insert(hovered_tile, Tile::Pane(*new_pane)); } } } // A central panel covers the remainder of the screen, i.e. whatever area is left after adding other panels. egui::CentralPanel::default().show(ctx, |ui| { - self.tree.ui(&mut self.behavior, ui); + self.panes_tree.ui(&mut self.behavior, ui); + }); + + // Show a panel at the bottom of the screen with few global controls + egui::TopBottomPanel::bottom("bottom_control").show(ctx, |ui| { + egui::global_theme_preference_switch(ui); }); } } @@ -110,21 +117,22 @@ impl Behavior<Pane> for ComposableBehavior { fn pane_ui( &mut self, ui: &mut egui::Ui, - _tile_id: egui_tiles::TileId, + _tile_id: TileId, pane: &mut Pane, ) -> egui_tiles::UiResponse { let PaneResponse { action_called, drag_response, } = pane.ui(ui); + // Capture the action and store it to be consumed in the update function if let Some(action_called) = action_called { self.action = Some(action_called); } drag_response } - fn tab_title_for_pane(&mut self, pane: &Pane) -> egui::WidgetText { - pane.tab_title() + fn tab_title_for_pane(&mut self, _pane: &Pane) -> egui::WidgetText { + "Tab".into() } } diff --git a/src/ui/panes.rs b/src/ui/panes.rs index 951d14e..9aebb54 100644 --- a/src/ui/panes.rs +++ b/src/ui/panes.rs @@ -7,24 +7,52 @@ use serde::{Deserialize, Serialize}; use super::composable_view::PaneResponse; -#[enum_dispatch(Pane)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Pane { + pub pane: PaneKind, +} + +impl Default for Pane { + fn default() -> Self { + Self { + pane: PaneKind::default(), + } + } +} + +impl Pane { + pub fn boxed(pane: PaneKind) -> Box<Self> { + Box::new(Self { pane }) + } +} + +#[enum_dispatch(PaneKind)] pub trait PaneBehavior { fn ui(&mut self, ui: &mut egui::Ui) -> PaneResponse; - fn tab_title(&self) -> egui::WidgetText; fn contains_pointer(&self) -> bool; } -// An enum to represent the different widgets available to the user. +impl PaneBehavior for Pane { + fn ui(&mut self, ui: &mut egui::Ui) -> PaneResponse { + self.pane.ui(ui) + } + + fn contains_pointer(&self) -> bool { + self.pane.contains_pointer() + } +} + +// An enum to represent the diffent kinds of widget available to the user. #[derive(Clone, Debug, Serialize, Deserialize)] #[enum_dispatch] -pub enum Pane { +pub enum PaneKind { Default(default::DefaultPane), MessagesViewer(messages_viewer::MessagesViewerPane), Plot2D(plot_2d::Plot2DPane), } -impl Default for Pane { +impl Default for PaneKind { fn default() -> Self { - Pane::Default(default::DefaultPane::default()) + PaneKind::Default(default::DefaultPane::default()) } } diff --git a/src/ui/panes/default.rs b/src/ui/panes/default.rs index 16f5d96..780dfa7 100644 --- a/src/ui/panes/default.rs +++ b/src/ui/panes/default.rs @@ -1,4 +1,4 @@ -use super::{plot_2d::Plot2DPane, Pane, PaneBehavior}; +use super::{plot_2d::Plot2DPane, Pane, PaneBehavior, PaneKind}; use serde::{Deserialize, Serialize}; use crate::ui::composable_view::{PaneAction, PaneResponse}; @@ -45,7 +45,7 @@ impl PaneBehavior for DefaultPane { height_occupied += btn.rect.height(); let btn = ui.button("Plot"); if btn.clicked() { - response.set_action(PaneAction::Replace(Box::new(Pane::Plot2D( + response.set_action(PaneAction::Replace(Pane::boxed(PaneKind::Plot2D( Plot2DPane::default(), )))); } @@ -75,10 +75,6 @@ impl PaneBehavior for DefaultPane { response } - fn tab_title(&self) -> egui::WidgetText { - "Default".into() - } - fn contains_pointer(&self) -> bool { self.contains_pointer } diff --git a/src/ui/panes/messages_viewer.rs b/src/ui/panes/messages_viewer.rs index 990882e..cca2803 100644 --- a/src/ui/panes/messages_viewer.rs +++ b/src/ui/panes/messages_viewer.rs @@ -22,10 +22,6 @@ impl PaneBehavior for MessagesViewerPane { response } - fn tab_title(&self) -> egui::WidgetText { - "Messages".into() - } - fn contains_pointer(&self) -> bool { self.contains_pointer } diff --git a/src/ui/panes/plot_2d.rs b/src/ui/panes/plot_2d.rs index 3f1f7c5..a3ad807 100644 --- a/src/ui/panes/plot_2d.rs +++ b/src/ui/panes/plot_2d.rs @@ -5,36 +5,105 @@ use super::PaneBehavior; use egui_plot::{Line, PlotPoints}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Plot2DPane { #[serde(skip)] - pub hovered: bool, + pub contains_pointer: bool, + settings_visible: bool, + n_points: u32, + frequency: f64, + width: f32, + color: egui::Color32, + open: bool, +} + +impl Default for Plot2DPane { + fn default() -> Self { + Self { + contains_pointer: false, + settings_visible: false, + n_points: 2, + frequency: 1.0, + width: 1.0, + color: egui::Color32::from_rgb(0, 120, 240), + open: false, + } + } } impl PaneBehavior for Plot2DPane { fn ui(&mut self, ui: &mut egui::Ui) -> PaneResponse { - let modifiers = ui.input(|i| i.modifiers); let mut response = PaneResponse::default(); + let mut window_visible = self.settings_visible; + egui::Window::new("Plot Settings") + .id(ui.id()) + .auto_sized() + .collapsible(true) + .movable(true) + .open(&mut window_visible) + .show(ui.ctx(), |ui| self.settings_window(ui)); + self.settings_visible = window_visible; + + let ctrl_pressed = ui.input(|i| i.modifiers.ctrl); + let plot = egui_plot::Plot::new("plot"); plot.show(ui, |plot_ui| { - self.hovered = plot_ui.response().contains_pointer(); - if plot_ui.response().dragged() && modifiers.ctrl { + self.contains_pointer = plot_ui.response().contains_pointer(); + if plot_ui.response().dragged() && ctrl_pressed { println!("ctrl + drag"); response.set_drag_started(); } - - plot_ui.line(Line::new(PlotPoints::from(vec![[1.0, 0.0], [2.0, 10.0]]))); + let points: Vec<[f64; 2]> = (0..self.n_points) + .map(|i| i as f64 * 100.0 / (self.n_points - 1) as f64) + .map(|i| [i, (i * std::f64::consts::PI * 2.0 * self.frequency).sin()]) + .collect(); + plot_ui.line( + Line::new(PlotPoints::from(points)) + .color(self.color) + .width(self.width), + ); + plot_ui.response().context_menu(|ui| self.menu(ui)); }); response } - fn tab_title(&self) -> egui::WidgetText { - "Plot".into() + fn contains_pointer(&self) -> bool { + self.contains_pointer } +} - fn contains_pointer(&self) -> bool { - self.hovered +impl Plot2DPane { + fn menu(&mut self, ui: &mut egui::Ui) { + ui.set_max_width(200.0); // To make sure we wrap long text + + if ui.button("Settings…").clicked() { + self.settings_visible = true; + ui.close_menu(); + } + } + + fn settings_window(&mut self, ui: &mut egui::Ui) { + egui::Grid::new(ui.id()) + .num_columns(2) + .spacing([10.0, 5.0]) + .show(ui, |ui| { + ui.label("Size:"); + ui.add(egui::Slider::new(&mut self.n_points, 2..=1000).text("Points")); + ui.end_row(); + + ui.label("Frequency:"); + ui.add(egui::Slider::new(&mut self.frequency, 0.1..=10.0).text("Hz")); + ui.end_row(); + + ui.label("Color:"); + ui.color_edit_button_srgba(&mut self.color); + ui.end_row(); + + ui.label("Width:"); + ui.add(egui::Slider::new(&mut self.width, 0.1..=10.0).text("pt")); + ui.end_row(); + }); } } -- GitLab