First working example of an egui gui

This commit is contained in:
David Holland 2023-01-27 17:02:01 +01:00
parent 7934a5ca4d
commit 1fd5833d19
Signed by: DustVoice
GPG Key ID: 47068995A14EDCA9
8 changed files with 2079 additions and 73 deletions

1759
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,5 +19,25 @@ toml = "0.5.9"
indicatif = "0.17.3"
dialoguer = "0.10.3"
notify-rust = "4.7.0"
tui = "0.19.0"
crossterm = "0.25.0"
rfd = "0.10.0"
egui = { git = "https://github.com/emilk/egui", branch = "master" }
eframe = { git = "https://github.com/emilk/egui", branch = "master" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3"
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6"
tracing-wasm = "0.2"
wasm-bindgen-futures = "0.4"
[profile.release]
opt-level = 2 # fast and small wasm
# Optimize all dependencies even in debug builds:
[profile.dev.package."*"]
opt-level = 2

View File

@ -3,11 +3,11 @@ use serde::{Deserialize, Serialize};
use std::fs;
use crate::config;
use crate::time::unix_to_human_date;
use crate::time::{unix_to_human_date, unix_to_human_time};
/// Structs for handling the Beanconqueror database "Beanconqueror.json" export data
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Database {
#[serde(rename = "BEANS")]
pub beans: Vec<Bean>,
@ -22,39 +22,39 @@ pub struct Database {
pub preparation: Vec<Preparation>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Bean {
pub name: String,
pub config: Config,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Brew {
pub bean: String,
pub config: Config,
pub flow_profile: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Mill {
pub name: String,
pub config: Config,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Preparation {
pub name: String,
pub config: Config,
pub tools: Vec<Tools>,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Tools {
pub name: String,
pub config: Config,
}
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Config {
pub uuid: String,
pub unix_timestamp: i64,
@ -77,26 +77,14 @@ impl Database {
pub fn bean_names(&self) -> Vec<String> {
self.beans
.iter()
.map(|bean| {
format!(
"{} from {}",
bean.name.clone(),
unix_to_human_date(bean.config.unix_timestamp).clone()
)
})
.map(|bean| bean.name_with_date())
.collect()
}
pub fn bean_names_with_uuids(&self) -> Vec<(String, String)> {
self.beans
.iter()
.map(|bean| {
(format!(
"{} from {}",
bean.name.clone(),
unix_to_human_date(bean.config.unix_timestamp).clone()
), bean.config.uuid.clone())
})
.map(|bean| (bean.name_with_date(), bean.config.uuid.clone()))
.collect()
}
@ -127,3 +115,19 @@ impl Database {
// TODO: Preparation, Tools, maybe transiton to HashMaps?
}
impl Bean {
pub fn name_with_date(&self) -> String {
format!(
"{} from {}",
self.name.clone(),
unix_to_human_date(self.config.unix_timestamp.clone())
)
}
}
impl Brew {
pub fn name_with_time(&self) -> String {
unix_to_human_time(self.config.unix_timestamp.clone())
}
}

View File

@ -6,22 +6,31 @@ mod plot;
mod sheets;
mod time;
use ui::ui;
use ui::Ui;
use crate::plot::database_plot_selected_tui;
use crate::plot::generate_plots;
extern crate console_error_panic_hook;
use std::io;
use std::panic;
fn main() -> Result<(), io::Error> {
use eframe::egui;
fn main() -> Result<(), eframe::Error>{
panic::set_hook(Box::new(console_error_panic_hook::hook));
// generate_plots();
// database_plot_selected_tui();
ui()?;
Ok(())
let options = eframe::NativeOptions {
drag_and_drop_support: true,
min_window_size: Some(egui::vec2(320.0, 100.0)),
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eframe::run_native(
"RustyBeans",
options,
Box::new(|_cc| Box::new(Ui::default())),
)
}

View File

@ -83,9 +83,7 @@ pub fn generate_plots() -> Vec<(String, Plot)> {
result
}
pub fn database_plot_selected(uuids: Vec<String>) -> Vec<(String, Plot)> {
let config = Config::from_default();
pub fn database_plot_selected(uuids: Vec<String>, config: &Config) -> Vec<(String, Plot)> {
let mut result: Vec<(String, Plot)> = Vec::with_capacity(config.charts.len());
let database = Database::from_config(&config);

View File

@ -40,6 +40,14 @@ pub fn unix_to_human_date(unix_timestamp: i64) -> String {
}
}
pub fn unix_to_human_time(unix_timestamp: i64) -> String {
if let Some(date_time) = NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
date_time.format("%I:%M %P").to_string()
} else {
"Unknown time".to_string()
}
}
pub fn unix_to_machine_date(unix_timestamp: i64) -> String {
if let Some(date_time) = NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
date_time.format("%F").to_string()

240
src/ui.rs
View File

@ -1,9 +1,235 @@
use std::io;
use tui::{backend::CrosstermBackend, Terminal};
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
pub fn ui() -> Result<(), io::Error> {
let stdout = io::stdout();
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
Ok(())
use eframe::egui;
use egui::{Align, Layout};
use rfd::FileDialog;
use crate::{
config::Config,
database::{Bean, Database},
};
use std::collections::HashMap;
#[derive(Default)]
pub struct Ui {
allowed_to_close: bool,
show_confirmation_dialog: bool,
side_panel_expanded: bool,
database_loaded: bool,
config: Option<Config>,
database: Option<Database>,
selected_bean:
bean_checkboxes: Option<HashMap<String, bool>>,
brew_checkboxes: Option<HashMap<String, bool>>,
picked_path: Option<String>,
}
impl eframe::App for Ui {
fn on_close_event(&mut self) -> bool {
self.show_confirmation_dialog = true;
self.allowed_to_close
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
self.allowed_to_close = true;
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
ui.columns(2, |columns| {});
});
egui::TopBottomPanel::top("dark_light").show(ctx, |ui| {
ui.columns(2, |columns| {
columns[0].heading("RustyBeans");
columns[1].with_layout(Layout::right_to_left(Align::Min), |ui| {
egui::widgets::global_dark_light_mode_buttons(ui);
});
});
});
egui::SidePanel::left("selection_panel")
.resizable(true)
.show_animated(ctx, self.side_panel_expanded, |ui| {
ui.vertical_centered_justified(|ui| {
let total_height = ui.available_height();
ui.horizontal_centered(|ui| {
ui.vertical(|ui| {
if let Some(bean_checkboxes) = self.bean_checkboxes.as_mut() {
if let Some(database) = &self.database {
ui.heading("Beans");
egui::ScrollArea::vertical()
.id_source("beans_scroll")
.max_height(total_height / 2.0)
.show(
ui,
|ui| {
for bean in &database.beans {
ui.horizontal(|ui| {
if let Some(checked) = bean_checkboxes
.get_mut(&bean.config.uuid)
{
ui.checkbox(
checked,
bean.name_with_date(),
);
}
});
}
},
);
}
}
});
ui.vertical(|ui| {
if let Some(brew_checkboxes) = self.brew_checkboxes.as_mut() {
if let Some(database) = &self.database {
ui.heading("Brews");
egui::ScrollArea::vertical()
.id_source("brews_scroll")
.max_height(total_height / 2.0)
.show(ui, |ui| {
for brew in &database.brews {
ui.horizontal(|ui| {
if let Some(checked) =
brew_checkboxes.get_mut(&brew.config.uuid)
{
ui.checkbox(checked, brew.name_with_time());
}
});
}
});
}
}
});
});
ui.horizontal_centered(|ui| {
ui.vertical(|ui| {
if let Some(brew_checkboxes) = self.brew_checkboxes.as_mut() {
if let Some(database) = &self.database {
ui.heading("Selected Brews");
egui::ScrollArea::vertical()
.id_source("selected_brews_scroll")
.max_height(total_height / 2.0)
.show(ui, |ui| {
for brew in &database.brews {
ui.horizontal(|ui| {
if let Some(checked) =
brew_checkboxes.get_mut(&brew.config.uuid)
{
ui.checkbox(checked, brew.name_with_time());
}
});
}
});
}
}
});
});
});
});
egui::CentralPanel::default().show(ctx, |ui| {
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
ui.with_layout(
Layout::left_to_right(Align::Center)
.with_cross_align(Align::Center)
.with_cross_justify(true),
|ui| {
if ui
.button(match self.side_panel_expanded {
true => "<",
false => ">",
})
.clicked()
{
self.side_panel_expanded = !self.side_panel_expanded;
}
},
);
ui.with_layout(
Layout::top_down(Align::Center).with_main_align(egui::Align::Center),
|ui| {
if let Some(picked_path) = &self.picked_path {
if !self.database_loaded {
self.config = Some(Config::from_file(&picked_path));
if let Some(config) = &self.config {
self.database = Some(Database::from_config(&config));
if let Some(database) = &self.database {
let mut checkboxes = HashMap::new();
for bean in &database.beans {
checkboxes.insert(bean.config.uuid.clone(), false);
}
self.bean_checkboxes = Some(checkboxes);
checkboxes = HashMap::new();
for brew in &database.brews {
checkboxes.insert(brew.config.uuid.clone(), false);
}
self.brew_checkboxes = Some(checkboxes);
self.database_loaded = true;
}
}
}
} else {
ui.label("Select the config.toml to start");
if ui.button("Open file").clicked() {
if let Some(path) = FileDialog::new()
.add_filter("toml", &["toml"])
.set_directory(
match &env::current_dir() {
Ok(path) => path.to_str(),
Err(_) => None,
}
.unwrap_or_default(),
)
.pick_file()
{
self.picked_path = Some(path.display().to_string());
}
}
}
},
);
});
});
if self.show_confirmation_dialog {
// Show confirmation dialog:
egui::Window::new("Really quit?")
.collapsible(false)
.resizable(false)
.show(ctx, |ui| {
ui.horizontal(|ui| {
if ui.button("Yes").clicked() {
self.allowed_to_close = true;
frame.close();
}
if ui.button("No").clicked() {
self.show_confirmation_dialog = false;
}
});
});
}
}
}

50
src/web.rs Normal file
View File

@ -0,0 +1,50 @@
use yew::prelude::*;
use crate::config::Config;
use crate::database::Database;
use crate::plot::database_plot_selected;
use crate::plot::database_plot_selected_tui;
use std::process::Command;
#[function_component(App)]
pub fn plot_component() -> Html {
let config = Config::from_default();
let database = Database::from_config(&config);
let plots = database_plot_selected(
database
.brews
.iter()
.map(|brew| brew.config.uuid.clone())
.collect(),
true,
);
let plot_names: Vec<String> = plots.clone().into_iter().map(|plot| plot.0.clone()).collect();
let p = yew_hooks::use_async::<_, _, ()>({
async move {
for plot in &plots {
plotly::bindings::new_plot(&plot.0, &plot.1).await;
}
Ok(())
}
});
use_effect_with_deps(
move |_| {
p.run();
|| ()
},
(),
);
html! {
{
for plot_names.into_iter().map(|name| {
html!{<div id={name}></div>}
})
}
}
}