Eframe plate

big if true?
This commit is contained in:
Emilia Allison 2024-12-24 21:54:36 -05:00
parent 0e0d72ec9c
commit dc3ef4830a
Signed by: emilia
GPG Key ID: 05D5D1107E5100A1
8 changed files with 2542 additions and 15 deletions

2323
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
[workspace] [workspace]
members = ["plate-tool-web", "plate-tool-lib"] members = ["plate-tool-web", "plate-tool-lib", "plate-tool-eframe"]
resolver = "2" resolver = "2"

View File

@ -0,0 +1,2 @@
[build]
target = "x86_64-unknown-linux-gnu"

View File

@ -0,0 +1,12 @@
[package]
name = "plate-tool-eframe"
version = "0.1.0"
edition = "2021"
[dependencies]
plate-tool-lib = { path = "../plate-tool-lib" }
eframe = { version = "0.30", default-features = false, features = [
"default_fonts",
"glow",
"wayland",
]}

View File

@ -0,0 +1,54 @@
use std::ops::DerefMut;
use std::sync::Mutex;
use eframe::egui::{self};
use plate_tool_lib::plate::PlateFormat;
use crate::plate::{add_grid, PlateUiState};
pub struct PlateToolEframe {
format: PlateFormat,
source_plate_state: Mutex<PlateUiState>,
}
impl Default for PlateToolEframe {
fn default() -> Self {
Self {
format: PlateFormat::W96,
source_plate_state: Mutex::new(PlateUiState::default()),
}
}
}
impl PlateToolEframe {
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
// Would load state here
Default::default()
}
}
impl eframe::App for PlateToolEframe {
// State storage
/*
fn save(&mut self, storage: &mut dyn eframe::Storage) {
unimplemented!()
};
*/
fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
ui.label("shrimp");
});
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("lobster");
add_grid(
PlateFormat::W1536,
ui,
self.source_plate_state.lock().unwrap().deref_mut(),
);
});
}
}

View File

@ -0,0 +1,3 @@
mod app;
mod plate;
pub use app::PlateToolEframe;

View File

@ -0,0 +1,15 @@
use eframe::*;
use eframe::egui;
fn main() -> eframe::Result{
let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("Shrimp"),
..Default::default()
};
eframe::run_native(
"PlateToolEframe",
native_options,
Box::new(|cc| Ok(Box::new(plate_tool_eframe::PlateToolEframe::new(cc))))
)
}

View File

@ -0,0 +1,146 @@
use eframe::egui::{self, pos2, Color32, Rounding};
use plate_tool_lib::plate::PlateFormat;
#[derive(Clone, Copy, Debug)]
pub struct PlateUiState {
pub drag_start_position: Option<egui::Pos2>,
}
impl Default for PlateUiState {
fn default() -> Self {
Self {
drag_start_position: None,
}
}
}
pub fn add_grid(pf: PlateFormat, ui: &mut egui::Ui, state: &mut PlateUiState) {
fn calculate_grid_params(
ul_origin: (f32, f32),
tw: f32,
th: f32,
rows: u8,
columns: u8,
) -> (f32, f32, f32) {
// (Start X, Start Y, Radius)
const PADDING: f32 = 20.0;
let usable_width = tw - 2.0 * PADDING;
let usable_height = th - 2.0 * PADDING;
let shared_width = f32::ceil(usable_width / columns as f32);
let shared_height = f32::ceil(usable_height / rows as f32);
let radius = f32::min(shared_width, shared_height) / 2.0;
(ul_origin.0 + PADDING, ul_origin.1 + PADDING, radius)
}
fn get_hover_well(
response: &egui::Response,
start_x: f32,
start_y: f32,
radius: f32,
) -> Option<(u8, u8)> {
get_well_from_pos(response.hover_pos(), start_x, start_y, radius)
}
fn get_well_from_pos(
position: Option<egui::Pos2>,
start_x: f32,
start_y: f32,
radius: f32,
) -> Option<(u8, u8)> {
// Some((row, column))
position
.map(|p| Into::<(f32, f32)>::into(p))
.and_then(|(x, y)| {
// Check bounds
if x < start_x || y < start_y {
return None;
}
// CHECK Bottom Right BOUND
let solved_column: u8 = (x - start_x).div_euclid(radius * 2.0) as u8;
let solved_row: u8 = (y - start_y).div_euclid(radius * 2.0) as u8;
Some((solved_row, solved_column))
})
}
fn add_grid_sub(rows: u8, columns: u8, ui: &mut egui::Ui, state: &mut PlateUiState) {
let available_size = ui.available_size();
let (response, painter) =
ui.allocate_painter(available_size, egui::Sense::click_and_drag());
let rect = response.rect;
let ul_origin = rect.left_top();
let total_width = rect.width();
let total_height = rect.height();
let stroke = egui::Stroke::new(2.0, egui::Color32::from_gray(128));
let (start_x, start_y, radius) =
calculate_grid_params(ul_origin.into(), total_width, total_height, rows, columns);
// Manage clicks and drags
if response.drag_started() {
state.drag_start_position = Some(response.hover_pos().unwrap());
}
if response.drag_stopped() {
state.drag_start_position = None;
}
let drag_start_well =
get_well_from_pos(state.drag_start_position, start_x, start_y, radius);
let hovered_well = get_hover_well(&response, start_x, start_y, radius);
// Plate Frame
painter.rect_stroke(
egui::Rect {
min: pos2(start_x, start_y),
max: pos2(
start_x + 2.0 * radius * columns as f32,
start_y + 2.0 * radius * rows as f32,
),
},
Rounding::default(),
stroke,
);
// Draw wells
for c_row in 0..rows {
for c_column in 0..columns {
let center = egui::pos2(
start_x + radius + 2.0 * radius * c_column as f32,
start_y + radius + 2.0 * radius * c_row as f32,
);
// Draw fill first
if response.dragged() {
if let (Some(hw), Some(dw)) = (hovered_well, drag_start_well) {
if c_column <= u8::max(hw.1, dw.1)
&& c_column >= u8::min(hw.1, dw.1)
&& c_row <= u8::max(hw.0, dw.0)
&& c_row >= u8::min(hw.0, dw.0)
{
painter.circle_filled(center, radius, Color32::DARK_BLUE);
}
}
} else {
if Some((c_row, c_column)) == hovered_well {
painter.circle_filled(center, radius, Color32::RED);
}
}
// Draw stroke on top
painter.circle_stroke(center, radius - 2.0, stroke);
}
}
}
match pf {
PlateFormat::W96 => add_grid_sub(8, 12, ui, state),
PlateFormat::W384 => add_grid_sub(16, 24, ui, state),
PlateFormat::W1536 => add_grid_sub(32, 48, ui, state),
_ => unimplemented!(),
}
}