Compare commits
5 Commits
ce0e1f2743
...
619f9594cf
Author | SHA1 | Date |
---|---|---|
Emilia Allison | 619f9594cf | |
Emilia Allison | 2ea2963b22 | |
Emilia Allison | 949822f26c | |
Emilia Allison | 453ad9ed35 | |
Emilia Allison | d296854580 |
|
@ -18,6 +18,16 @@ version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
|
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "accesskit"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a"
|
||||||
|
dependencies = [
|
||||||
|
"enumn",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.24.2"
|
version = "0.24.2"
|
||||||
|
@ -42,6 +52,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"serde",
|
||||||
"version_check",
|
"version_check",
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
@ -215,6 +226,12 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
|
@ -256,6 +273,9 @@ name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block"
|
name = "block"
|
||||||
|
@ -651,6 +671,7 @@ checksum = "7d72e9c39f6e11a2e922d04a34ec5e7ef522ea3f5a1acfca7a19d16ad5fe50f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"emath",
|
"emath",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -669,6 +690,7 @@ dependencies = [
|
||||||
"glow 0.16.0",
|
"glow 0.16.0",
|
||||||
"glutin",
|
"glutin",
|
||||||
"glutin-winit",
|
"glutin-winit",
|
||||||
|
"home",
|
||||||
"image",
|
"image",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
|
@ -679,6 +701,8 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"profiling",
|
"profiling",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
|
"ron",
|
||||||
|
"serde",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
@ -695,12 +719,15 @@ version = "0.30.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "252d52224d35be1535d7fd1d6139ce071fb42c9097773e79f7665604f5596b5e"
|
checksum = "252d52224d35be1535d7fd1d6139ce071fb42c9097773e79f7665604f5596b5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"accesskit",
|
||||||
"ahash",
|
"ahash",
|
||||||
"emath",
|
"emath",
|
||||||
"epaint",
|
"epaint",
|
||||||
"log",
|
"log",
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
"profiling",
|
"profiling",
|
||||||
|
"ron",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -735,6 +762,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"profiling",
|
"profiling",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
|
"serde",
|
||||||
"smithay-clipboard",
|
"smithay-clipboard",
|
||||||
"web-time",
|
"web-time",
|
||||||
"webbrowser",
|
"webbrowser",
|
||||||
|
@ -766,6 +794,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4fe73c1207b864ee40aa0b0c038d6092af1030744678c60188a05c28553515d"
|
checksum = "c4fe73c1207b864ee40aa0b0c038d6092af1030744678c60188a05c28553515d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumn"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.87",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -807,6 +847,7 @@ dependencies = [
|
||||||
"nohash-hasher",
|
"nohash-hasher",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"profiling",
|
"profiling",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2425,6 +2466,7 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"plate-tool-lib",
|
"plate-tool-lib",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2693,6 +2735,18 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ron"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
|
@ -2823,7 +2877,7 @@ version = "3.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
|
checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
|
|
|
@ -9,7 +9,9 @@ eframe = { version = "0.30", default-features = false, features = [
|
||||||
"default_fonts",
|
"default_fonts",
|
||||||
"glow",
|
"glow",
|
||||||
"wayland",
|
"wayland",
|
||||||
|
"persistence",
|
||||||
]}
|
]}
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
|
@ -4,76 +4,14 @@ use std::sync::Mutex;
|
||||||
use eframe::egui::{self};
|
use eframe::egui::{self};
|
||||||
|
|
||||||
use plate_tool_lib::plate::PlateFormat;
|
use plate_tool_lib::plate::PlateFormat;
|
||||||
use plate_tool_lib::plate_instances;
|
|
||||||
|
|
||||||
use crate::plate::{add_grid, PlateUiState};
|
use crate::main_state::{construct_fake_mainstate, MainState};
|
||||||
|
use crate::plate::{add_plate, PlateUiState};
|
||||||
use crate::transfer_menu::{self, transfer_menu, CurrentTransferState, TransferMenuState};
|
use crate::transfer_menu::{self, transfer_menu, CurrentTransferState, TransferMenuState};
|
||||||
|
use crate::tree::tree;
|
||||||
#[derive(Default)]
|
|
||||||
pub struct MainState {
|
|
||||||
pub source_plates: Vec<plate_tool_lib::plate_instances::PlateInstance>,
|
|
||||||
pub destination_plates: Vec<plate_tool_lib::plate_instances::PlateInstance>,
|
|
||||||
pub transfers: Vec<plate_tool_lib::transfer::Transfer>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn construct_fake_mainstate() -> MainState {
|
|
||||||
let src_plate: plate_tool_lib::plate_instances::PlateInstance =
|
|
||||||
plate_instances::PlateInstance::new(
|
|
||||||
plate_tool_lib::plate::PlateType::Source,
|
|
||||||
PlateFormat::W96,
|
|
||||||
"src1".to_owned(),
|
|
||||||
);
|
|
||||||
let dest_plate: plate_tool_lib::plate_instances::PlateInstance =
|
|
||||||
plate_instances::PlateInstance::new(
|
|
||||||
plate_tool_lib::plate::PlateType::Destination,
|
|
||||||
PlateFormat::W96,
|
|
||||||
"dest1".to_owned(),
|
|
||||||
);
|
|
||||||
let well_a1 = plate_tool_lib::Well { row: 1, col: 1 };
|
|
||||||
let well_c3 = plate_tool_lib::Well { row: 3, col: 3 };
|
|
||||||
let well_a5 = plate_tool_lib::Well { row: 1, col: 5 };
|
|
||||||
|
|
||||||
let transfer1_region: plate_tool_lib::transfer_region::TransferRegion =
|
|
||||||
plate_tool_lib::transfer_region::TransferRegion {
|
|
||||||
source_plate: src_plate.plate,
|
|
||||||
source_region: plate_tool_lib::transfer_region::Region::Rect(well_a1, well_c3),
|
|
||||||
dest_plate: dest_plate.plate,
|
|
||||||
dest_region: plate_tool_lib::transfer_region::Region::Point(well_a1),
|
|
||||||
interleave_source: (1, 1),
|
|
||||||
interleave_dest: (1, 1),
|
|
||||||
};
|
|
||||||
let transfer1: plate_tool_lib::transfer::Transfer = plate_tool_lib::transfer::Transfer::new(
|
|
||||||
src_plate.clone(),
|
|
||||||
dest_plate.clone(),
|
|
||||||
transfer1_region,
|
|
||||||
"Shrimp".to_owned(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let transfer2_region: plate_tool_lib::transfer_region::TransferRegion =
|
|
||||||
plate_tool_lib::transfer_region::TransferRegion {
|
|
||||||
source_plate: src_plate.plate,
|
|
||||||
source_region: plate_tool_lib::transfer_region::Region::Rect(well_a1, well_c3),
|
|
||||||
dest_plate: dest_plate.plate,
|
|
||||||
dest_region: plate_tool_lib::transfer_region::Region::Point(well_a5),
|
|
||||||
interleave_source: (1, 1),
|
|
||||||
interleave_dest: (2, 2),
|
|
||||||
};
|
|
||||||
let transfer2: plate_tool_lib::transfer::Transfer = plate_tool_lib::transfer::Transfer::new(
|
|
||||||
src_plate.clone(),
|
|
||||||
dest_plate.clone(),
|
|
||||||
transfer2_region,
|
|
||||||
"Shrimp".to_owned(),
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
MainState {
|
|
||||||
source_plates: vec![src_plate],
|
|
||||||
destination_plates: vec![dest_plate],
|
|
||||||
transfers: vec![transfer1, transfer2],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
struct MainWindowState {
|
struct MainWindowState {
|
||||||
show_side_panel: bool,
|
show_side_panel: bool,
|
||||||
}
|
}
|
||||||
|
@ -87,12 +25,15 @@ impl Default for MainWindowState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PlateToolEframe {
|
pub struct PlateToolEframe {
|
||||||
source_plate_state: Mutex<PlateUiState>,
|
source_plate_state: Mutex<PlateUiState>,
|
||||||
destination_plate_state: Mutex<PlateUiState>,
|
destination_plate_state: Mutex<PlateUiState>,
|
||||||
main_window_state: MainWindowState,
|
main_window_state: MainWindowState,
|
||||||
current_transfer_state: CurrentTransferState,
|
current_transfer_state: CurrentTransferState,
|
||||||
|
#[serde(skip)]
|
||||||
transfer_menu_state: TransferMenuState,
|
transfer_menu_state: TransferMenuState,
|
||||||
|
#[serde(skip)]
|
||||||
transfer_region_cache: plate_tool_lib::transfer_region_cache::TransferRegionCache,
|
transfer_region_cache: plate_tool_lib::transfer_region_cache::TransferRegionCache,
|
||||||
main_state: MainState,
|
main_state: MainState,
|
||||||
}
|
}
|
||||||
|
@ -115,35 +56,26 @@ impl Default for PlateToolEframe {
|
||||||
impl PlateToolEframe {
|
impl PlateToolEframe {
|
||||||
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
// Would load state here
|
// Would load state here
|
||||||
|
if let Some(storage) = cc.storage {
|
||||||
|
return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default();
|
||||||
|
} else {
|
||||||
let pte: PlateToolEframe = Default::default();
|
let pte: PlateToolEframe = Default::default();
|
||||||
pte.transfer_region_cache.generate_cache(&pte.main_state.transfers);
|
pte.transfer_region_cache
|
||||||
|
.generate_cache(&pte.main_state.transfers);
|
||||||
|
|
||||||
pte
|
pte
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for PlateToolEframe {
|
impl eframe::App for PlateToolEframe {
|
||||||
// State storage
|
// State storage
|
||||||
/*
|
|
||||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||||
unimplemented!()
|
log::info!("Saving state");
|
||||||
};
|
eframe::set_value(storage, eframe::APP_KEY, self);
|
||||||
*/
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {
|
||||||
let ordered_ids: Vec<plate_tool_lib::uuid::Uuid> = {
|
|
||||||
let mut ids: Vec<plate_tool_lib::uuid::Uuid> = self
|
|
||||||
.main_state
|
|
||||||
.transfers
|
|
||||||
.clone()
|
|
||||||
.iter()
|
|
||||||
.map(|x| x.id)
|
|
||||||
.collect();
|
|
||||||
ids.sort_unstable();
|
|
||||||
ids
|
|
||||||
};
|
|
||||||
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
egui::menu::bar(ui, |ui| {
|
egui::menu::bar(ui, |ui| {
|
||||||
ui.menu_button("File", |ui| {
|
ui.menu_button("File", |ui| {
|
||||||
|
@ -179,11 +111,31 @@ impl eframe::App for PlateToolEframe {
|
||||||
if self.main_window_state.show_side_panel {
|
if self.main_window_state.show_side_panel {
|
||||||
egui::SidePanel::left("side_menus").show(ctx, |ui| {
|
egui::SidePanel::left("side_menus").show(ctx, |ui| {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
transfer_menu(ui, &self.current_transfer_state, &mut self.transfer_menu_state);
|
tree(ui, &mut self.main_state, &self.current_transfer_state);
|
||||||
|
ui.separator();
|
||||||
|
transfer_menu(
|
||||||
|
ui,
|
||||||
|
&self.current_transfer_state,
|
||||||
|
&mut self.transfer_menu_state,
|
||||||
|
&mut self.main_state,
|
||||||
|
&self.transfer_region_cache,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This MUST happen after the menu calls as they can mutate main state
|
||||||
|
let ordered_ids: Vec<plate_tool_lib::uuid::Uuid> = {
|
||||||
|
let mut ids: Vec<plate_tool_lib::uuid::Uuid> = self
|
||||||
|
.main_state
|
||||||
|
.transfers
|
||||||
|
.clone()
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.id)
|
||||||
|
.collect();
|
||||||
|
ids.sort_unstable();
|
||||||
|
ids
|
||||||
|
};
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
let available_size = ui.available_size();
|
let available_size = ui.available_size();
|
||||||
|
@ -192,7 +144,8 @@ impl eframe::App for PlateToolEframe {
|
||||||
x.y /= 2.0;
|
x.y /= 2.0;
|
||||||
x
|
x
|
||||||
};
|
};
|
||||||
add_grid(
|
if self.main_state.get_current_source().is_some() {
|
||||||
|
add_plate(
|
||||||
half_height,
|
half_height,
|
||||||
PlateFormat::W96,
|
PlateFormat::W96,
|
||||||
&self.main_state.transfers,
|
&self.main_state.transfers,
|
||||||
|
@ -203,7 +156,9 @@ impl eframe::App for PlateToolEframe {
|
||||||
ui,
|
ui,
|
||||||
self.source_plate_state.lock().unwrap().deref_mut(),
|
self.source_plate_state.lock().unwrap().deref_mut(),
|
||||||
);
|
);
|
||||||
add_grid(
|
}
|
||||||
|
if self.main_state.get_current_destination().is_some() {
|
||||||
|
add_plate(
|
||||||
half_height,
|
half_height,
|
||||||
PlateFormat::W96,
|
PlateFormat::W96,
|
||||||
&self.main_state.transfers,
|
&self.main_state.transfers,
|
||||||
|
@ -214,6 +169,7 @@ impl eframe::App for PlateToolEframe {
|
||||||
ui,
|
ui,
|
||||||
self.destination_plate_state.lock().unwrap().deref_mut(),
|
self.destination_plate_state.lock().unwrap().deref_mut(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,5 @@ mod app;
|
||||||
mod plate;
|
mod plate;
|
||||||
mod tree;
|
mod tree;
|
||||||
mod transfer_menu;
|
mod transfer_menu;
|
||||||
|
mod main_state;
|
||||||
pub use app::PlateToolEframe;
|
pub use app::PlateToolEframe;
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
use plate_tool_lib::plate_instances;
|
||||||
|
use plate_tool_lib::plate::PlateFormat;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct MainState {
|
||||||
|
/// Stores all plates and transfers
|
||||||
|
///
|
||||||
|
/// It is not guaranteed that the current_* variables will refer
|
||||||
|
/// to UUIDs that exist in the lists, only that they did at one time.
|
||||||
|
/// This can happen if a plate or transfer is removed and set_* is not called.
|
||||||
|
pub source_plates: Vec<plate_tool_lib::plate_instances::PlateInstance>,
|
||||||
|
pub destination_plates: Vec<plate_tool_lib::plate_instances::PlateInstance>,
|
||||||
|
pub transfers: Vec<plate_tool_lib::transfer::Transfer>,
|
||||||
|
current_source: Option<plate_tool_lib::uuid::Uuid>,
|
||||||
|
current_destination: Option<plate_tool_lib::uuid::Uuid>,
|
||||||
|
current_transfer: Option<plate_tool_lib::uuid::Uuid>,
|
||||||
|
}
|
||||||
|
impl MainState {
|
||||||
|
pub fn get_current_source(&self) -> Option<plate_tool_lib::uuid::Uuid> {
|
||||||
|
self.current_source
|
||||||
|
}
|
||||||
|
pub fn get_current_destination(&self) -> Option<plate_tool_lib::uuid::Uuid> {
|
||||||
|
self.current_destination
|
||||||
|
}
|
||||||
|
pub fn get_current_transfer(&self) -> Option<plate_tool_lib::uuid::Uuid> {
|
||||||
|
self.current_transfer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_current_source(&mut self, id: plate_tool_lib::uuid::Uuid) -> bool {
|
||||||
|
if self.source_plates.iter().map(|x| x.get_uuid()).find(|x| *x == id).is_some() {
|
||||||
|
self.current_source = Some(id);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_current_destination(&mut self, id: plate_tool_lib::uuid::Uuid) -> bool {
|
||||||
|
if self.destination_plates.iter().map(|x| x.get_uuid()).find(|x| *x == id).is_some() {
|
||||||
|
self.current_destination = Some(id);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_current_transfer(&mut self, id: plate_tool_lib::uuid::Uuid) -> bool {
|
||||||
|
if self.transfers.iter().map(|x| x.get_uuid()).find(|x| *x == id).is_some() {
|
||||||
|
self.current_transfer = Some(id);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set_no_current_source(&mut self) {
|
||||||
|
self.current_source = None;
|
||||||
|
}
|
||||||
|
pub fn set_no_current_destination(&mut self) {
|
||||||
|
self.current_destination = None;
|
||||||
|
}
|
||||||
|
pub fn set_no_current_transfer(&mut self) {
|
||||||
|
self.current_transfer = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn construct_fake_mainstate() -> MainState {
|
||||||
|
let src_plate: plate_tool_lib::plate_instances::PlateInstance =
|
||||||
|
plate_instances::PlateInstance::new(
|
||||||
|
plate_tool_lib::plate::PlateType::Source,
|
||||||
|
PlateFormat::W96,
|
||||||
|
"src1".to_owned(),
|
||||||
|
);
|
||||||
|
let dest_plate: plate_tool_lib::plate_instances::PlateInstance =
|
||||||
|
plate_instances::PlateInstance::new(
|
||||||
|
plate_tool_lib::plate::PlateType::Destination,
|
||||||
|
PlateFormat::W96,
|
||||||
|
"dest1".to_owned(),
|
||||||
|
);
|
||||||
|
let well_a1 = plate_tool_lib::Well { row: 1, col: 1 };
|
||||||
|
let well_c3 = plate_tool_lib::Well { row: 3, col: 3 };
|
||||||
|
let well_a5 = plate_tool_lib::Well { row: 1, col: 5 };
|
||||||
|
|
||||||
|
let transfer1_region: plate_tool_lib::transfer_region::TransferRegion =
|
||||||
|
plate_tool_lib::transfer_region::TransferRegion {
|
||||||
|
source_plate: src_plate.plate,
|
||||||
|
source_region: plate_tool_lib::transfer_region::Region::Rect(well_a1, well_c3),
|
||||||
|
dest_plate: dest_plate.plate,
|
||||||
|
dest_region: plate_tool_lib::transfer_region::Region::Point(well_a1),
|
||||||
|
interleave_source: (1, 1),
|
||||||
|
interleave_dest: (1, 1),
|
||||||
|
};
|
||||||
|
let transfer1: plate_tool_lib::transfer::Transfer = plate_tool_lib::transfer::Transfer::new(
|
||||||
|
src_plate.clone(),
|
||||||
|
dest_plate.clone(),
|
||||||
|
transfer1_region,
|
||||||
|
"Shrimp".to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let transfer2_region: plate_tool_lib::transfer_region::TransferRegion =
|
||||||
|
plate_tool_lib::transfer_region::TransferRegion {
|
||||||
|
source_plate: src_plate.plate,
|
||||||
|
source_region: plate_tool_lib::transfer_region::Region::Rect(well_a1, well_c3),
|
||||||
|
dest_plate: dest_plate.plate,
|
||||||
|
dest_region: plate_tool_lib::transfer_region::Region::Point(well_a5),
|
||||||
|
interleave_source: (1, 1),
|
||||||
|
interleave_dest: (2, 2),
|
||||||
|
};
|
||||||
|
let transfer2: plate_tool_lib::transfer::Transfer = plate_tool_lib::transfer::Transfer::new(
|
||||||
|
src_plate.clone(),
|
||||||
|
dest_plate.clone(),
|
||||||
|
transfer2_region,
|
||||||
|
"Shrimp".to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
MainState {
|
||||||
|
source_plates: vec![src_plate],
|
||||||
|
destination_plates: vec![dest_plate],
|
||||||
|
transfers: vec![transfer1, transfer2],
|
||||||
|
current_source: None,
|
||||||
|
current_destination: None,
|
||||||
|
current_transfer: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ static STROKE_SELECT: LazyLock<egui::Stroke> =
|
||||||
static STROKE_HOVER: LazyLock<egui::Stroke> =
|
static STROKE_HOVER: LazyLock<egui::Stroke> =
|
||||||
LazyLock::new(|| egui::Stroke::new(3.0, egui::Color32::from_gray(255)));
|
LazyLock::new(|| egui::Stroke::new(3.0, egui::Color32::from_gray(255)));
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PlateUiState {
|
pub struct PlateUiState {
|
||||||
pub drag_start_position: Option<egui::Pos2>,
|
pub drag_start_position: Option<egui::Pos2>,
|
||||||
}
|
}
|
||||||
|
@ -69,8 +69,18 @@ fn get_hover_well(
|
||||||
start_x: f32,
|
start_x: f32,
|
||||||
start_y: f32,
|
start_y: f32,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
|
rows: u8,
|
||||||
|
columns: u8,
|
||||||
) -> Option<(u8, u8)> {
|
) -> Option<(u8, u8)> {
|
||||||
get_well_from_pos(response.hover_pos(), start_x, start_y, radius)
|
get_well_from_pos(
|
||||||
|
response.hover_pos(),
|
||||||
|
start_x,
|
||||||
|
start_y,
|
||||||
|
radius,
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
true,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_well_from_pos(
|
fn get_well_from_pos(
|
||||||
|
@ -78,19 +88,35 @@ fn get_well_from_pos(
|
||||||
start_x: f32,
|
start_x: f32,
|
||||||
start_y: f32,
|
start_y: f32,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
|
rows: u8,
|
||||||
|
columns: u8,
|
||||||
|
saturate: bool,
|
||||||
) -> Option<(u8, u8)> {
|
) -> Option<(u8, u8)> {
|
||||||
|
// cool fact: the bounds of our frame aren't actually the bounds of what counts
|
||||||
|
// as hovering. as a result, we have to make sure that we check these bounds here.
|
||||||
|
// yippee
|
||||||
|
let max_width = start_x + radius * 2.0 * columns as f32;
|
||||||
|
let max_height = start_y + radius * 2.0 * rows as f32;
|
||||||
|
|
||||||
// Some((row, column))
|
// Some((row, column))
|
||||||
position
|
position
|
||||||
.map(|p| Into::<(f32, f32)>::into(p))
|
.map(|p| Into::<(f32, f32)>::into(p))
|
||||||
.and_then(|(x, y)| {
|
.and_then(|(x, y)| {
|
||||||
// Check bounds
|
// Check bounds if no saturation
|
||||||
if x < start_x || y < start_y {
|
if !saturate && (x < start_x || y < start_y || x > max_width || y > max_height) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// CHECK Bottom Right BOUND
|
|
||||||
|
|
||||||
let solved_column: u8 = (x - start_x).div_euclid(radius * 2.0) as u8;
|
let solved_column: u8 = match x {
|
||||||
let solved_row: u8 = (y - start_y).div_euclid(radius * 2.0) as u8;
|
x if (x < start_x) => 1,
|
||||||
|
x if (x > max_width) => columns,
|
||||||
|
_ => (x - start_x).div_euclid(radius * 2.0) as u8 + 1u8,
|
||||||
|
};
|
||||||
|
let solved_row: u8 = match y {
|
||||||
|
y if (y < start_y) => 1,
|
||||||
|
y if (y > max_height) => rows,
|
||||||
|
_ => (y - start_y).div_euclid(radius * 2.0) as u8 + 1u8,
|
||||||
|
};
|
||||||
|
|
||||||
Some((solved_row, solved_column))
|
Some((solved_row, solved_column))
|
||||||
})
|
})
|
||||||
|
@ -109,20 +135,27 @@ fn calculate_shading_for_wells(
|
||||||
|
|
||||||
for transfer in transfers {
|
for transfer in transfers {
|
||||||
let cache_result = match plate_type {
|
let cache_result = match plate_type {
|
||||||
plate_tool_lib::plate::PlateType::Source => cache.get_source(transfer),
|
plate_tool_lib::plate::PlateType::Source => cache.get_or_calculate_source(transfer),
|
||||||
plate_tool_lib::plate::PlateType::Destination => cache.get_destination(transfer),
|
plate_tool_lib::plate::PlateType::Destination => {
|
||||||
|
cache.get_or_calculate_destination(transfer)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if let Some(wells) = cache_result {
|
if let Some(wells) = cache_result {
|
||||||
for well in wells.iter().filter(|x| x.row <= rows && x.col <= columns) {
|
for well in wells.iter().filter(|x| x.row <= rows && x.col <= columns) {
|
||||||
if let Some(mut x) =
|
if let Some(Some(mut x)) = well_infos
|
||||||
well_infos[well.row as usize * columns as usize + well.col as usize]
|
.get_mut((well.row - 1) as usize * columns as usize + (well.col - 1) as usize)
|
||||||
{
|
{
|
||||||
x.volume += 5.0;
|
x.volume += 5.0;
|
||||||
x.color = PALETTE.get_ordered(transfer.id, ordered_ids);
|
x.color = PALETTE.get_ordered(transfer.id, ordered_ids);
|
||||||
} else {
|
} else {
|
||||||
well_infos[well.row as usize * columns as usize + well.col as usize] = Some(
|
if let Some(mut wi) = well_infos.get_mut(
|
||||||
WellInfo::new(5.0, PALETTE.get_ordered(transfer.id, ordered_ids)),
|
(well.row - 1) as usize * columns as usize + (well.col - 1) as usize,
|
||||||
);
|
) {
|
||||||
|
*wi = Some(WellInfo::new(
|
||||||
|
5.0,
|
||||||
|
PALETTE.get_ordered(transfer.id, ordered_ids),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +171,7 @@ fn f64_to_color32(x: [f64; 3]) -> Color32 {
|
||||||
Color32::from_rgb(r, g, b)
|
Color32::from_rgb(r, g, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_grid_sub(
|
fn add_plate_sub(
|
||||||
size: egui::Vec2,
|
size: egui::Vec2,
|
||||||
rows: u8,
|
rows: u8,
|
||||||
columns: u8,
|
columns: u8,
|
||||||
|
@ -166,15 +199,20 @@ fn add_grid_sub(
|
||||||
state.drag_start_position = Some(response.hover_pos().unwrap());
|
state.drag_start_position = Some(response.hover_pos().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let drag_start_well = get_well_from_pos(state.drag_start_position, start_x, start_y, radius);
|
let drag_start_well = get_well_from_pos(
|
||||||
let hovered_well = get_hover_well(&response, start_x, start_y, radius);
|
state.drag_start_position,
|
||||||
|
start_x,
|
||||||
|
start_y,
|
||||||
|
radius,
|
||||||
|
rows,
|
||||||
|
columns,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let hovered_well = get_hover_well(&response, start_x, start_y, radius, rows, columns);
|
||||||
|
|
||||||
if response.clicked() {
|
if response.clicked() {
|
||||||
if let Some(cts) = current_transfer_state {
|
if let Some(cts) = current_transfer_state {
|
||||||
let end_well: Option<Well> = hovered_well.map(|(row, col)| Well {
|
let end_well: Option<Well> = hovered_well.map(|(row, col)| Well { row, col });
|
||||||
row: row + 1,
|
|
||||||
col: col + 1,
|
|
||||||
});
|
|
||||||
if let Some(end_well) = end_well {
|
if let Some(end_well) = end_well {
|
||||||
let new_region = Region::new_from_wells(end_well, None);
|
let new_region = Region::new_from_wells(end_well, None);
|
||||||
let mut cts = cts.lock().unwrap();
|
let mut cts = cts.lock().unwrap();
|
||||||
|
@ -189,17 +227,8 @@ fn add_grid_sub(
|
||||||
}
|
}
|
||||||
if response.drag_stopped() {
|
if response.drag_stopped() {
|
||||||
if let Some(cts) = current_transfer_state {
|
if let Some(cts) = current_transfer_state {
|
||||||
let start_well: Well = drag_start_well
|
if let Some(start_well) = drag_start_well.map(|(row, col)| Well { row, col }) {
|
||||||
.map(|(row, col)| Well {
|
let end_well: Option<Well> = hovered_well.map(|(row, col)| Well { row, col });
|
||||||
// Lib uses 1-indexing!
|
|
||||||
row: row + 1,
|
|
||||||
col: col + 1,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
let end_well: Option<Well> = hovered_well.map(|(row, col)| Well {
|
|
||||||
row: row + 1,
|
|
||||||
col: col + 1,
|
|
||||||
});
|
|
||||||
let new_region = Region::new_from_wells(start_well, end_well);
|
let new_region = Region::new_from_wells(start_well, end_well);
|
||||||
let mut cts = cts.lock().unwrap();
|
let mut cts = cts.lock().unwrap();
|
||||||
match plate_type {
|
match plate_type {
|
||||||
|
@ -209,6 +238,7 @@ fn add_grid_sub(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
state.drag_start_position = None;
|
state.drag_start_position = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,9 +256,12 @@ fn add_grid_sub(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let well_infos = {
|
let well_infos = {
|
||||||
let mut well_infos = calculate_shading_for_wells(rows, columns, transfers, plate_type, ordered_ids, cache);
|
// Get non-active transfer info
|
||||||
|
let mut well_infos =
|
||||||
|
calculate_shading_for_wells(rows, columns, transfers, plate_type, ordered_ids, cache);
|
||||||
|
|
||||||
// Get wells in the current transfer to tack on to well_infos separately
|
// Get wells in the current transfer to tack on to well_infos separately
|
||||||
let current_transfer_wells: Option<Box<[(usize,usize)]>> = {
|
let current_transfer_wells: Option<Box<[(usize, usize)]>> = {
|
||||||
(match plate_type {
|
(match plate_type {
|
||||||
plate_tool_lib::plate::PlateType::Source => current_transfer_state
|
plate_tool_lib::plate::PlateType::Source => current_transfer_state
|
||||||
.and_then(|x| x.lock().ok())
|
.and_then(|x| x.lock().ok())
|
||||||
|
@ -237,27 +270,33 @@ fn add_grid_sub(
|
||||||
.and_then(|x| x.lock().ok())
|
.and_then(|x| x.lock().ok())
|
||||||
.map(|mut x| x.get_destination_wells()),
|
.map(|mut x| x.get_destination_wells()),
|
||||||
})
|
})
|
||||||
// Drop back to 0-indexing here
|
.map(|xs| {
|
||||||
.map(|xs| xs.iter().map(|x| (x.row as usize - 1, x.col as usize - 1)).collect())
|
xs.iter()
|
||||||
|
.map(|x| (x.row as usize, x.col as usize))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
};
|
};
|
||||||
if let Some(wells) = current_transfer_wells {
|
if let Some(wells) = current_transfer_wells {
|
||||||
for w in wells {
|
for w in wells {
|
||||||
let well_info = &mut well_infos[w.0 * columns as usize + w.1];
|
if let Some(mut well_info) =
|
||||||
|
well_infos.get_mut((w.0 - 1) * columns as usize + (w.1 - 1))
|
||||||
|
{
|
||||||
let volume = well_info.map(|x| x.volume).unwrap_or(0.0)
|
let volume = well_info.map(|x| x.volume).unwrap_or(0.0)
|
||||||
+ current_transfer_state.and_then(|x| x.lock().ok())
|
+ current_transfer_state
|
||||||
|
.and_then(|x| x.lock().ok())
|
||||||
.map(|x| x.volume)
|
.map(|x| x.volume)
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
|
|
||||||
*well_info = Some(WellInfo {
|
*well_info = Some(WellInfo {
|
||||||
color: [255.0,255.0,255.0],
|
color: [255.0, 255.0, 255.0],
|
||||||
volume: 1.0
|
volume: 1.0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
well_infos
|
well_infos
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Plate Frame
|
// Plate Frame
|
||||||
painter.rect_stroke(
|
painter.rect_stroke(
|
||||||
egui::Rect {
|
egui::Rect {
|
||||||
|
@ -272,11 +311,11 @@ fn add_grid_sub(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw wells
|
// Draw wells
|
||||||
for c_row in 0..rows {
|
for c_row in 1..=rows {
|
||||||
for c_column in 0..columns {
|
for c_column in 1..=columns {
|
||||||
let center = egui::pos2(
|
let center = egui::pos2(
|
||||||
start_x + radius + 2.0 * radius * c_column as f32,
|
start_x + radius + 2.0 * radius * (c_column - 1) as f32,
|
||||||
start_y + radius + 2.0 * radius * c_row as f32,
|
start_y + radius + 2.0 * radius * (c_row - 1) as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -284,7 +323,7 @@ fn add_grid_sub(
|
||||||
//
|
//
|
||||||
|
|
||||||
if let Some(well_info) =
|
if let Some(well_info) =
|
||||||
well_infos[c_row as usize * columns as usize + c_column as usize]
|
well_infos[(c_row - 1) as usize * columns as usize + (c_column - 1) as usize]
|
||||||
{
|
{
|
||||||
painter.circle_filled(center, radius * 0.80, f64_to_color32(well_info.color));
|
painter.circle_filled(center, radius * 0.80, f64_to_color32(well_info.color));
|
||||||
}
|
}
|
||||||
|
@ -299,8 +338,8 @@ fn add_grid_sub(
|
||||||
if current_selection.as_ref().is_some_and(|x| {
|
if current_selection.as_ref().is_some_and(|x| {
|
||||||
x.contains_well(
|
x.contains_well(
|
||||||
&Well {
|
&Well {
|
||||||
row: c_row + 1,
|
row: c_row,
|
||||||
col: c_column + 1,
|
col: c_column,
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -351,7 +390,7 @@ fn add_grid_sub(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_grid(
|
pub fn add_plate(
|
||||||
size: egui::Vec2,
|
size: egui::Vec2,
|
||||||
pf: PlateFormat,
|
pf: PlateFormat,
|
||||||
transfers: &Vec<plate_tool_lib::transfer::Transfer>,
|
transfers: &Vec<plate_tool_lib::transfer::Transfer>,
|
||||||
|
@ -362,11 +401,10 @@ pub fn add_grid(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
state: &mut PlateUiState,
|
state: &mut PlateUiState,
|
||||||
) {
|
) {
|
||||||
match pf {
|
add_plate_sub(
|
||||||
PlateFormat::W96 => add_grid_sub(
|
|
||||||
size,
|
size,
|
||||||
8,
|
pf.rows(),
|
||||||
12,
|
pf.columns(),
|
||||||
transfers,
|
transfers,
|
||||||
plate_type,
|
plate_type,
|
||||||
ordered_ids,
|
ordered_ids,
|
||||||
|
@ -374,31 +412,5 @@ pub fn add_grid(
|
||||||
current_transfer_state,
|
current_transfer_state,
|
||||||
ui,
|
ui,
|
||||||
state,
|
state,
|
||||||
),
|
);
|
||||||
PlateFormat::W384 => add_grid_sub(
|
|
||||||
size,
|
|
||||||
16,
|
|
||||||
24,
|
|
||||||
transfers,
|
|
||||||
plate_type,
|
|
||||||
ordered_ids,
|
|
||||||
cache,
|
|
||||||
current_transfer_state,
|
|
||||||
ui,
|
|
||||||
state,
|
|
||||||
),
|
|
||||||
PlateFormat::W1536 => add_grid_sub(
|
|
||||||
size,
|
|
||||||
32,
|
|
||||||
48,
|
|
||||||
transfers,
|
|
||||||
plate_type,
|
|
||||||
ordered_ids,
|
|
||||||
cache,
|
|
||||||
current_transfer_state,
|
|
||||||
ui,
|
|
||||||
state,
|
|
||||||
),
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use plate_tool_lib::transfer_region::{Region, TransferRegion};
|
use plate_tool_lib::transfer_region::{self, Region, TransferRegion};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::main_state::{self, MainState};
|
||||||
|
|
||||||
pub type CurrentTransferState = Arc<Mutex<CurrentTransferStateInterior>>;
|
pub type CurrentTransferState = Arc<Mutex<CurrentTransferStateInterior>>;
|
||||||
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct CurrentTransferStateInterior {
|
pub struct CurrentTransferStateInterior {
|
||||||
pub transfer_name: String,
|
pub transfer_name: String,
|
||||||
pub source_region: plate_tool_lib::transfer_region::Region,
|
pub source_region: plate_tool_lib::transfer_region::Region,
|
||||||
|
@ -15,8 +18,11 @@ pub struct CurrentTransferStateInterior {
|
||||||
pub destination_row_interleave: i8,
|
pub destination_row_interleave: i8,
|
||||||
pub destination_column_interleave: i8,
|
pub destination_column_interleave: i8,
|
||||||
pub volume: f32,
|
pub volume: f32,
|
||||||
|
#[serde(skip)]
|
||||||
transfer_hash: Option<u64>,
|
transfer_hash: Option<u64>,
|
||||||
|
#[serde(skip)]
|
||||||
source_wells: Option<Vec<plate_tool_lib::Well>>,
|
source_wells: Option<Vec<plate_tool_lib::Well>>,
|
||||||
|
#[serde(skip)]
|
||||||
destination_wells: Option<Vec<plate_tool_lib::Well>>,
|
destination_wells: Option<Vec<plate_tool_lib::Well>>,
|
||||||
}
|
}
|
||||||
impl Default for CurrentTransferStateInterior {
|
impl Default for CurrentTransferStateInterior {
|
||||||
|
@ -93,11 +99,63 @@ impl CurrentTransferStateInterior {
|
||||||
dest_plate: self.destination_plate,
|
dest_plate: self.destination_plate,
|
||||||
dest_region: self.destination_region.clone(),
|
dest_region: self.destination_region.clone(),
|
||||||
interleave_source: (self.source_row_interleave, self.source_column_interleave),
|
interleave_source: (self.source_row_interleave, self.source_column_interleave),
|
||||||
interleave_dest: (self.destination_row_interleave, self.destination_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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_to_transfer(&self, ms: &MainState) -> Option<plate_tool_lib::transfer::Transfer> {
|
||||||
|
let source_plate_uuid = ms.get_current_source()?;
|
||||||
|
let destination_plate_uuid = ms.get_current_destination()?;
|
||||||
|
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 struct TransferMenuState {
|
||||||
pub source_region_string: String,
|
pub source_region_string: String,
|
||||||
pub destination_region_string: String,
|
pub destination_region_string: String,
|
||||||
|
@ -127,6 +185,8 @@ pub fn transfer_menu(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
state: &CurrentTransferState,
|
state: &CurrentTransferState,
|
||||||
ui_state: &mut TransferMenuState,
|
ui_state: &mut TransferMenuState,
|
||||||
|
main_state: &mut MainState,
|
||||||
|
transfer_region_cache: &plate_tool_lib::transfer_region_cache::TransferRegionCache,
|
||||||
) {
|
) {
|
||||||
// Can we reduce the length of this lock pls
|
// Can we reduce the length of this lock pls
|
||||||
let mut state = state.lock().unwrap();
|
let mut state = state.lock().unwrap();
|
||||||
|
@ -220,4 +280,30 @@ pub fn transfer_menu(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Save").clicked() {
|
||||||
|
if let Some(transfer_uuid) = main_state.get_current_transfer() {
|
||||||
|
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();
|
||||||
|
transfer_region_cache.invalidate(&transfer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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");
|
||||||
|
transfer_region_cache.add_overwrite(new_transfer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ui.button("New").clicked() {
|
||||||
|
*state = CurrentTransferStateInterior::default();
|
||||||
|
main_state.set_no_current_transfer();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
use crate::{main_state::MainState, transfer_menu::{CurrentTransferState, CurrentTransferStateInterior}};
|
||||||
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
static SELECT_COLOR: LazyLock<egui::Color32> =
|
||||||
|
LazyLock::new(|| egui::Color32::from_hex("#aa0000").unwrap());
|
||||||
|
|
||||||
|
pub fn tree(ui: &mut egui::Ui, ms: &mut MainState, cts: &CurrentTransferState) {
|
||||||
|
// Add all source plates
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.heading("Source Plates");
|
||||||
|
let mut new_uuid: Option<plate_tool_lib::uuid::Uuid> = None;
|
||||||
|
for (name, uuid) in ms.source_plates.iter().map(|x| (&x.name, x.get_uuid())) {
|
||||||
|
let f = {
|
||||||
|
let mut f = egui::Frame::none();
|
||||||
|
if ms.get_current_source().is_some_and(|x| x == uuid) {
|
||||||
|
f = f.fill(*SELECT_COLOR);
|
||||||
|
}
|
||||||
|
f
|
||||||
|
};
|
||||||
|
f.show(ui, |ui| {
|
||||||
|
let r = ui.add(
|
||||||
|
egui::Label::new(name)
|
||||||
|
.sense(egui::Sense::click())
|
||||||
|
.selectable(false),
|
||||||
|
);
|
||||||
|
if r.clicked() {
|
||||||
|
new_uuid = Some(uuid);
|
||||||
|
log::info!("{:?}", uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(uuid) = new_uuid {
|
||||||
|
ms.set_current_source(uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add all destination plates
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.heading("Destination Plates");
|
||||||
|
let mut new_uuid: Option<plate_tool_lib::uuid::Uuid> = None;
|
||||||
|
for (name, uuid) in ms
|
||||||
|
.destination_plates
|
||||||
|
.iter()
|
||||||
|
.map(|x| (&x.name, x.get_uuid()))
|
||||||
|
{
|
||||||
|
let f = {
|
||||||
|
let mut f = egui::Frame::none();
|
||||||
|
if ms.get_current_destination().is_some_and(|x| x == uuid) {
|
||||||
|
f = f.fill(*SELECT_COLOR);
|
||||||
|
}
|
||||||
|
f
|
||||||
|
};
|
||||||
|
f.show(ui, |ui| {
|
||||||
|
let r = ui.add(
|
||||||
|
egui::Label::new(name)
|
||||||
|
.sense(egui::Sense::click())
|
||||||
|
.selectable(false),
|
||||||
|
);
|
||||||
|
if r.clicked() {
|
||||||
|
new_uuid = Some(uuid);
|
||||||
|
log::info!("{:?}", uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(uuid) = new_uuid {
|
||||||
|
ms.set_current_destination(uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Add all transfers
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.heading("Transfers");
|
||||||
|
let mut new_uuid: Option<plate_tool_lib::uuid::Uuid> = None;
|
||||||
|
for (name, uuid) in ms.transfers.iter().map(|x| (&x.name, x.get_uuid())) {
|
||||||
|
let f = {
|
||||||
|
let mut f = egui::Frame::none();
|
||||||
|
if ms.get_current_transfer().is_some_and(|x| x == uuid) {
|
||||||
|
f = f.fill(*SELECT_COLOR);
|
||||||
|
}
|
||||||
|
f
|
||||||
|
};
|
||||||
|
f.show(ui, |ui| {
|
||||||
|
let r = ui.add(
|
||||||
|
egui::Label::new(name)
|
||||||
|
.sense(egui::Sense::click())
|
||||||
|
.selectable(false),
|
||||||
|
);
|
||||||
|
if r.clicked() {
|
||||||
|
new_uuid = Some(uuid);
|
||||||
|
log::info!("{:?}", uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(uuid) = new_uuid {
|
||||||
|
ms.set_current_transfer(uuid);
|
||||||
|
if let Some(new_cts) = CurrentTransferStateInterior::try_new_from_main_state_transfer(ms) {
|
||||||
|
*cts.lock().unwrap() = new_cts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -114,4 +114,10 @@ impl PlateFormat {
|
||||||
PlateFormat::W3456 => (48, 72),
|
PlateFormat::W3456 => (48, 72),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn rows(&self) -> u8 {
|
||||||
|
self.size().0
|
||||||
|
}
|
||||||
|
pub fn columns(&self) -> u8 {
|
||||||
|
self.size().1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,24 +250,39 @@ impl TransferRegion {
|
||||||
|
|
||||||
// Non-replicate transfers:
|
// Non-replicate transfers:
|
||||||
match &self.dest_region {
|
match &self.dest_region {
|
||||||
Region::Point(Well { row: x, col: y }) => {
|
Region::Point(dest_point) => {
|
||||||
Box::new(move |Well { row: i, col: j }| {
|
// Breaking from form somewhat, we really should return an entirely different
|
||||||
if source_wells.contains(&Well { row: i, col: j }) {
|
// function if a point-dest can't fit the whole source.
|
||||||
|
// If the bottom-right well of the source won't fit in the dest,
|
||||||
|
// we can abort.
|
||||||
|
let source_bottom_right = match self.source_region {
|
||||||
|
Region::Point(x) => x,
|
||||||
|
Region::Rect(w1, w2) => standardize_rectangle(&w1, &w2).1,
|
||||||
|
Region::Custom(_) => unreachable!("A point destination region cannot be paired with a custom source destination?"),
|
||||||
|
};
|
||||||
|
let bottom_right_mapped = Self::point_destination_region_calc(
|
||||||
|
source_bottom_right,
|
||||||
|
source_ul,
|
||||||
|
il_source,
|
||||||
|
il_dest,
|
||||||
|
*dest_point,
|
||||||
|
);
|
||||||
|
if bottom_right_mapped.row > self.dest_plate.plate_format.rows()
|
||||||
|
|| bottom_right_mapped.col > self.dest_plate.plate_format.columns()
|
||||||
|
{
|
||||||
|
return Box::new(|_| None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Box::new(move |input| {
|
||||||
|
if source_wells.contains(&input) {
|
||||||
// Validity here already checked by self.validate()
|
// Validity here already checked by self.validate()
|
||||||
Some(vec![Well {
|
Some(vec![Self::point_destination_region_calc(
|
||||||
row: x + i
|
input,
|
||||||
.checked_sub(source_ul.row)
|
source_ul,
|
||||||
.expect("Point cannot have been less than UL")
|
il_source,
|
||||||
.checked_div(il_source.0.unsigned_abs())
|
il_dest,
|
||||||
.expect("Source interleave cannot be 0")
|
*dest_point,
|
||||||
.mul(il_dest.0.unsigned_abs()),
|
)])
|
||||||
col: y + j
|
|
||||||
.checked_sub(source_ul.col)
|
|
||||||
.expect("Point cannot have been less than UL")
|
|
||||||
.checked_div(il_source.1.unsigned_abs())
|
|
||||||
.expect("Source interleave cannot be 0")
|
|
||||||
.mul(il_dest.1.unsigned_abs()),
|
|
||||||
}])
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -387,6 +402,33 @@ impl TransferRegion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn point_destination_region_calc(
|
||||||
|
input: Well,
|
||||||
|
source_ul: Well,
|
||||||
|
il_source: (i8, i8),
|
||||||
|
il_dest: (i8, i8),
|
||||||
|
dest_point: Well,
|
||||||
|
) -> Well {
|
||||||
|
Well {
|
||||||
|
row: dest_point.row
|
||||||
|
+ input
|
||||||
|
.row
|
||||||
|
.checked_sub(source_ul.row)
|
||||||
|
.expect("Point cannot have been less than UL")
|
||||||
|
.checked_div(il_source.0.unsigned_abs())
|
||||||
|
.expect("Source interleave cannot be 0")
|
||||||
|
.mul(il_dest.0.unsigned_abs()),
|
||||||
|
col: dest_point.col
|
||||||
|
+ input
|
||||||
|
.col
|
||||||
|
.checked_sub(source_ul.col)
|
||||||
|
.expect("Point cannot have been less than UL")
|
||||||
|
.checked_div(il_source.1.unsigned_abs())
|
||||||
|
.expect("Source interleave cannot be 0")
|
||||||
|
.mul(il_dest.1.unsigned_abs()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn validate(&self) -> Result<(), &'static str> {
|
pub fn validate(&self) -> Result<(), &'static str> {
|
||||||
// Checks if the region does anything suspect
|
// Checks if the region does anything suspect
|
||||||
//
|
//
|
||||||
|
@ -400,6 +442,12 @@ impl TransferRegion {
|
||||||
let il_dest = self.interleave_dest;
|
let il_dest = self.interleave_dest;
|
||||||
|
|
||||||
match self.source_region {
|
match self.source_region {
|
||||||
|
/*
|
||||||
|
* Note 04Jan2025:
|
||||||
|
* I genuinely cannot think of a reason why we should need to validate a source
|
||||||
|
* point region???
|
||||||
|
* Like, why would it *not* be in the plate?
|
||||||
|
*/
|
||||||
Region::Point(_) => return Ok(()), // Should make sure it's actually in the plate, leave for
|
Region::Point(_) => return Ok(()), // Should make sure it's actually in the plate, leave for
|
||||||
// later
|
// later
|
||||||
Region::Rect(s1, s2) => {
|
Region::Rect(s1, s2) => {
|
||||||
|
|
|
@ -11,6 +11,11 @@ pub struct TransferRegionCache {
|
||||||
interior: Arc<Mutex<TransferRegionCacheInterior>>,
|
interior: Arc<Mutex<TransferRegionCacheInterior>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TransferRegionCache {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
impl TransferRegionCache {
|
impl TransferRegionCache {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
TransferRegionCache {
|
TransferRegionCache {
|
||||||
|
@ -46,10 +51,22 @@ impl TransferRegionCache {
|
||||||
pub fn get_source(&self, tr: &Transfer) -> Option<Arc<[Well]>> {
|
pub fn get_source(&self, tr: &Transfer) -> Option<Arc<[Well]>> {
|
||||||
self.get(tr, true)
|
self.get(tr, true)
|
||||||
}
|
}
|
||||||
|
pub fn get_or_calculate_source(&self, tr: &Transfer) -> Option<Arc<[Well]>> {
|
||||||
|
if !self.has(tr) {
|
||||||
|
self.add_overwrite(tr);
|
||||||
|
}
|
||||||
|
self.get_source(tr)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_destination(&self, tr: &Transfer) -> Option<Arc<[Well]>> {
|
pub fn get_destination(&self, tr: &Transfer) -> Option<Arc<[Well]>> {
|
||||||
self.get(tr, false)
|
self.get(tr, false)
|
||||||
}
|
}
|
||||||
|
pub fn get_or_calculate_destination(&self, tr: &Transfer) -> Option<Arc<[Well]>> {
|
||||||
|
if !self.has(tr) {
|
||||||
|
self.add_overwrite(tr);
|
||||||
|
}
|
||||||
|
self.get_destination(tr)
|
||||||
|
}
|
||||||
|
|
||||||
fn get(&self, tr: &Transfer, is_source: bool) -> Option<Arc<[Well]>> {
|
fn get(&self, tr: &Transfer, is_source: bool) -> Option<Arc<[Well]>> {
|
||||||
if let Ok(interior) = self.interior.lock() {
|
if let Ok(interior) = self.interior.lock() {
|
||||||
|
@ -63,6 +80,13 @@ impl TransferRegionCache {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn has(&self, tr: &Transfer) -> bool {
|
||||||
|
if let Ok(interior) = self.interior.lock() {
|
||||||
|
interior.cache.contains_key(&tr.get_uuid())
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for TransferRegionCache {
|
impl Clone for TransferRegionCache {
|
||||||
|
|
Loading…
Reference in New Issue