Some small refactoring and moving around to make the code less monolitic, as well as some cargo fmt

This commit is contained in:
David Holland 2023-09-22 12:15:18 +02:00
parent 5e3e30c30e
commit 26bf2380df
Signed by: DustVoice
GPG Key ID: 47068995A14EDCA9
5 changed files with 528 additions and 438 deletions

View File

@ -68,10 +68,10 @@ impl Config {
pub fn with_data(mut self, data: HashMap<String, String>) -> Self {
self.data = Some(data);
self
}
pub fn get_file(&self, file: &str) -> Option<&String> {
if let Some(data) = &self.data {
data.get(file)
@ -86,6 +86,7 @@ impl Config {
pub fn get_main_file(&self) -> &String {
let main_filename = &self.get_filename(&self.main_json);
self.get_file(&main_filename).expect("Can't get main file {main_filename}")
self.get_file(&main_filename)
.expect("Can't get main file {main_filename}")
}
}

View File

@ -76,9 +76,7 @@ impl Database {
pub fn from_config(config: &config::Config) -> Self {
if config.data.is_some() {
Database::from_str(
&config.get_main_file()
)
Database::from_str(&config.get_main_file())
} else {
Database::from_file(&config.get_filename(&config.main_json))
}

View File

@ -63,8 +63,8 @@ impl FlowProfile {
}
pub fn from_str(str: &str, cutoff: Option<f64>) -> Self {
let mut brew: Self = serde_json::from_str(&str)
.unwrap_or_else(|_| panic!("Cannot deserialize file"));
let mut brew: Self =
serde_json::from_str(&str).unwrap_or_else(|_| panic!("Cannot deserialize file"));
brew.cutoff = cutoff;
brew

View File

@ -34,7 +34,8 @@ pub fn database_plot_entry(brew: &Brew, config: &Config, database: &Database) ->
if let Some(file) = &config.get_file(&flow_profile_filename) {
flow_profile = FlowProfile::from_str(&file, None).preprocess_json();
} else {
flow_profile = FlowProfile::from_file(&flow_profile_filename, None).preprocess_json();
flow_profile =
FlowProfile::from_file(&flow_profile_filename, None).preprocess_json();
}
if let Some(data_collection) = &flow_profile.data_collection {

948
src/ui.rs
View File

@ -2,8 +2,10 @@
use eframe::egui;
use egui::{plot::{Legend, Line, Plot}, Align, Layout, ProgressBar, Modifiers, Checkbox, collapsing_header, SelectableLabel};
use log::debug;
use egui::{
plot::{Legend, Line, Plot},
Align, Layout, ProgressBar,
};
use rfd::{AsyncFileDialog, AsyncMessageDialog};
use crate::{
@ -14,7 +16,7 @@ use crate::{
use std::{
collections::HashMap,
io::{Read, Cursor}
io::{Cursor, Read},
};
use std::{
env,
@ -34,10 +36,17 @@ struct LoadingData {
#[derive(Default)]
pub struct Ui {
// Program behaviour
allowed_to_close: bool,
continuous_mode: bool,
modal: bool,
// Program status
side_panel_expanded: bool,
show_confirmation_dialog: bool,
show_loading_screen: bool,
// Data loading
loading_progress: Arc<Mutex<LoadingProgress>>,
loading_data: Arc<Mutex<Option<LoadingData>>>,
config_file: Arc<Mutex<Option<String>>>,
@ -45,45 +54,15 @@ pub struct Ui {
data_transfered: bool,
files_provided: Arc<Mutex<bool>>,
// Data
config: Config,
database: Database,
brew_checkboxes: HashMap<String, bool>,
bean_brew_marked: HashMap<String, bool>,
plot_entries: HashMap<String, PlotEntry>,
continuous_mode: bool,
modal: bool,
show_confirmation_dialog: bool,
show_loading_screen: bool,
}
impl Ui {
pub fn get_selected_brew_uuids(&self) -> Vec<String> {
self.brew_checkboxes
.iter()
.filter_map(|(uuid, checked)| match checked {
true => Some(uuid.to_owned()),
false => None,
})
.collect::<Vec<String>>()
}
pub fn get_selected_brews(&self) -> Vec<&Brew> {
self.database
.brews_for_uuids(&self.get_selected_brew_uuids())
}
pub fn reset(&mut self) {
*self = Self::default();
}
pub fn clear_selection(&mut self) {
for checkbox in &mut self.brew_checkboxes {
*checkbox.1 = false;
}
}
// User inputs
brew_checkboxes: HashMap<String, bool>,
bean_brew_marked: HashMap<String, bool>,
}
impl eframe::App for Ui {
@ -93,403 +72,16 @@ impl eframe::App for Ui {
}
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.vertical(|ui| {
egui::menu::bar(ui, |ui| {
let load_button = ui.button(
if !self.data_transfered {
"Load Files"
} else {
"Reload Files"
});
Self::status_bar(ctx);
if load_button.hovered() {
if self.show_loading_screen {
egui::show_tooltip(ctx, egui::Id::new("reload_tooltip"), |ui| {
ui.label("Loading is still in progress.\nTo reload, please wait until previous loading has finished!");
});
}
}
if load_button.clicked() {
self.reset();
let config_file_arc = self.config_file.clone();
let data_archive_arc = self.data_archive.clone();
let files_provided_arc = self.files_provided.clone();
let file_dialog = async move {
let config_message = AsyncMessageDialog::new()
.set_title("Select config file")
.set_description("Please select the config (.toml) file in the next dialog.\n(Default \"config.toml\")")
.set_buttons(rfd::MessageButtons::Ok)
.set_level(rfd::MessageLevel::Info)
.show().await;
if !config_message { return }
let config_file = AsyncFileDialog::new()
.add_filter("toml", &["toml"])
.set_directory(
match &env::current_dir() {
Ok(path) => path.to_str(),
Err(_) => None,
}.unwrap_or_default()
)
.set_title("Config file [.toml]")
.set_file_name("config.toml")
.pick_file().await;
if config_file.is_none() { return }
let config_raw = config_file.unwrap().read().await;
let archive_message = AsyncMessageDialog::new()
.set_title("Select data archive")
.set_description("Please select the data archive (.zip) in the next dialog.\n(Default \"config.toml\")\n\nWihin the zip archive there should be the main json file (e.g. \"Beanconqueror.json\"), as well as a \"brews\" folder, with exact paths specified in the previously selected config file.")
.set_buttons(rfd::MessageButtons::Ok)
.set_level(rfd::MessageLevel::Info)
.show().await;
if !archive_message { return }
let archive_file = AsyncFileDialog::new()
.add_filter("zip", &["zip"])
.set_directory(
match &env::current_dir() {
Ok(path) => path.to_str(),
Err(_) => None,
}.unwrap_or_default()
)
.set_title("Data archive [.zip]")
.set_file_name("beanconqueror.zip")
.pick_file().await;
if archive_file.is_none() { return; }
let archive_raw = archive_file.unwrap().read().await;
let config_content = String::from_utf8(config_raw)
.expect("Can't convert provided config file to UTF-8");
let mut config_file_lock = config_file_arc.lock().unwrap();
*config_file_lock = Some(config_content);
let mut data_archive: Option<HashMap<String, String>> = None;
if let Ok(mut zip) = zip::ZipArchive::new(Cursor::new(archive_raw)) {
let mut unzip: HashMap<String, String> = HashMap::new();
for i in 0..zip.len() {
if let Ok(mut file) = zip.by_index(i) {
if file.is_file() {
let mut content: String = String::new();
if file.read_to_string(&mut content).is_ok() {
if let Some(enclosed_name) = &file.enclosed_name() {
if let Some(filename) = enclosed_name.to_str() {
unzip.insert(filename.to_string(), content);
}
}
}
}
}
}
data_archive = Some(unzip);
}
let mut data_archive_lock = data_archive_arc.lock().unwrap();
*data_archive_lock = data_archive;
let mut files_provided_lock = files_provided_arc.lock().unwrap();
*files_provided_lock = true;
};
#[cfg(not(target_arch = "wasm32"))]
task::spawn(file_dialog);
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(file_dialog);
}
if ui.button("Clear Selection").clicked() {
self.clear_selection();
}
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
egui::widgets::global_dark_light_mode_buttons(ui);
ui.heading("RustyBeans");
});
});
})
});
self.menu_bar(ctx);
if self.modal {
egui::CentralPanel::default().show(ctx, |ui| {
ui.centered_and_justified(|ui| {
if self.show_loading_screen {
let loading_progress_lock = self.loading_progress.lock().unwrap();
ui.add(
ProgressBar::new(loading_progress_lock.percentage.to_owned()).text(
format!(
"{:.0}% | Brew [{}/{}]",
loading_progress_lock.percentage * 100.0,
loading_progress_lock.current,
loading_progress_lock.total
),
),
);
if loading_progress_lock.finished {
self.continuous_mode = false;
self.modal = false;
self.show_loading_screen = false;
}
}
#[cfg(not(target_arch = "wasm32"))]
if self.show_confirmation_dialog {
// Show confirmation dialog:
ui.separator();
ui.vertical(|ui| {
ui.label("Really quit?");
ui.horizontal(|ui| {
if ui.button("Yes").clicked() {
self.allowed_to_close = true;
frame.close();
}
if ui.button("No").clicked() {
self.modal = false;
self.show_confirmation_dialog = false;
}
});
});
}
});
});
self.loading_screen(ctx, frame);
} else {
egui::SidePanel::left("selection_panel")
.resizable(true)
.show_animated(ctx, self.side_panel_expanded, |ui| {
ui.vertical_centered_justified(|ui| {
ui.horizontal_centered(|ui| {
ui.vertical(|ui| {
if self.data_transfered {
ui.heading("Beans with Brews");
egui::ScrollArea::vertical()
.id_source("beans_scroll")
.show(ui, |ui| {
for bean in &self.database.beans {
ui.horizontal(|ui| {
if let Some(brew_marked) = self.bean_brew_marked.get_mut(&bean.config.uuid) {
let id = bean.name_with_date();
ui.collapsing(id, |ui| {
ui.vertical(|ui| {
let mut marked: Option<bool> = None;
let mut flip_all: Option<bool> = None;
let brews = self.database.brews_for_bean(&bean);
for brew in &brews {
ui.horizontal(|ui| {
if let Some(checked) = self
.brew_checkboxes
.get_mut(&brew.config.uuid)
{
let response = ui.toggle_value(
checked,
brew.date_time(&self.database),
);
if response.clicked_by(egui::PointerButton::Primary) {
if ctx.input(|i| i.modifiers.command) {
flip_all = Some(*checked);
*checked = !*checked;
}
}
if *checked {
marked = Some(true);
} else if marked.is_none() {
marked = Some(false);
}
}
});
}
if let Some(value) = flip_all {
self.brew_checkboxes.iter_mut().filter(|brew_checkbox| {
brews.iter().any(|&brew| &brew.config.uuid == brew_checkbox.0)
}).for_each(|brew_checkbox| {
*brew_checkbox.1 = value;
});
marked = Some(value);
}
match marked {
Some(marked) => *brew_marked = marked,
None => *brew_marked = false,
}
});
});
}
});
}
});
}
});
});
});
});
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(Align::Center),
|ui| {
if *self.files_provided.lock().unwrap() {
let loading_finished: bool;
{
loading_finished = self.loading_progress.lock().unwrap().finished;
}
if !loading_finished {
self.continuous_mode = true;
self.modal = true;
self.show_loading_screen = true;
self.data_transfered = false;
let config_file_arc = self.config_file.clone();
let data_archive_arc = self.data_archive.clone();
let loading_data_arc = self.loading_data.clone();
let loading_progress_arc = self.loading_progress.clone();
let ctx_clone = ctx.clone();
let data_loader = async move {
let ctx = ctx_clone;
let mut loading_data = LoadingData::default();
let mut config_file_lock = config_file_arc.lock().unwrap();
let mut data_archive_lock = data_archive_arc.lock().unwrap();
loading_data.config = Config::from_str(&config_file_lock.take().unwrap()).with_data(data_archive_lock.take().unwrap());
loading_data.database = Database::from_config(&loading_data.config);
let mut checkboxes = HashMap::new();
for brew in &loading_data.database.brews {
checkboxes.insert(brew.config.uuid.to_owned(), false);
}
loading_data.brew_checkboxes = checkboxes;
let mut brew_marked = HashMap::new();
for bean in &loading_data.database.beans {
brew_marked.insert(bean.config.uuid.to_owned(), false);
}
loading_data.bean_brew_marked = brew_marked;
loading_data.plot_entries = plot::database_plot_entries(
loading_data
.database
.brews
.iter()
.map(|brew| brew)
.collect(),
&loading_data.config,
&loading_data.database,
Some(loading_progress_arc),
);
let mut loading_data_lock =
loading_data_arc.lock().unwrap();
*loading_data_lock = Some(loading_data);
ctx.request_repaint();
};
#[cfg(not(target_arch = "wasm32"))]
task::spawn(data_loader);
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(data_loader);
} else {
if !self.data_transfered {
self.side_panel_expanded = true;
// println!("loading_data: {:?}", &self.loading_data);
let mut loading_data_lock =
self.loading_data.lock().unwrap();
// println!("loading_data_lock: {:?}", loading_data_lock);
let loading_data = loading_data_lock.take().unwrap();
self.config = loading_data.config;
self.database = loading_data.database;
self.brew_checkboxes = loading_data.brew_checkboxes;
self.bean_brew_marked = loading_data.bean_brew_marked;
self.plot_entries = loading_data.plot_entries;
self.data_transfered = true;
} else {
Plot::new("Combined Chart of selected brews")
.legend(Legend::default())
.label_formatter(|name, value| {
let coord = format!("{:.1}g @ {:.1}s", value.y, value.x);
if !name.is_empty() {
format!("{} [{}]", coord, name)
} else {
coord
}
})
.show(ui, |plot_ui| {
for brew_checkbox in &self.brew_checkboxes {
if *brew_checkbox.1 {
if let Some(plot_points) =
self.plot_entries.get(brew_checkbox.0)
{
plot_ui.line(
Line::new(plot_points_to_owned(
&plot_points.1,
))
.name(plot_points.0.to_owned()),
);
}
}
}
});
}
}
}
},
);
});
});
self.selection_panel(ctx);
self.main_panel(ctx);
}
}
@ -500,3 +92,501 @@ impl eframe::App for Ui {
self.allowed_to_close
}
}
impl Ui {
// General methods
/// Get all UUIDs of all selected Brews
pub fn _get_selected_brew_uuids(&self) -> Vec<String> {
self.brew_checkboxes
.iter()
.filter_map(|(uuid, checked)| match checked {
true => Some(uuid.to_owned()),
false => None,
})
.collect::<Vec<String>>()
}
/// Get all selected brews
pub fn _get_selected_brews(&self) -> Vec<&Brew> {
self.database
.brews_for_uuids(&self._get_selected_brew_uuids())
}
/// Reset the whole program
pub fn reset(&mut self) {
*self = Self::default();
}
/// Uncheck all selected Brews
pub fn clear_selection(&mut self) {
for checkbox in &mut self.brew_checkboxes {
*checkbox.1 = false;
}
}
// Ui data metods
/// Load data from user
fn load_data(&mut self, ctx: &egui::Context) {
self.continuous_mode = true;
self.modal = true;
self.show_loading_screen = true;
self.data_transfered = false;
let config_file_arc = self.config_file.clone();
let data_archive_arc = self.data_archive.clone();
let loading_data_arc = self.loading_data.clone();
let loading_progress_arc = self.loading_progress.clone();
let ctx_clone = ctx.clone();
let data_loader = async move {
let ctx = ctx_clone;
let mut loading_data = LoadingData::default();
let mut config_file_lock = config_file_arc.lock().unwrap();
let mut data_archive_lock = data_archive_arc.lock().unwrap();
loading_data.config = Config::from_str(&config_file_lock.take().unwrap())
.with_data(data_archive_lock.take().unwrap());
loading_data.database = Database::from_config(&loading_data.config);
let mut checkboxes = HashMap::new();
for brew in &loading_data.database.brews {
checkboxes.insert(brew.config.uuid.to_owned(), false);
}
loading_data.brew_checkboxes = checkboxes;
let mut brew_marked = HashMap::new();
for bean in &loading_data.database.beans {
brew_marked.insert(bean.config.uuid.to_owned(), false);
}
loading_data.bean_brew_marked = brew_marked;
loading_data.plot_entries = plot::database_plot_entries(
loading_data
.database
.brews
.iter()
.map(|brew| brew)
.collect(),
&loading_data.config,
&loading_data.database,
Some(loading_progress_arc),
);
let mut loading_data_lock = loading_data_arc.lock().unwrap();
*loading_data_lock = Some(loading_data);
ctx.request_repaint();
};
#[cfg(not(target_arch = "wasm32"))]
task::spawn(data_loader);
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(data_loader);
}
/// Transfer the acquired data into the "native" fields
fn transfer_data(&mut self) {
self.side_panel_expanded = true;
// println!("loading_data: {:?}", &self.loading_data);
let mut loading_data_lock = self.loading_data.lock().unwrap();
// println!("loading_data_lock: {:?}", loading_data_lock);
let loading_data = loading_data_lock.take().unwrap();
self.config = loading_data.config;
self.database = loading_data.database;
self.brew_checkboxes = loading_data.brew_checkboxes;
self.bean_brew_marked = loading_data.bean_brew_marked;
self.plot_entries = loading_data.plot_entries;
self.data_transfered = true;
}
/// Display file dialog that acquires the data and stores it asynchronously
async fn load_file_dialog(
config_file_arc: Arc<Mutex<Option<String>>>,
data_archive_arc: Arc<Mutex<Option<HashMap<String, String>>>>,
files_provided_arc: Arc<Mutex<bool>>,
) {
let config_message = AsyncMessageDialog::new()
.set_title("Select config file")
.set_description("Please select the config (.toml) file in the next dialog.\n(Default \"config.toml\")")
.set_buttons(rfd::MessageButtons::Ok)
.set_level(rfd::MessageLevel::Info)
.show().await;
if !config_message {
return;
}
let config_file = AsyncFileDialog::new()
.add_filter("toml", &["toml"])
.set_directory(
match &env::current_dir() {
Ok(path) => path.to_str(),
Err(_) => None,
}
.unwrap_or_default(),
)
.set_title("Config file [.toml]")
.set_file_name("config.toml")
.pick_file()
.await;
if config_file.is_none() {
return;
}
let config_raw = config_file.unwrap().read().await;
let archive_message = AsyncMessageDialog::new()
.set_title("Select data archive")
.set_description("Please select the data archive (.zip) in the next dialog.\n\
(Default \"config.toml\")\n\n\
Wihin the zip archive there should be the main json file (e.g. \"Beanconqueror.json\"),\
as well as a \"brews\" folder, with exact paths specified in the previously selected config file.")
.set_buttons(rfd::MessageButtons::Ok)
.set_level(rfd::MessageLevel::Info)
.show().await;
if !archive_message {
return;
}
let archive_file = AsyncFileDialog::new()
.add_filter("zip", &["zip"])
.set_directory(
match &env::current_dir() {
Ok(path) => path.to_str(),
Err(_) => None,
}
.unwrap_or_default(),
)
.set_title("Data archive [.zip]")
.set_file_name("beanconqueror.zip")
.pick_file()
.await;
if archive_file.is_none() {
return;
}
let archive_raw = archive_file.unwrap().read().await;
let config_content =
String::from_utf8(config_raw).expect("Can't convert provided config file to UTF-8");
let mut config_file_lock = config_file_arc.lock().unwrap();
*config_file_lock = Some(config_content);
let mut data_archive: Option<HashMap<String, String>> = None;
if let Ok(mut zip) = zip::ZipArchive::new(Cursor::new(archive_raw)) {
let mut unzip: HashMap<String, String> = HashMap::new();
for i in 0..zip.len() {
if let Ok(mut file) = zip.by_index(i) {
if file.is_file() {
let mut content: String = String::new();
if file.read_to_string(&mut content).is_ok() {
if let Some(enclosed_name) = &file.enclosed_name() {
if let Some(filename) = enclosed_name.to_str() {
unzip.insert(filename.to_string(), content);
}
}
}
}
}
}
data_archive = Some(unzip);
}
let mut data_archive_lock = data_archive_arc.lock().unwrap();
*data_archive_lock = data_archive;
let mut files_provided_lock = files_provided_arc.lock().unwrap();
*files_provided_lock = true;
}
// Ui component metods
fn status_bar(ctx: &egui::Context) {
egui::TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
ui.columns(2, |_columns| {});
});
}
/// Draws a menu bar
fn menu_bar(&mut self, ctx: &egui::Context) {
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
ui.vertical(|ui| {
egui::menu::bar(ui, |ui| {
// Load button
self.load_button(ui, ctx);
// Clear selection button
if ui.button("Clear Selection").clicked() {
self.clear_selection();
}
// Program title with dark/light mode switch
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
egui::widgets::global_dark_light_mode_buttons(ui);
ui.heading("RustyBeans");
});
});
})
});
}
/// Draws the load button for the data
fn load_button(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
// Display (Re)load files depending on the loading state
let load_button = ui.button(if !self.data_transfered {
"Load Files"
} else {
"Reload Files"
});
// If still in progress, show warning on button hover
if load_button.hovered() {
if self.show_loading_screen {
egui::show_tooltip(ctx, egui::Id::new("reload_tooltip"), |ui| {
ui.label("Loading is still in progress.\nTo reload, please wait until previous loading has finished!");
});
}
}
// Data loading procedure
if load_button.clicked() {
self.reset();
let config_file_arc = self.config_file.clone();
let data_archive_arc = self.data_archive.clone();
let files_provided_arc = self.files_provided.clone();
// File dialog that handles data acquisition
let file_dialog =
Self::load_file_dialog(config_file_arc, data_archive_arc, files_provided_arc);
// On Desktop
#[cfg(not(target_arch = "wasm32"))]
task::spawn(file_dialog);
// On web
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(file_dialog);
}
}
/// Draws the loading screen with the progress bar
fn loading_screen(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.centered_and_justified(|ui| {
if self.show_loading_screen {
let loading_progress_lock = self.loading_progress.lock().unwrap();
ui.add(
ProgressBar::new(loading_progress_lock.percentage.to_owned()).text(
format!(
"{:.0}% | Brew [{}/{}]",
loading_progress_lock.percentage * 100.0,
loading_progress_lock.current,
loading_progress_lock.total
),
),
);
if loading_progress_lock.finished {
self.continuous_mode = false;
self.modal = false;
self.show_loading_screen = false;
}
}
#[cfg(not(target_arch = "wasm32"))]
if self.show_confirmation_dialog {
// Show confirmation dialog:
ui.separator();
ui.vertical(|ui| {
ui.label("Really quit?");
ui.horizontal(|ui| {
if ui.button("Yes").clicked() {
self.allowed_to_close = true;
frame.close();
}
if ui.button("No").clicked() {
self.modal = false;
self.show_confirmation_dialog = false;
}
});
});
}
});
});
}
/// Draw Bean -> Brew selection panel
fn selection_panel(&mut self, ctx: &egui::Context) {
egui::SidePanel::left("selection_panel")
.resizable(true)
.show_animated(ctx, self.side_panel_expanded, |ui| {
ui.vertical_centered_justified(|ui| {
ui.horizontal_centered(|ui| {
ui.vertical(|ui| {
if self.data_transfered {
ui.heading("Beans with Brews");
egui::ScrollArea::vertical()
.id_source("beans_scroll")
.show(ui, |ui| {
for bean in &self.database.beans {
ui.horizontal(|ui| {
if let Some(brew_marked) = self.bean_brew_marked.get_mut(&bean.config.uuid) {
let id = bean.name_with_date();
ui.collapsing(id, |ui| {
ui.vertical(|ui| {
let mut marked: Option<bool> = None;
let mut flip_all: Option<bool> = None;
let brews = self.database.brews_for_bean(&bean);
for brew in &brews {
ui.horizontal(|ui| {
if let Some(checked) = self
.brew_checkboxes
.get_mut(&brew.config.uuid)
{
let response = ui.toggle_value(
checked,
brew.date_time(&self.database),
);
if response.clicked_by(egui::PointerButton::Primary) {
if ctx.input(|i| i.modifiers.command) {
flip_all = Some(*checked);
*checked = !*checked;
}
}
if *checked {
marked = Some(true);
} else if marked.is_none() {
marked = Some(false);
}
}
});
}
if let Some(value) = flip_all {
self.brew_checkboxes.iter_mut().filter(|brew_checkbox| {
brews.iter().any(|&brew| &brew.config.uuid == brew_checkbox.0)
}).for_each(|brew_checkbox| {
*brew_checkbox.1 = value;
});
marked = Some(value);
}
match marked {
Some(marked) => *brew_marked = marked,
None => *brew_marked = false,
}
});
});
}
});
}
});
}
});
});
});
});
}
/// Draw main panel, depending on loading status
fn main_panel(&mut self, ctx: &egui::Context) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
self.side_panel(ui);
ui.with_layout(
Layout::top_down(Align::Center).with_main_align(Align::Center),
|ui| {
if *self.files_provided.lock().unwrap() {
let loading_finished: bool;
{
loading_finished = self.loading_progress.lock().unwrap().finished;
}
if !loading_finished {
self.load_data(ctx);
} else {
if !self.data_transfered {
self.transfer_data();
} else {
self.plot(ui);
}
}
}
},
);
});
});
}
/// Draw side panel
fn side_panel(&mut self, ui: &mut egui::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;
}
},
);
}
/// Draw plot
fn plot(&self, ui: &mut egui::Ui) {
Plot::new("Combined Chart of selected brews")
.legend(Legend::default())
.label_formatter(|name, value| {
let coord = format!("{:.1}g @ {:.1}s", value.y, value.x);
if !name.is_empty() {
format!("{} [{}]", coord, name)
} else {
coord
}
})
.show(ui, |plot_ui| {
for brew_checkbox in &self.brew_checkboxes {
if *brew_checkbox.1 {
if let Some(plot_points) = self.plot_entries.get(brew_checkbox.0) {
plot_ui.line(
Line::new(plot_points_to_owned(&plot_points.1))
.name(plot_points.0.to_owned()),
);
}
}
}
});
}
}