Skip to content
Snippets Groups Projects
Commit 88e0fc7d authored by Federico Lolli's avatar Federico Lolli
Browse files

Added some experimentation with window and settings

parent a92830bf
Branches
No related tags found
No related merge requests found
......@@ -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"
......@@ -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()
}
}
......
......@@ -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())
}
}
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
}
......
......@@ -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
}
......
......@@ -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
}
}
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 contains_pointer(&self) -> bool {
self.hovered
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();
});
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment