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

324 lines
12 KiB
Rust

use eframe::egui;
use plate_tool_lib::transfer_region::{self, Region, TransferRegion};
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::{Arc, Mutex};
use crate::main_state::{self, 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_uuid();
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::warn!("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
}
pub fn convert_to_transfer(&self, ms: &MainState) -> Option<plate_tool_lib::transfer::Transfer> {
let source_plate_uuid = ms.get_current_source_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 destination_plate_instance = ms.destination_plates.iter().find(|x| x.get_uuid() == destination_plate_uuid)?;
let transfer = Some(plate_tool_lib::transfer::Transfer::new(
source_plate_instance.clone(),
destination_plate_instance.clone(),
self.generate_transfer_region(),
self.transfer_name.clone(),
));
transfer.map(|mut x| {x.volume = plate_tool_lib::transfer_volume::TransferVolume::Single(self.volume); x})
}
}
#[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,
main_state: &mut MainState,
) {
// Can we reduce the length of this lock pls
let cts = state;
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),
);
});
});
ui.horizontal(|ui| {
if ui.button("Save").clicked() {
if let Some(transfer_uuid) = main_state.get_current_transfer_uuid() {
log::info!("should change transfer");
if let Some(mut transfer) = main_state.transfers.iter_mut().find(|x| x.id == transfer_uuid) {
transfer.transfer_region = state.generate_transfer_region();
transfer.name = state.transfer_name.clone();
main_state.transfer_region_cache.invalidate(&transfer);
}
} else { // Need to make a new transfer
if state.transfer_name.is_empty() {
state.transfer_name = "New Transfer".to_string();
}
let new_transfer = state.convert_to_transfer(main_state);
log::info!("{:?}", new_transfer);
if let Some(new_transfer) = new_transfer {
main_state.transfers.push(new_transfer);
let new_transfer = main_state.transfers.last()
.expect("Cannot be empty, just added a transfer");
main_state.transfer_region_cache.add_overwrite(new_transfer);
main_state.set_current_transfer(new_transfer.id);
}
}
}
if ui.button("New").clicked() {
*state = CurrentTransferStateInterior::default();
set_plates(main_state, &mut state);
main_state.set_no_current_transfer();
}
});
}
fn set_plates(ms: &MainState, cts: &mut CurrentTransferStateInterior) {
if let Some(source_plate) = ms.get_current_source_plateinstance() {
cts.source_plate = source_plate.plate;
}
if let Some(destination_plate) = ms.get_current_destination_plateinstance() {
cts.destination_plate = destination_plate.plate;
}
}