First working example of an egui gui
This commit is contained in:
parent
7934a5ca4d
commit
1fd5833d19
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
21
src/main.rs
21
src/main.rs
|
@ -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())),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
240
src/ui.rs
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue