use eframe::egui; use plate_tool_lib::transfer_region::{self, Region, TransferRegion}; use std::sync::{Arc, Mutex}; use std::hash::{DefaultHasher, Hash, Hasher}; use crate::main_state::MainState; pub type CurrentTransferState = Arc>; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct CurrentTransferStateInterior { pub transfer_name: String, pub source_region: plate_tool_lib::transfer_region::Region, pub destination_region: plate_tool_lib::transfer_region::Region, pub source_plate: plate_tool_lib::plate::Plate, pub destination_plate: plate_tool_lib::plate::Plate, pub source_row_interleave: i8, pub source_column_interleave: i8, pub destination_row_interleave: i8, pub destination_column_interleave: i8, pub volume: f32, #[serde(skip)] transfer_hash: Option, #[serde(skip)] source_wells: Option>, #[serde(skip)] destination_wells: Option>, } impl Default for CurrentTransferStateInterior { fn default() -> Self { Self { transfer_name: String::default(), source_region: plate_tool_lib::transfer_region::Region::Point(plate_tool_lib::Well { row: 1, col: 1, }), destination_region: plate_tool_lib::transfer_region::Region::Point( plate_tool_lib::Well { row: 1, col: 1 }, ), source_plate: plate_tool_lib::plate::SOURCE_W96, destination_plate: plate_tool_lib::plate::DESTINATION_W96, source_row_interleave: 1, source_column_interleave: 1, destination_row_interleave: 1, destination_column_interleave: 1, volume: 5.0, transfer_hash: None, source_wells: None, destination_wells: None, } } } impl CurrentTransferStateInterior { pub fn get_source_wells(&mut self) -> Box<[plate_tool_lib::Well]> { if self.source_wells.is_none() { self.recalulate_wells(); } self.recalculate_wells_if_needed(); self.source_wells .clone() .expect("Source wells must have been calculated by here.") .into_boxed_slice() } pub fn get_destination_wells(&mut self) -> Box<[plate_tool_lib::Well]> { if self.destination_wells.is_none() { self.recalulate_wells(); } self.recalculate_wells_if_needed(); self.destination_wells .clone() .expect("Destination wells must have been calculated by here.") .into_boxed_slice() } fn recalculate_wells_if_needed(&mut self) { if self.transfer_hash.is_none() { self.recalulate_wells(); } if self.transfer_hash.unwrap() != self.calculate_hash() { self.recalulate_wells(); } } fn recalulate_wells(&mut self) { let tr = self.generate_transfer_region(); self.source_wells = Some(tr.get_source_wells()); self.destination_wells = Some(tr.get_destination_wells()); self.transfer_hash = Some(self.calculate_hash()); } fn calculate_hash(&self) -> u64 { let mut hasher = DefaultHasher::new(); self.generate_transfer_region().hash(&mut hasher); hasher.finish() } fn generate_transfer_region(&self) -> TransferRegion { TransferRegion { source_plate: self.source_plate, source_region: self.source_region.clone(), dest_plate: self.destination_plate, dest_region: self.destination_region.clone(), interleave_source: (self.source_row_interleave, self.source_column_interleave), interleave_dest: (self.destination_row_interleave, self.destination_column_interleave), } } pub fn try_new_from_main_state_transfer(ms: &MainState) -> Option { let transfer_id = ms.get_current_transfer(); let transfer = ms.transfers.iter().find(|x| Some(x.get_uuid()) == transfer_id); if let Some(transfer) = transfer { let volume: f32 = match transfer.volume { plate_tool_lib::transfer_volume::TransferVolume::Single(x) => x, plate_tool_lib::transfer_volume::TransferVolume::WellMap(_) => { log::debug!("COOL BUG: I genuinely don't know when this variant is used and honestly assume that it just never is constructed! Anyway, here's main state:\n{:?}", ms); unreachable!("It better not!"); }, _ => unreachable!() }; return Some(Self { transfer_name: transfer.name.clone(), source_region: transfer.transfer_region.source_region.clone(), destination_region: transfer.transfer_region.dest_region.clone(), source_plate: transfer.transfer_region.source_plate, destination_plate: transfer.transfer_region.dest_plate, source_row_interleave: transfer.transfer_region.interleave_source.0, source_column_interleave: transfer.transfer_region.interleave_source.1, destination_row_interleave: transfer.transfer_region.interleave_dest.0, destination_column_interleave: transfer.transfer_region.interleave_dest.1, volume, source_wells: None, destination_wells: None, transfer_hash: None, }) } None } } #[derive(Debug)] pub struct TransferMenuState { pub source_region_string: String, pub destination_region_string: String, } impl Default for TransferMenuState { fn default() -> Self { TransferMenuState { source_region_string: String::new(), destination_region_string: String::new(), } } } impl TransferMenuState { fn new_from_cts(cts: &CurrentTransferState) -> Self { let cts = cts.lock().unwrap(); let source_region_string = cts.source_region.to_string(); let destination_region_string = cts.destination_region.to_string(); TransferMenuState { source_region_string, destination_region_string, } } } pub fn transfer_menu( ui: &mut egui::Ui, state: &CurrentTransferState, ui_state: &mut TransferMenuState, ) { // Can we reduce the length of this lock pls let mut state = state.lock().unwrap(); ui.horizontal(|ui| { ui.add(egui::Label::new("Name")); ui.add( egui::TextEdit::singleline(&mut state.transfer_name) .hint_text("Transfer Name") .horizontal_align(egui::Align::Center), ); }); ui.horizontal(|ui| { ui.add(egui::Label::new("Source Region")); let resp = ui.add( egui::TextEdit::singleline(&mut ui_state.source_region_string) .hint_text("Source Region") .char_limit(9) .horizontal_align(egui::Align::Center), ); if resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { if let Some(new_region) = Region::try_parse_str(&ui_state.source_region_string) { state.source_region = new_region; } else { log::warn!("Invalid source region entered.") } } if !resp.has_focus() && !resp.lost_focus() { ui_state.source_region_string = state.source_region.to_string(); } }); ui.end_row(); ui.horizontal(|ui| { ui.add(egui::Label::new("Destination Region")); let resp = ui.add( egui::TextEdit::singleline(&mut ui_state.destination_region_string) .hint_text("Destination Region") .char_limit(9) .horizontal_align(egui::Align::Center), ); if resp.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { if let Some(new_region) = Region::try_parse_str(&ui_state.destination_region_string) { state.destination_region = new_region; } else { log::warn!("Invalid destination region entered.") } } if !resp.has_focus() { ui_state.destination_region_string = state.destination_region.to_string(); } }); ui.vertical(|ui| { ui.label("Source Interleave"); ui.horizontal(|ui| { ui.label("Row: "); ui.add( egui::DragValue::new(&mut state.source_row_interleave) .fixed_decimals(0) .range(1..=30) .speed(0.1), ); ui.label("Col: "); ui.add( egui::DragValue::new(&mut state.source_column_interleave) .fixed_decimals(0) .range(1..=30) .speed(0.1), ); }); }); ui.vertical(|ui| { ui.label("Destination Interleave"); ui.horizontal(|ui| { ui.label("Row: "); ui.add( egui::DragValue::new(&mut state.destination_row_interleave) .fixed_decimals(0) .range(0..=30) .speed(0.1), ); ui.label("Col: "); ui.add( egui::DragValue::new(&mut state.destination_column_interleave) .fixed_decimals(0) .range(0..=30) .speed(0.1), ); }); }); }