Compare commits

...

5 Commits

5 changed files with 384 additions and 190 deletions

View File

@ -14,13 +14,15 @@ use crate::modals::{self, ModalState};
use crate::plate::{add_plate, PlateDisplayOptions, PlateUiState}; use crate::plate::{add_plate, PlateDisplayOptions, PlateUiState};
use crate::transfer_menu::{transfer_menu, CurrentTransferState, TransferMenuState}; use crate::transfer_menu::{transfer_menu, CurrentTransferState, TransferMenuState};
use crate::tree::tree; use crate::tree::tree;
use crate::upper_menu;
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, serde::Serialize, serde::Deserialize)]
struct MainWindowState { pub struct MainWindowState {
show_side_panel: bool, pub show_side_panel: bool,
plate_display_options: PlateDisplayOptions, pub plate_display_options: PlateDisplayOptions,
csv_export_type: CsvExportType, pub csv_export_type: CsvExportType,
pub show_plates_horizontal: bool,
} }
impl Default for MainWindowState { impl Default for MainWindowState {
@ -29,13 +31,14 @@ impl Default for MainWindowState {
show_side_panel: true, show_side_panel: true,
plate_display_options: PlateDisplayOptions::default(), plate_display_options: PlateDisplayOptions::default(),
csv_export_type: CsvExportType::default(), csv_export_type: CsvExportType::default(),
show_plates_horizontal: false,
} }
} }
} }
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, std::default::Default)] #[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, std::default::Default)]
enum CsvExportType { pub enum CsvExportType {
#[default] #[default]
Normal, Normal,
EchoClient, EchoClient,
@ -107,127 +110,13 @@ impl eframe::App for PlateToolEframe {
crate::styling::set_visuals(&ctx); crate::styling::set_visuals(&ctx);
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::menu::bar(ui, |ui| { upper_menu::render_menu_bar(
ui.menu_button("File", |ui| { ui,
if ui.button("New Plate").clicked() { &mut self.main_state,
crate::modals::open_new_plate_modal(&mut self.modal_state); &mut self.modal_state,
} &mut self.current_transfer_state,
ui.menu_button("Export", |ui| { &mut self.main_window_state,
if ui.button("Export as CSV").clicked() { );
let records: Vec<TransferRecord> = self
.main_state
.transfers
.iter()
.flat_map(|transfer| {
let src_barcode = self
.main_state
.source_plates
.iter()
.find(|spi| spi.get_uuid() == transfer.source_id)?;
let dest_barcode = self
.main_state
.destination_plates
.iter()
.find(|dpi| dpi.get_uuid() == transfer.dest_id)?;
if self.main_window_state.csv_export_type
!= CsvExportType::EchoClient
|| self
.main_state
.get_current_source_uuid()
.is_some_and(|x| x != src_barcode.get_uuid())
|| self
.main_state
.get_current_destination_uuid()
.is_some_and(|x| x != dest_barcode.get_uuid())
{
return None;
}
Some(transfer_to_records(
transfer,
&src_barcode.name,
&dest_barcode.name,
))
})
.flatten()
.collect();
let data = match self.main_window_state.csv_export_type {
CsvExportType::Normal => records_to_csv(records),
CsvExportType::EchoClient => records_to_echo_client_csv(records),
};
if let Ok(data) = data {
let bytes: &[u8] = data.as_bytes();
save_file(bytes, Some("transfers.csv"));
}
}
if ui.button("Export as JSON").clicked() {
let data = serde_json::to_string_pretty(&self.main_state);
if let Ok(data) = data {
let bytes: &[u8] = data.as_bytes();
save_file(bytes, Some("plate_tool_state.json"));
}
}
});
ui.menu_button("Import", |ui| {
if ui.button("Import from JSON").clicked() {}
if ui.button("Import transfer from CSV").clicked() {}
});
if ui.button("Reset All").clicked() {
self.main_state = MainState::default();
self.current_transfer_state = CurrentTransferState::default();
}
if ui.button("Dump State").clicked() {
log::warn!("{:?}\n{:?}", self.main_state, self.current_transfer_state);
}
});
ui.menu_button("Options", |ui| {
ui.menu_button("Styles", |ui| {
ui.toggle_value(
&mut self
.main_window_state
.plate_display_options
.show_transfer_hashes,
"Toggle transfer hashes",
);
ui.toggle_value(
&mut self
.main_window_state
.plate_display_options
.show_volume_heatmap,
"Toggle volume heatmap",
);
ui.toggle_value(
&mut self
.main_window_state
.plate_display_options
.show_coordinates,
"Toggle coordinate highlighting",
);
});
ui.menu_button("Exports", |ui| {
ui.menu_button("CSV Export Type", |ui| {
ui.radio_value(
&mut self.main_window_state.csv_export_type,
CsvExportType::Normal,
format!("{}", CsvExportType::Normal),
);
ui.radio_value(
&mut self.main_window_state.csv_export_type,
CsvExportType::EchoClient,
format!("{}", CsvExportType::EchoClient),
);
});
});
ui.menu_button("Windows", |ui| {
ui.toggle_value(
&mut self.main_window_state.show_side_panel,
"Toggle side panel",
);
});
});
});
}); });
if self.main_window_state.show_side_panel { if self.main_window_state.show_side_panel {
@ -262,45 +151,85 @@ impl eframe::App for PlateToolEframe {
ids.sort_unstable(); ids.sort_unstable();
ids ids
}; };
fn add_plates(
ui: &mut egui::Ui,
main_state: &MainState,
current_transfer_state: &CurrentTransferState,
source_plate_state: &Mutex<PlateUiState>,
destination_plate_state: &Mutex<PlateUiState>,
main_window_state: &MainWindowState,
plate_size: egui::Vec2,
ordered_ids: Vec<plate_tool_lib::uuid::Uuid>,
) {
if let Some(source_pi) = main_state.get_current_source_plateinstance() {
add_plate(
plate_size,
source_pi.plate.plate_format,
main_state.get_current_source_transfers(),
plate_tool_lib::plate::PlateType::Source,
&ordered_ids,
&main_state.transfer_region_cache,
Some(&current_transfer_state),
ui,
source_plate_state.lock().unwrap().deref_mut(),
main_window_state.plate_display_options,
);
}
if let Some(destination_pi) = main_state.get_current_destination_plateinstance() {
add_plate(
plate_size,
destination_pi.plate.plate_format,
main_state.get_current_destination_transfers(),
plate_tool_lib::plate::PlateType::Destination,
&ordered_ids,
&main_state.transfer_region_cache,
Some(&current_transfer_state),
ui,
destination_plate_state.lock().unwrap().deref_mut(),
main_window_state.plate_display_options,
);
}
}
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical(|ui| { let available_size = ui.available_size();
let available_size = ui.available_size(); if !self.main_window_state.show_plates_horizontal {
let half_height = { ui.vertical(|ui| {
let mut x = available_size; let plate_size = {
x.y /= 2.0; let mut x = available_size;
x x.y /= 2.0;
}; x
if let Some(source_pi) = self.main_state.get_current_source_plateinstance() { };
add_plate( add_plates(
half_height,
source_pi.plate.plate_format,
self.main_state.get_current_source_transfers(),
plate_tool_lib::plate::PlateType::Source,
&ordered_ids,
&self.main_state.transfer_region_cache,
Some(&self.current_transfer_state),
ui, ui,
self.source_plate_state.lock().unwrap().deref_mut(), &self.main_state,
self.main_window_state.plate_display_options, &self.current_transfer_state,
&self.source_plate_state,
&self.destination_plate_state,
&self.main_window_state,
plate_size,
ordered_ids,
); );
} });
if let Some(destination_pi) = } else {
self.main_state.get_current_destination_plateinstance() ui.horizontal(|ui| {
{ let plate_size = {
add_plate( let mut x = available_size;
half_height, x.x /= 2.0;
destination_pi.plate.plate_format, x
self.main_state.get_current_destination_transfers(), };
plate_tool_lib::plate::PlateType::Destination, add_plates(
&ordered_ids,
&self.main_state.transfer_region_cache,
Some(&self.current_transfer_state),
ui, ui,
self.destination_plate_state.lock().unwrap().deref_mut(), &self.main_state,
self.main_window_state.plate_display_options, &self.current_transfer_state,
&self.source_plate_state,
&self.destination_plate_state,
&self.main_window_state,
plate_size,
ordered_ids,
); );
} });
}); }
}); });
// Modal processing // Modal processing

View File

@ -0,0 +1,37 @@
use eframe::egui;
pub fn drag_value_with_scroll<T>(
ui: &mut egui::Ui,
value: &mut T,
range: std::ops::RangeInclusive<T>,
speed_drag: f64,
speed_scroll: f64,
) -> egui::Response
where
T: egui::emath::Numeric + std::ops::Add<Output = T> + std::fmt::Debug
{
let drag = egui::DragValue::new(value)
.speed(speed_drag)
.range(range.clone());
let response = ui.add(drag);
if response.hovered() {
let scroll_diff = ui.input(|i| i.smooth_scroll_delta.y);
const THRESHOLD: f32 = 10.0;
if scroll_diff > THRESHOLD {
*value = clamp_partial(*value + T::from_f64(speed_scroll), *range.start(), *range.end());
} else if scroll_diff < -THRESHOLD {
*value = clamp_partial(*value + T::from_f64(-speed_scroll), *range.start(), *range.end());
}
}
response
}
fn clamp_partial<T: PartialOrd + Copy + std::fmt::Debug>(val: T, min: T, max: T) -> T {
if val < min { min }
else if val > max { max }
else { val }
}

View File

@ -6,4 +6,6 @@ mod main_state;
mod modals; mod modals;
mod styling; mod styling;
mod file_handling; mod file_handling;
mod upper_menu;
mod extra_widgets;
pub use app::PlateToolEframe; pub use app::PlateToolEframe;

View File

@ -3,6 +3,7 @@ use plate_tool_lib::transfer_region::{self, Region, TransferRegion};
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::extra_widgets;
use crate::main_state::{self, MainState}; use crate::main_state::{self, MainState};
pub type CurrentTransferState = Arc<Mutex<CurrentTransferStateInterior>>; pub type CurrentTransferState = Arc<Mutex<CurrentTransferStateInterior>>;
@ -140,18 +141,30 @@ impl CurrentTransferStateInterior {
None None
} }
pub fn convert_to_transfer(&self, ms: &MainState) -> Option<plate_tool_lib::transfer::Transfer> { pub fn convert_to_transfer(
&self,
ms: &MainState,
) -> Option<plate_tool_lib::transfer::Transfer> {
let source_plate_uuid = ms.get_current_source_uuid()?; let source_plate_uuid = ms.get_current_source_uuid()?;
let destination_plate_uuid = ms.get_current_destination_uuid()?; let destination_plate_uuid = ms.get_current_destination_uuid()?;
let source_plate_instance = ms.source_plates.iter().find(|x| x.get_uuid() == source_plate_uuid)?; let source_plate_instance = ms
let destination_plate_instance = ms.destination_plates.iter().find(|x| x.get_uuid() == destination_plate_uuid)?; .source_plates
.iter()
.find(|x| x.get_uuid() == source_plate_uuid)?;
let destination_plate_instance = ms
.destination_plates
.iter()
.find(|x| x.get_uuid() == destination_plate_uuid)?;
let transfer = Some(plate_tool_lib::transfer::Transfer::new( let transfer = Some(plate_tool_lib::transfer::Transfer::new(
source_plate_instance.clone(), source_plate_instance.clone(),
destination_plate_instance.clone(), destination_plate_instance.clone(),
self.generate_transfer_region(), self.generate_transfer_region(),
self.transfer_name.clone(), self.transfer_name.clone(),
)); ));
transfer.map(|mut x| {x.volume = plate_tool_lib::transfer_volume::TransferVolume::Single(self.volume); x}) transfer.map(|mut x| {
x.volume = plate_tool_lib::transfer_volume::TransferVolume::Single(self.volume);
x
})
} }
} }
@ -170,7 +183,7 @@ impl Default for TransferMenuState {
} }
} }
impl TransferMenuState { impl TransferMenuState {
fn new_from_cts(cts: &CurrentTransferState) -> Self { fn _new_from_cts(cts: &CurrentTransferState) -> Self {
let cts = cts.lock().unwrap(); let cts = cts.lock().unwrap();
let source_region_string = cts.source_region.to_string(); let source_region_string = cts.source_region.to_string();
let destination_region_string = cts.destination_region.to_string(); let destination_region_string = cts.destination_region.to_string();
@ -188,7 +201,7 @@ pub fn transfer_menu(
main_state: &mut MainState, main_state: &mut MainState,
) { ) {
// Can we reduce the length of this lock pls // Can we reduce the length of this lock pls
let cts = state; let _cts = state;
let mut state = state.lock().unwrap(); let mut state = state.lock().unwrap();
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -200,6 +213,17 @@ pub fn transfer_menu(
); );
}); });
ui.horizontal(|ui| {
ui.add(egui::Label::new("Volume"));
extra_widgets::drag_value_with_scroll(
ui,
&mut state.volume,
0.0..=f32::INFINITY,
0.5,
1.0,
);
});
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(egui::Label::new("Source Region")); ui.add(egui::Label::new("Source Region"));
let resp = ui.add( let resp = ui.add(
@ -245,18 +269,20 @@ pub fn transfer_menu(
ui.label("Source Interleave"); ui.label("Source Interleave");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Row: "); ui.label("Row: ");
ui.add( extra_widgets::drag_value_with_scroll(
egui::DragValue::new(&mut state.source_row_interleave) ui,
.fixed_decimals(0) &mut state.source_row_interleave,
.range(1..=30) 0..=30,
.speed(0.1), 0.1,
1.0,
); );
ui.label("Col: "); ui.label("Col: ");
ui.add( extra_widgets::drag_value_with_scroll(
egui::DragValue::new(&mut state.source_column_interleave) ui,
.fixed_decimals(0) &mut state.source_column_interleave,
.range(1..=30) 0..=30,
.speed(0.1), 0.1,
1.0,
); );
}); });
}); });
@ -265,18 +291,20 @@ pub fn transfer_menu(
ui.label("Destination Interleave"); ui.label("Destination Interleave");
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Row: "); ui.label("Row: ");
ui.add( extra_widgets::drag_value_with_scroll(
egui::DragValue::new(&mut state.destination_row_interleave) ui,
.fixed_decimals(0) &mut state.destination_row_interleave,
.range(0..=30) 0..=30,
.speed(0.1), 0.1,
1.0,
); );
ui.label("Col: "); ui.label("Col: ");
ui.add( extra_widgets::drag_value_with_scroll(
egui::DragValue::new(&mut state.destination_column_interleave) ui,
.fixed_decimals(0) &mut state.destination_column_interleave,
.range(0..=30) 0..=30,
.speed(0.1), 0.1,
1.0,
); );
}); });
}); });
@ -285,12 +313,17 @@ pub fn transfer_menu(
if ui.button("Save").clicked() { if ui.button("Save").clicked() {
if let Some(transfer_uuid) = main_state.get_current_transfer_uuid() { if let Some(transfer_uuid) = main_state.get_current_transfer_uuid() {
log::info!("should change transfer"); log::info!("should change transfer");
if let Some(mut transfer) = main_state.transfers.iter_mut().find(|x| x.id == transfer_uuid) { if let Some(transfer) = main_state
.transfers
.iter_mut()
.find(|x| x.id == transfer_uuid)
{
transfer.transfer_region = state.generate_transfer_region(); transfer.transfer_region = state.generate_transfer_region();
transfer.name = state.transfer_name.clone(); transfer.name = state.transfer_name.clone();
main_state.transfer_region_cache.invalidate(&transfer); main_state.transfer_region_cache.invalidate(&transfer);
} }
} else { // Need to make a new transfer } else {
// Need to make a new transfer
if state.transfer_name.is_empty() { if state.transfer_name.is_empty() {
state.transfer_name = "New Transfer".to_string(); state.transfer_name = "New Transfer".to_string();
} }
@ -298,8 +331,10 @@ pub fn transfer_menu(
log::info!("{:?}", new_transfer); log::info!("{:?}", new_transfer);
if let Some(new_transfer) = new_transfer { if let Some(new_transfer) = new_transfer {
main_state.transfers.push(new_transfer); main_state.transfers.push(new_transfer);
let new_transfer = main_state.transfers.last() let new_transfer = main_state
.expect("Cannot be empty, just added a transfer"); .transfers
.last()
.expect("Cannot be empty, just added a transfer");
main_state.transfer_region_cache.add_overwrite(new_transfer); main_state.transfer_region_cache.add_overwrite(new_transfer);
main_state.set_current_transfer(new_transfer.id); main_state.set_current_transfer(new_transfer.id);
} }

View File

@ -0,0 +1,191 @@
use crate::app::{CsvExportType, MainWindowState};
use crate::file_handling::save_file;
use crate::main_state::MainState;
use crate::modals::ModalState;
use crate::transfer_menu::CurrentTransferState;
use plate_tool_lib::csv::{
records_to_csv, records_to_echo_client_csv, transfer_to_records, TransferRecord,
};
use eframe::egui;
pub fn render_menu_bar(
ui: &mut egui::Ui,
main_state: &mut MainState,
modal_state: &mut ModalState,
current_transfer_state: &mut CurrentTransferState,
main_window_state: &mut MainWindowState,
) {
egui::MenuBar::new().ui(ui, |ui| {
ui.menu_button("File", |ui| {
render_file_menu(
ui,
main_state,
modal_state,
current_transfer_state,
main_window_state,
);
});
ui.menu_button("Options", |ui| {
render_options_menu(
ui,
main_state,
modal_state,
current_transfer_state,
main_window_state,
);
});
});
}
fn render_file_menu(
ui: &mut egui::Ui,
main_state: &mut MainState,
modal_state: &mut ModalState,
current_transfer_state: &mut CurrentTransferState,
main_window_state: &mut MainWindowState,
) {
if ui.button("New Plate").clicked() {
crate::modals::open_new_plate_modal(modal_state);
}
ui.menu_button("Export", |ui| {
render_export_menu(
ui,
main_state,
modal_state,
current_transfer_state,
main_window_state,
);
});
ui.menu_button("Import", |ui| {
render_import_menu(
ui,
main_state,
modal_state,
current_transfer_state,
main_window_state,
);
});
if ui.button("Reset All").clicked() {
*main_state = MainState::default();
*current_transfer_state = CurrentTransferState::default();
}
if ui.button("Dump State").clicked() {
log::warn!("{:?}\n{:?}", main_state, current_transfer_state);
}
}
fn render_export_menu(
ui: &mut egui::Ui,
main_state: &mut MainState,
_modal_state: &mut ModalState,
_current_transfer_state: &mut CurrentTransferState,
main_window_state: &mut MainWindowState,
) {
if ui.button("Export as CSV").clicked() {
let records: Vec<TransferRecord> = main_state
.transfers
.iter()
.flat_map(|transfer| {
let src_barcode = main_state
.source_plates
.iter()
.find(|spi| spi.get_uuid() == transfer.source_id)?;
let dest_barcode = main_state
.destination_plates
.iter()
.find(|dpi| dpi.get_uuid() == transfer.dest_id)?;
if main_window_state.csv_export_type != CsvExportType::EchoClient
|| main_state
.get_current_source_uuid()
.is_some_and(|x| x != src_barcode.get_uuid())
|| main_state
.get_current_destination_uuid()
.is_some_and(|x| x != dest_barcode.get_uuid())
{
return None;
}
Some(transfer_to_records(
transfer,
&src_barcode.name,
&dest_barcode.name,
))
})
.flatten()
.collect();
let data = match main_window_state.csv_export_type {
CsvExportType::Normal => records_to_csv(records),
CsvExportType::EchoClient => records_to_echo_client_csv(records),
};
if let Ok(data) = data {
let bytes: &[u8] = data.as_bytes();
save_file(bytes, Some("transfers.csv"));
}
}
if ui.button("Export as JSON").clicked() {
let data = serde_json::to_string_pretty(main_state);
if let Ok(data) = data {
let bytes: &[u8] = data.as_bytes();
save_file(bytes, Some("plate_tool_state.json"));
}
}
}
fn render_import_menu(
ui: &mut egui::Ui,
_main_state: &mut MainState,
_modal_state: &mut ModalState,
_current_transfer_state: &mut CurrentTransferState,
_main_window_state: &mut MainWindowState,
) {
if ui.button("Import from JSON").clicked() {}
if ui.button("Import transfer from CSV").clicked() {}
}
fn render_options_menu(
ui: &mut egui::Ui,
_main_state: &mut MainState,
_modal_state: &mut ModalState,
_current_transfer_state: &mut CurrentTransferState,
main_window_state: &mut MainWindowState,
) {
ui.menu_button("Styles", |ui| {
ui.toggle_value(
&mut main_window_state.plate_display_options.show_transfer_hashes,
"Toggle transfer hashes",
);
ui.toggle_value(
&mut main_window_state.plate_display_options.show_volume_heatmap,
"Toggle volume heatmap",
);
ui.toggle_value(
&mut main_window_state.plate_display_options.show_coordinates,
"Toggle coordinate highlighting",
);
});
ui.menu_button("Exports", |ui| {
ui.menu_button("CSV Export Type", |ui| {
ui.radio_value(
&mut main_window_state.csv_export_type,
CsvExportType::Normal,
format!("{}", CsvExportType::Normal),
);
ui.radio_value(
&mut main_window_state.csv_export_type,
CsvExportType::EchoClient,
format!("{}", CsvExportType::EchoClient),
);
});
});
ui.menu_button("Windows", |ui| {
ui.toggle_value(&mut main_window_state.show_side_panel, "Toggle side panel");
ui.toggle_value(&mut main_window_state.show_plates_horizontal, "Show plates horizontally");
});
}