diff --git a/plate-tool-eframe/src/app.rs b/plate-tool-eframe/src/app.rs index 11b5dff..25b39d6 100644 --- a/plate-tool-eframe/src/app.rs +++ b/plate-tool-eframe/src/app.rs @@ -5,7 +5,7 @@ use eframe::egui::{self}; use crate::main_state::{construct_fake_mainstate, MainState}; use crate::modals::{self, ModalState}; -use crate::plate::{add_plate, PlateUiState}; +use crate::plate::{add_plate, PlateDisplayOptions, PlateUiState}; use crate::transfer_menu::{transfer_menu, CurrentTransferState, TransferMenuState}; use crate::tree::tree; @@ -13,12 +13,14 @@ use crate::tree::tree; #[derive(Debug, serde::Serialize, serde::Deserialize)] struct MainWindowState { show_side_panel: bool, + plate_display_options: PlateDisplayOptions, } impl Default for MainWindowState { fn default() -> Self { Self { show_side_panel: true, + plate_display_options: PlateDisplayOptions::default(), } } } @@ -101,9 +103,15 @@ impl eframe::App for PlateToolEframe { }); ui.menu_button("Options", |ui| { ui.menu_button("Styles", |ui| { - if ui.button("Toggle transfer hashes").clicked() {} - if ui.button("Toggle volume heatmap").clicked() {} - if ui.button("Toggle current coordinates view").clicked() {} + if ui.button("Toggle transfer hashes").clicked() { + self.main_window_state.plate_display_options.show_transfer_hashes ^= true; + } + if ui.button("Toggle volume heatmap").clicked() { + self.main_window_state.plate_display_options.show_volume_heatmap ^= true; + } + if ui.button("Toggle current coordinates view").clicked() { + self.main_window_state.plate_display_options.show_coordinates ^= true; + } }); ui.menu_button("Exports", |ui| { if ui.button("Change CSV export type").clicked() {} @@ -169,6 +177,7 @@ impl eframe::App for PlateToolEframe { Some(&self.current_transfer_state), ui, self.source_plate_state.lock().unwrap().deref_mut(), + self.main_window_state.plate_display_options, ); } if let Some(destination_pi) = @@ -184,6 +193,7 @@ impl eframe::App for PlateToolEframe { Some(&self.current_transfer_state), ui, self.destination_plate_state.lock().unwrap().deref_mut(), + self.main_window_state.plate_display_options, ); } }); diff --git a/plate-tool-eframe/src/plate.rs b/plate-tool-eframe/src/plate.rs index 2691395..0bb486b 100644 --- a/plate-tool-eframe/src/plate.rs +++ b/plate-tool-eframe/src/plate.rs @@ -1,4 +1,5 @@ use eframe::egui::{self, pos2, Color32, Rounding}; +use eframe::glow::OFFSET; use plate_tool_lib::plate::PlateFormat; use plate_tool_lib::transfer_region::Region; use plate_tool_lib::uuid::Uuid; @@ -37,11 +38,34 @@ impl Default for PlateUiState { struct WellInfo { volume: f32, color: [f64; 3], + highlight: bool, + fill: bool, } impl WellInfo { fn new(volume: f32, color: [f64; 3]) -> Self { - WellInfo { volume, color } + WellInfo { + volume, + color, + highlight: false, + fill: true, + } + } +} + +#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] +pub struct PlateDisplayOptions { + pub show_transfer_hashes: bool, + pub show_volume_heatmap: bool, + pub show_coordinates: bool, +} +impl Default for PlateDisplayOptions { + fn default() -> Self { + Self { + show_transfer_hashes: true, + show_volume_heatmap: false, + show_coordinates: true, + } } } @@ -134,34 +158,34 @@ fn calculate_shading_for_wells( let mut well_infos: Box<[Option]> = vec![None; box_size].into_boxed_slice(); if let Some(transfers) = transfers { - for transfer in transfers { - let cache_result = match plate_type { - plate_tool_lib::plate::PlateType::Source => cache.get_or_calculate_source(transfer), - plate_tool_lib::plate::PlateType::Destination => { - cache.get_or_calculate_destination(transfer) - } - }; - if let Some(wells) = cache_result { - for well in wells.iter().filter(|x| x.row <= rows && x.col <= columns) { - if let Some(Some(mut x)) = well_infos - .get_mut((well.row - 1) as usize * columns as usize + (well.col - 1) as usize) - { - x.volume += 5.0; - x.color = PALETTE.get_ordered(transfer.id, ordered_ids); - } else { - if let Some(mut wi) = well_infos.get_mut( + for transfer in transfers { + let cache_result = match plate_type { + plate_tool_lib::plate::PlateType::Source => cache.get_or_calculate_source(transfer), + plate_tool_lib::plate::PlateType::Destination => { + cache.get_or_calculate_destination(transfer) + } + }; + if let Some(wells) = cache_result { + for well in wells.iter().filter(|x| x.row <= rows && x.col <= columns) { + if let Some(Some(mut x)) = well_infos.get_mut( (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), - )); + x.volume += 5.0; + x.color = PALETTE.get_ordered(transfer.id, ordered_ids); + } else { + if let Some(mut wi) = well_infos.get_mut( + (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), + )); + } } } } } } - } well_infos } @@ -173,6 +197,20 @@ fn f64_to_color32(x: [f64; 3]) -> Color32 { Color32::from_rgb(r, g, b) } +fn draw_cross(painter: &egui::Painter, center: egui::Pos2, radius: f32, stroke: egui::Stroke) { + // Generate points on circle + const OFFSET_ARRAY_X: [f32; 4] = [0.71, -0.71, -0.71, 0.71]; // == sin(2pi/8) == cos(2pi/8) + const OFFSET_ARRAY_Y: [f32; 4] = [0.71, 0.71, -0.71, -0.71]; + let radius_adjusted_array_x: [f32; 4] = core::array::from_fn(|x| OFFSET_ARRAY_X[x] * radius); + let radius_adjusted_array_y: [f32; 4] = core::array::from_fn(|y| OFFSET_ARRAY_Y[y] * radius); + let xs: [f32; 4] = core::array::from_fn(|x| radius_adjusted_array_x[x] + center.x); + let ys: [f32; 4] = core::array::from_fn(|y| radius_adjusted_array_y[y] + center.y); + let pts: [egui::Pos2; 4] = core::array::from_fn(|i| egui::Pos2::new(xs[i], ys[i])); + + painter.line_segment([pts[0], pts[2]], stroke); + painter.line_segment([pts[1], pts[3]], stroke); +} + fn add_plate_sub( size: egui::Vec2, rows: u8, @@ -184,6 +222,7 @@ fn add_plate_sub( current_transfer_state: Option<&CurrentTransferState>, ui: &mut egui::Ui, state: &mut PlateUiState, + display_options: PlateDisplayOptions, ) { let (response, painter) = ui.allocate_painter(size, egui::Sense::click_and_drag()); @@ -288,10 +327,14 @@ fn add_plate_sub( .and_then(|x| x.lock().ok()) .map(|x| x.volume) .unwrap_or(0.0); + let color = well_info.map(|x| x.color).unwrap_or([255.0, 255.0, 255.0]); + let fill = well_info.map(|x| x.color).is_some(); *well_info = Some(WellInfo { - color: [255.0, 255.0, 255.0], + color, volume: 1.0, + fill, + highlight: true, }) } } @@ -327,7 +370,12 @@ fn add_plate_sub( if let Some(well_info) = 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)); + if well_info.fill { + painter.circle_filled(center, radius * 0.80, f64_to_color32(well_info.color)); + } + if well_info.highlight && display_options.show_transfer_hashes { + draw_cross(&painter, center, radius * 0.80, *STROKE_CURRENT); + } } // @@ -366,7 +414,19 @@ fn add_plate_sub( } // Draw row/column labels + let default_font = egui::FontId::monospace(f32::min(radius, 14.0)); + static DEFAULT_TEXT_COLOR: LazyLock = + LazyLock::new(|| egui::Color32::from_gray(128)); + static HIGHLIGHT_TEXT_COLOR: LazyLock = + LazyLock::new(|| egui::Color32::from_gray(255)); for c_row in 0..rows { + let text_color = { + if display_options.show_coordinates && hovered_well.is_some_and(|x| x.0 == c_row + 1) { + *HIGHLIGHT_TEXT_COLOR + } else { + *DEFAULT_TEXT_COLOR + } + }; painter.text( egui::pos2( start_x - 10.0, @@ -374,11 +434,18 @@ fn add_plate_sub( ), egui::Align2::CENTER_CENTER, (c_row + 1).to_string(), - egui::FontId::monospace(f32::min(radius, 14.0)), - egui::Color32::from_gray(128), + default_font.clone(), + text_color, ); } for c_column in 0..columns { + let text_color = { + if display_options.show_coordinates && hovered_well.is_some_and(|x| x.1 == c_column + 1) { + *HIGHLIGHT_TEXT_COLOR + } else { + *DEFAULT_TEXT_COLOR + } + }; painter.text( egui::pos2( start_x + radius + 2.0 * radius * c_column as f32, @@ -386,8 +453,8 @@ fn add_plate_sub( ), egui::Align2::CENTER_CENTER, plate_tool_lib::util::num_to_letters(c_column + 1).unwrap(), - egui::FontId::monospace(f32::min(radius, 14.0)), - egui::Color32::from_gray(128), + default_font.clone(), + text_color, ); } } @@ -402,6 +469,7 @@ pub fn add_plate( current_transfer_state: Option<&CurrentTransferState>, ui: &mut egui::Ui, state: &mut PlateUiState, + display_options: PlateDisplayOptions, ) { add_plate_sub( size, @@ -414,5 +482,6 @@ pub fn add_plate( current_transfer_state, ui, state, + display_options, ); }