From e6d80ab8a375fbdcd6d8fd6bba249638f0bcd3d6 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sun, 12 Jan 2025 17:25:46 -0500 Subject: [PATCH] Edit plates --- plate-tool-eframe/src/app.rs | 88 +++++++++++---- plate-tool-eframe/src/main_state.rs | 77 ++++++++++++-- plate-tool-eframe/src/modals.rs | 142 +++++++++++++++++++++++-- plate-tool-eframe/src/transfer_menu.rs | 19 +++- plate-tool-eframe/src/tree.rs | 76 +++++++++++-- 5 files changed, 351 insertions(+), 51 deletions(-) diff --git a/plate-tool-eframe/src/app.rs b/plate-tool-eframe/src/app.rs index aab89f1..11b5dff 100644 --- a/plate-tool-eframe/src/app.rs +++ b/plate-tool-eframe/src/app.rs @@ -3,12 +3,10 @@ use std::sync::Mutex; use eframe::egui::{self}; -use plate_tool_lib::plate::PlateFormat; - use crate::main_state::{construct_fake_mainstate, MainState}; use crate::modals::{self, ModalState}; use crate::plate::{add_plate, PlateUiState}; -use crate::transfer_menu::{self, transfer_menu, CurrentTransferState, TransferMenuState}; +use crate::transfer_menu::{transfer_menu, CurrentTransferState, TransferMenuState}; use crate::tree::tree; #[non_exhaustive] @@ -57,10 +55,13 @@ impl PlateToolEframe { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { // Would load state here if let Some(storage) = cc.storage { - return eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); + let out: PlateToolEframe = + eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); + return out; } else { let pte: PlateToolEframe = Default::default(); - pte.main_state.transfer_region_cache + pte.main_state + .transfer_region_cache .generate_cache(&pte.main_state.transfers); pte @@ -80,7 +81,7 @@ impl eframe::App for PlateToolEframe { egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { if ui.button("New Plate").clicked() { - crate::modals::open_new_plate_modal(ctx, &mut self.modal_state); + crate::modals::open_new_plate_modal(&mut self.modal_state); } ui.menu_button("Export", |ui| { if ui.button("Export as CSV").clicked() {} @@ -90,7 +91,13 @@ impl eframe::App for PlateToolEframe { if ui.button("Import from JSON").clicked() {} if ui.button("Import transfer from CSV").clicked() {} }); - if ui.button("Reset All").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| { @@ -114,7 +121,12 @@ impl eframe::App for PlateToolEframe { if self.main_window_state.show_side_panel { egui::SidePanel::left("side_menus").show(ctx, |ui| { ui.vertical(|ui| { - tree(ui, &mut self.main_state, &self.current_transfer_state); + tree( + ui, + &mut self.main_state, + &self.current_transfer_state, + &mut self.modal_state, + ); ui.separator(); transfer_menu( ui, @@ -150,7 +162,7 @@ impl eframe::App for PlateToolEframe { add_plate( half_height, source_pi.plate.plate_format, - self.main_state.get_current_transfers(), + self.main_state.get_current_source_transfers(), plate_tool_lib::plate::PlateType::Source, &ordered_ids, &self.main_state.transfer_region_cache, @@ -159,11 +171,13 @@ impl eframe::App for PlateToolEframe { self.source_plate_state.lock().unwrap().deref_mut(), ); } - if let Some(destination_pi) = self.main_state.get_current_destination_plateinstance() { + if let Some(destination_pi) = + self.main_state.get_current_destination_plateinstance() + { add_plate( half_height, destination_pi.plate.plate_format, - self.main_state.get_current_transfers(), + self.main_state.get_current_destination_transfers(), plate_tool_lib::plate::PlateType::Destination, &ordered_ids, &self.main_state.transfer_region_cache, @@ -177,19 +191,47 @@ impl eframe::App for PlateToolEframe { // Modal processing modals::show_modal_if_open(ctx, &mut self.modal_state); - if let modals::ModalState::NewPlateComplete(modals::NewPlateModalComplete(Some(pi))) = - &self.modal_state - { - let plate_type = pi.plate.plate_type; - match plate_type { - plate_tool_lib::plate::PlateType::Source => { - self.main_state.source_plates.push(pi.clone()); - }, - plate_tool_lib::plate::PlateType::Destination => { - self.main_state.destination_plates.push(pi.clone()); - }, + match &self.modal_state { + modals::ModalState::NewPlateComplete(modals::NewPlateModalComplete(Some(pi))) => { + let plate_type = pi.plate.plate_type; + match plate_type { + plate_tool_lib::plate::PlateType::Source => { + self.main_state.source_plates.push(pi.clone()); + } + plate_tool_lib::plate::PlateType::Destination => { + self.main_state.destination_plates.push(pi.clone()); + } + } + self.modal_state = modals::ModalState::None; } - self.modal_state = modals::ModalState::None; + modals::ModalState::EditPlateComplete(modals::EditPlateModalStateComplete(Some(x))) => { + let pi = { + match x.plate_type { + plate_tool_lib::plate::PlateType::Source => self + .main_state + .source_plates + .iter_mut() + .find(|y| y.get_uuid() == x.id), + plate_tool_lib::plate::PlateType::Destination => self + .main_state + .destination_plates + .iter_mut() + .find(|y| y.get_uuid() == x.id), + } + }; + if let Some(pi) = pi { + pi.name = x.name.to_owned(); + pi.change_format(&x.plate_format); + } + self.modal_state = modals::ModalState::None; + } + ModalState::NewPlateComplete(modals::NewPlateModalComplete(None)) => { + self.modal_state = modals::ModalState::None; + } + ModalState::EditPlateComplete(modals::EditPlateModalStateComplete(None)) => { + self.modal_state = modals::ModalState::None; + } + _ => (), } } } diff --git a/plate-tool-eframe/src/main_state.rs b/plate-tool-eframe/src/main_state.rs index 9fa48fb..475a4b1 100644 --- a/plate-tool-eframe/src/main_state.rs +++ b/plate-tool-eframe/src/main_state.rs @@ -1,8 +1,10 @@ +use core::fmt; + use plate_tool_lib::plate::PlateFormat; -use plate_tool_lib::plate_instances; +use plate_tool_lib::plate_instances::{self, PlateInstance}; use plate_tool_lib::transfer_region_cache::TransferRegionCache; -#[derive(Default, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Default, serde::Serialize, serde::Deserialize)] pub struct MainState { /// Stores all plates and transfers /// @@ -55,14 +57,28 @@ impl MainState { None } } - pub fn get_current_transfers(&self) -> Option> { + pub fn get_current_source_transfers(&self) -> Option> { let source_uuid = self.get_current_source_uuid(); + if let Some(source_uuid) = source_uuid { + Some( + self.transfers + .iter() + .filter(|tr| tr.source_id == source_uuid) + .collect(), + ) + } else { + None + } + } + pub fn get_current_destination_transfers(&self) -> Option> { let destination_uuid = self.get_current_destination_uuid(); - if let (Some(source_uuid), Some(destination_uuid)) = (source_uuid, destination_uuid) { - Some(self.transfers - .iter() - .filter(|tr| tr.source_id == source_uuid && tr.dest_id == destination_uuid) - .collect()) + if let Some(destination_uuid) = destination_uuid { + Some( + self.transfers + .iter() + .filter(|tr| tr.dest_id == destination_uuid) + .collect(), + ) } else { None } @@ -117,6 +133,12 @@ impl MainState { .find(|x| *x == id) .is_some() } + pub fn get_source_by_uuid(&self, id: plate_tool_lib::uuid::Uuid) -> Option<&PlateInstance> { + self.source_plates + .iter() + .find(|x| x.get_uuid() == id) + } + fn check_destination_exists(&self, id: plate_tool_lib::uuid::Uuid) -> bool { self.destination_plates .iter() @@ -124,6 +146,45 @@ impl MainState { .find(|x| *x == id) .is_some() } + pub fn get_destination_by_uuid(&self, id: plate_tool_lib::uuid::Uuid) -> Option<&PlateInstance> { + self.destination_plates + .iter() + .find(|x| x.get_uuid() == id) + } +} +impl fmt::Debug for MainState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + #[derive(Debug)] + #[allow(dead_code)] + struct SelectedFields<'a> { + pub source_plates: &'a Vec, + pub destination_plates: &'a Vec, + pub transfers: &'a Vec, + current_source: &'a Option, + current_destination: &'a Option, + current_transfer: &'a Option, + } + let Self { + source_plates, + destination_plates, + transfers, + current_source, + current_destination, + current_transfer, + transfer_region_cache: _, + } = self; + fmt::Debug::fmt( + &SelectedFields { + source_plates, + destination_plates, + transfers, + current_source, + current_destination, + current_transfer, + }, + f, + ) + } } pub fn construct_fake_mainstate() -> MainState { diff --git a/plate-tool-eframe/src/modals.rs b/plate-tool-eframe/src/modals.rs index ac7904d..fe8115b 100644 --- a/plate-tool-eframe/src/modals.rs +++ b/plate-tool-eframe/src/modals.rs @@ -5,6 +5,8 @@ use eframe::egui; pub enum ModalState { NewPlate(NewPlateModalState), NewPlateComplete(NewPlateModalComplete), + EditPlate(EditPlateModalState), + EditPlateComplete(EditPlateModalStateComplete), None, } impl Default for ModalState { @@ -14,11 +16,22 @@ impl Default for ModalState { } #[non_exhaustive] -#[derive(Debug, Default)] +#[derive(Debug)] pub struct NewPlateModalState { pub name: String, pub plate_type: plate_tool_lib::plate::PlateType, pub plate_format: plate_tool_lib::plate::PlateFormat, + window_open: bool, +} +impl Default for NewPlateModalState { + fn default() -> Self { + Self { + name: String::default(), + plate_type: plate_tool_lib::plate::PlateType::default(), + plate_format: plate_tool_lib::plate::PlateFormat::default(), + window_open: true, + } + } } #[non_exhaustive] @@ -28,6 +41,7 @@ pub struct NewPlateModalComplete(pub Option show_new_plate_modal(ctx, modal_state), + ModalState::EditPlate(_) => show_edit_plate_modal(ctx, modal_state), _ => (), } } @@ -50,6 +64,7 @@ fn show_new_plate_modal(ctx: &egui::Context, modal_state: &mut ModalState) { .order(egui::Order::Foreground) .collapsible(false) .resizable(false) + .open(&mut state.window_open) .show(ctx, |ui| { ui.vertical(|ui| { ui.add(egui::TextEdit::singleline(&mut state.name).hint_text("Plate Name")); @@ -87,21 +102,134 @@ fn show_new_plate_modal(ctx: &egui::Context, modal_state: &mut ModalState) { ); }); if ui.button("Add").clicked() { - output = Some(plate_tool_lib::plate_instances::PlateInstance::new(state.plate_type, state.plate_format, state.name.clone())); + output = Some(plate_tool_lib::plate_instances::PlateInstance::new( + state.plate_type, + state.plate_format, + state.name.clone(), + )); } }); }); + if state.window_open == false { + *modal_state = ModalState::NewPlateComplete(NewPlateModalComplete(None)); + return; + } if output.is_some() { *modal_state = ModalState::NewPlateComplete(NewPlateModalComplete(output)); } } - -pub fn open_new_plate_modal( - ctx: &egui::Context, - modal_state: &mut ModalState, -) { +pub fn open_new_plate_modal(modal_state: &mut ModalState) { // Do not close another modal if matches!(modal_state, ModalState::None) { *modal_state = ModalState::NewPlate(NewPlateModalState::default()); } } + +#[non_exhaustive] +#[derive(Debug, Clone)] +pub struct EditPlateModalState { + pub name: String, + pub plate_type: plate_tool_lib::plate::PlateType, + pub plate_format: plate_tool_lib::plate::PlateFormat, + pub id: plate_tool_lib::uuid::Uuid, + window_open: bool, +} +impl EditPlateModalState { + fn new( + name: &str, + plate_type: plate_tool_lib::plate::PlateType, + plate_format: plate_tool_lib::plate::PlateFormat, + id: plate_tool_lib::uuid::Uuid, + ) -> Self { + Self { + name: name.to_owned(), + plate_type, + plate_format, + id, + window_open: true, + } + } +} +#[non_exhaustive] +#[derive(Debug)] +pub struct EditPlateModalStateComplete(pub Option); + +fn show_edit_plate_modal(ctx: &egui::Context, modal_state: &mut ModalState) { + let state: &mut EditPlateModalState = { + if let ModalState::EditPlate(x) = modal_state { + x + } else { + return; + } + }; + + let mut should_complete: bool = false; + egui::Window::new("Edit Plate") + .order(egui::Order::Foreground) + .collapsible(false) + .resizable(false) + .open(&mut state.window_open) + .show(ctx, |ui| { + ui.vertical(|ui| { + ui.add(egui::TextEdit::singleline(&mut state.name).hint_text("Plate Name")); + egui::ComboBox::from_label("Plate Format") + .selected_text(format!("{:?}", state.plate_format)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut state.plate_format, + plate_tool_lib::plate::PlateFormat::W96, + "96W", + ); + ui.selectable_value( + &mut state.plate_format, + plate_tool_lib::plate::PlateFormat::W384, + "384W", + ); + ui.selectable_value( + &mut state.plate_format, + plate_tool_lib::plate::PlateFormat::W1536, + "1536W", + ); + }); + if ui.button("Save").clicked() { + should_complete = true; + } + }); + }); + if state.window_open == false { + *modal_state = ModalState::EditPlateComplete(EditPlateModalStateComplete(None)); + return; + } + if should_complete { + *modal_state = + ModalState::EditPlateComplete(EditPlateModalStateComplete(Some(state.to_owned()))); + return; + } +} +pub fn open_edit_plate_modal( + modal_state: &mut ModalState, + name: &str, + plate_type: plate_tool_lib::plate::PlateType, + plate_format: plate_tool_lib::plate::PlateFormat, + id: plate_tool_lib::uuid::Uuid, +) { + // Do not close another modal + if matches!(modal_state, ModalState::None) { + *modal_state = + ModalState::EditPlate(EditPlateModalState::new(name, plate_type, plate_format, id)) + } +} +pub fn open_edit_plate_modal_plateinstance( + modal_state: &mut ModalState, + plate_instance: &plate_tool_lib::plate_instances::PlateInstance, +) { + // Do not close another modal + if matches!(modal_state, ModalState::None) { + *modal_state = ModalState::EditPlate(EditPlateModalState::new( + &plate_instance.name, + plate_instance.plate.plate_type, + plate_instance.plate.plate_format, + plate_instance.get_uuid(), + )); + } +} diff --git a/plate-tool-eframe/src/transfer_menu.rs b/plate-tool-eframe/src/transfer_menu.rs index ad1b4ef..7ca6673 100644 --- a/plate-tool-eframe/src/transfer_menu.rs +++ b/plate-tool-eframe/src/transfer_menu.rs @@ -116,7 +116,7 @@ impl CurrentTransferStateInterior { 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); + 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!(), @@ -188,6 +188,7 @@ pub fn transfer_menu( 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| { @@ -289,7 +290,10 @@ pub fn transfer_menu( transfer.name = state.transfer_name.clone(); main_state.transfer_region_cache.invalidate(&transfer); } - } else { + } 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 { @@ -297,12 +301,23 @@ pub fn transfer_menu( 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; + } +} diff --git a/plate-tool-eframe/src/tree.rs b/plate-tool-eframe/src/tree.rs index f82560d..06c2ad7 100644 --- a/plate-tool-eframe/src/tree.rs +++ b/plate-tool-eframe/src/tree.rs @@ -1,13 +1,22 @@ use eframe::egui; -use crate::{main_state::MainState, transfer_menu::{CurrentTransferState, CurrentTransferStateInterior}}; +use crate::{ + main_state::MainState, + modals::ModalState, + transfer_menu::{CurrentTransferState, CurrentTransferStateInterior}, +}; use std::sync::LazyLock; static SELECT_COLOR: LazyLock = LazyLock::new(|| egui::Color32::from_hex("#aa0000").unwrap()); -pub fn tree(ui: &mut egui::Ui, ms: &mut MainState, cts: &CurrentTransferState) { +pub fn tree( + ui: &mut egui::Ui, + ms: &mut MainState, + cts: &CurrentTransferState, + modal_state: &mut ModalState, +) { // Add all source plates ui.vertical(|ui| { ui.heading("Source Plates"); @@ -29,13 +38,23 @@ pub fn tree(ui: &mut egui::Ui, ms: &mut MainState, cts: &CurrentTransferState) { if r.clicked() { new_uuid = Some(uuid); } + if r.double_clicked() { + let pi = ms.get_source_by_uuid(uuid); + if let Some(pi) = pi { + crate::modals::open_edit_plate_modal_plateinstance(modal_state, &pi); + } + } }); } if let Some(uuid) = new_uuid { - ms.set_current_source(uuid); - ms.set_no_current_transfer(); - if let Some(mut cts) = cts.lock().ok() { - *cts = CurrentTransferStateInterior::default(); + let current_source_uuid = ms.get_current_source_uuid(); + if current_source_uuid.is_some_and(|x| x != uuid) || current_source_uuid.is_none() { + ms.set_current_source(uuid); + ms.set_no_current_transfer(); + if let Some(mut cts) = cts.lock().ok() { + *cts = CurrentTransferStateInterior::default(); + } + set_plates(ms, cts); } } }); @@ -65,13 +84,34 @@ pub fn tree(ui: &mut egui::Ui, ms: &mut MainState, cts: &CurrentTransferState) { if r.clicked() { new_uuid = Some(uuid); } + if r.double_clicked() { + let pi = ms.get_destination_by_uuid(uuid); + if let Some(pi) = pi { + crate::modals::open_edit_plate_modal_plateinstance(modal_state, &pi); + } + } }); } if let Some(uuid) = new_uuid { - ms.set_current_destination(uuid); - ms.set_no_current_transfer(); - if let Some(mut cts) = cts.lock().ok() { - *cts = CurrentTransferStateInterior::default(); + let current_destination_uuid = ms.get_current_destination_uuid(); + if current_destination_uuid.is_some_and(|x| x != uuid) + || current_destination_uuid.is_none() + { + ms.set_current_destination(uuid); + ms.set_no_current_transfer(); + if let Some(mut cts) = cts.lock().ok() { + *cts = CurrentTransferStateInterior::default(); + cts.destination_plate = ms + .get_current_destination_plateinstance() + .expect("Just set destination") + .plate; + } + set_plates(ms, cts); + log::warn!( + "{:?}\n{:?}", + cts, + ms.get_current_destination_plateinstance() + ); } } }); @@ -101,9 +141,23 @@ pub fn tree(ui: &mut egui::Ui, ms: &mut MainState, cts: &CurrentTransferState) { } if let Some(uuid) = new_uuid { ms.set_current_transfer(uuid); - if let Some(new_cts) = CurrentTransferStateInterior::try_new_from_main_state_transfer(ms) { + if let Some(new_cts) = + CurrentTransferStateInterior::try_new_from_main_state_transfer(ms) + { *cts.lock().unwrap() = new_cts; } + set_plates(ms, cts); } }); } + +fn set_plates(ms: &MainState, cts: &CurrentTransferState) { + if let Some(mut cts) = cts.lock().ok() { + 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; + } + } +}