plate-tool/plate-tool-eframe/src/transfer_menu.rs

262 lines
9.4 KiB
Rust

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<Mutex<CurrentTransferStateInterior>>;
#[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<u64>,
#[serde(skip)]
source_wells: Option<Vec<plate_tool_lib::Well>>,
#[serde(skip)]
destination_wells: Option<Vec<plate_tool_lib::Well>>,
}
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<Self> {
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),
);
});
});
}