From 5e70a17a00a6a47e052e86cb8925dbd04167f4cb Mon Sep 17 00:00:00 2001 From: Emilia Date: Sun, 12 Jan 2025 22:02:37 -0500 Subject: [PATCH] pretty --- plate-tool-eframe/src/app.rs | 2 + plate-tool-eframe/src/lib.rs | 1 + plate-tool-eframe/src/plate.rs | 10 +- plate-tool-eframe/src/styling.rs | 135 +++++++++++++++++++++++++++ plate-tool-eframe/src/tree.rs | 152 ++++++++++++++++++------------- 5 files changed, 230 insertions(+), 70 deletions(-) create mode 100644 plate-tool-eframe/src/styling.rs diff --git a/plate-tool-eframe/src/app.rs b/plate-tool-eframe/src/app.rs index f9d06b0..82be74a 100644 --- a/plate-tool-eframe/src/app.rs +++ b/plate-tool-eframe/src/app.rs @@ -79,6 +79,8 @@ impl eframe::App for PlateToolEframe { } fn update(&mut self, ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) { + crate::styling::set_visuals(&ctx); + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { diff --git a/plate-tool-eframe/src/lib.rs b/plate-tool-eframe/src/lib.rs index 4a97986..15f992c 100644 --- a/plate-tool-eframe/src/lib.rs +++ b/plate-tool-eframe/src/lib.rs @@ -4,4 +4,5 @@ mod tree; mod transfer_menu; mod main_state; mod modals; +mod styling; pub use app::PlateToolEframe; diff --git a/plate-tool-eframe/src/plate.rs b/plate-tool-eframe/src/plate.rs index a82e635..8dc5bda 100644 --- a/plate-tool-eframe/src/plate.rs +++ b/plate-tool-eframe/src/plate.rs @@ -13,13 +13,13 @@ const PALETTE: plate_tool_lib::util::ColorPalette = plate_tool_lib::util::Palett // Stroke types static STROKE_DEFAULT: LazyLock = - LazyLock::new(|| egui::Stroke::new(2.0, egui::Color32::from_gray(128))); + LazyLock::new(|| egui::Stroke::new(2.0, egui::Color32::from_gray(80))); static STROKE_CURRENT: LazyLock = - LazyLock::new(|| egui::Stroke::new(2.0, egui::Color32::from_gray(200))); + LazyLock::new(|| egui::Stroke::new(2.0, egui::Color32::from_gray(30))); static STROKE_SELECT: LazyLock = - LazyLock::new(|| egui::Stroke::new(3.0, egui::Color32::from_gray(200))); + LazyLock::new(|| egui::Stroke::new(3.0, egui::Color32::from_gray(30))); static STROKE_HOVER: LazyLock = - LazyLock::new(|| egui::Stroke::new(3.0, egui::Color32::from_gray(255))); + LazyLock::new(|| egui::Stroke::new(3.0, egui::Color32::from_gray(0))); #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct PlateUiState { @@ -445,7 +445,7 @@ fn add_plate_sub( static DEFAULT_TEXT_COLOR: LazyLock = LazyLock::new(|| egui::Color32::from_gray(128)); static HIGHLIGHT_TEXT_COLOR: LazyLock = - LazyLock::new(|| egui::Color32::from_gray(255)); + LazyLock::new(|| egui::Color32::from_gray(0)); 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) { diff --git a/plate-tool-eframe/src/styling.rs b/plate-tool-eframe/src/styling.rs new file mode 100644 index 0000000..6d6a2a6 --- /dev/null +++ b/plate-tool-eframe/src/styling.rs @@ -0,0 +1,135 @@ +use std::sync::LazyLock; + +use eframe::egui::{ + self, + style::{Selection, Visuals, WidgetVisuals, Widgets}, + Color32, Stroke, +}; + +pub fn set_visuals(ctx: &egui::Context) { + let current = ctx.style().visuals.clone(); + let visuals = Visuals { + override_text_color: Some(STANDARD_THEME.text), + faint_bg_color: STANDARD_THEME.surface0, + extreme_bg_color: STANDARD_THEME.surface0, + window_fill: STANDARD_THEME.base, + panel_fill: STANDARD_THEME.base, + window_stroke: Stroke { + color: STANDARD_THEME.overlay1, + ..current.window_stroke + }, + widgets: Widgets { + noninteractive: make_widget_visual( + current.widgets.noninteractive, + STANDARD_THEME.base, + &STANDARD_THEME, + ), + inactive: make_widget_visual( + current.widgets.inactive, + STANDARD_THEME.surface0, + &STANDARD_THEME, + ), + hovered: make_widget_visual( + current.widgets.hovered, + STANDARD_THEME.light_purple, + &STANDARD_THEME, + ), + active: make_widget_visual( + current.widgets.active, + STANDARD_THEME.surface1, + &STANDARD_THEME, + ), + open: make_widget_visual( + current.widgets.open, + STANDARD_THEME.surface0, + &STANDARD_THEME, + ), + }, + selection: Selection { + bg_fill: STANDARD_THEME.purple, + stroke: Stroke { + color: STANDARD_THEME.overlay1, + ..current.selection.stroke + }, + }, + ..current + }; + ctx.set_visuals(visuals); +} +fn make_widget_visual(current: WidgetVisuals, bg: Color32, theme: &Theme) -> WidgetVisuals { + WidgetVisuals { + bg_fill: bg, + weak_bg_fill: bg, + bg_stroke: Stroke { + color: theme.overlay1, + ..current.bg_stroke + }, + fg_stroke: Stroke { + color: theme.text, + ..current.fg_stroke + }, + ..current + } +} + +pub static STANDARD_THEME: LazyLock = LazyLock::new(|| { + Theme::standard() +}); +#[derive(Debug)] +pub struct Theme { + pub base: Color32, + pub surface0: Color32, + pub surface1: Color32, + pub surface2: Color32, + pub overlay0: Color32, + pub overlay1: Color32, + pub overlay2: Color32, + pub text: Color32, + pub purple: Color32, + pub light_purple: Color32, +} +impl Theme { + fn standard() -> Self { + const BASE_HUE: f32 = 45.0; + const BASE_SATURATION: f32 = 40.0; + Self { + base: hsl_to_color32(BASE_HUE, BASE_SATURATION, 88.0), + surface0: hsl_to_color32(BASE_HUE, BASE_SATURATION, 80.0), + surface1: hsl_to_color32(BASE_HUE, BASE_SATURATION, 75.0), + surface2: hsl_to_color32(BASE_HUE, BASE_SATURATION, 65.0), + overlay0: hsl_to_color32(BASE_HUE, BASE_SATURATION, 60.0), + overlay1: hsl_to_color32(BASE_HUE, BASE_SATURATION, 55.0), + overlay2: hsl_to_color32(BASE_HUE, BASE_SATURATION, 50.0), + text: hsl_to_color32(BASE_HUE, BASE_SATURATION, 25.0), + purple: hsl_to_color32(270.0, 80.0, 80.0), + light_purple: hsl_to_color32(270.0, 70.0, 90.0), + } + } +} + +fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) { + let s = s / 100.0; + let l = l / 100.0; + let chroma = (1.0 - f32::abs(2.0 * l - 1.0)) * s; + let h_prime = h / 60.0; + let x = chroma * (1.0 - f32::abs((h_prime % 2.0) - 1.0)); + + let (r1, g1, b1) = match h_prime { + _ if h_prime < 1.0 => (chroma, x, 0.0), + _ if h_prime >= 1.0 && h_prime < 2.0 => (x, chroma, 0.0), + _ if h_prime >= 2.0 && h_prime < 3.0 => (0.0, chroma, x), + _ if h_prime >= 3.0 && h_prime < 4.0 => (0.0, x, chroma), + _ if h_prime >= 4.0 && h_prime < 5.0 => (x, 0.0, chroma), + _ if h_prime >= 5.0 && h_prime < 6.0 => (chroma, 0.0, x), + _ => unreachable!(), + }; + + let m = l - (chroma/2.0); + let (r,g,b) = ((r1 + m) * 255.0, (g1 + m) * 255.0, (b1 + m) * 255.0); + (r as u8, g as u8, b as u8) +} + +fn hsl_to_color32(h: f32, s: f32, l: f32) -> Color32 { + let (r, g, b) = hsl_to_rgb(h, s, l); + Color32::from_rgb(r, g, b) +} diff --git a/plate-tool-eframe/src/tree.rs b/plate-tool-eframe/src/tree.rs index 06c2ad7..1759411 100644 --- a/plate-tool-eframe/src/tree.rs +++ b/plate-tool-eframe/src/tree.rs @@ -1,4 +1,4 @@ -use eframe::egui; +use eframe::egui::{self, text_selection::visuals, NumExt}; use crate::{ main_state::MainState, @@ -8,8 +8,49 @@ use crate::{ use std::sync::LazyLock; -static SELECT_COLOR: LazyLock = - LazyLock::new(|| egui::Color32::from_hex("#aa0000").unwrap()); +fn tree_label( + ui: &mut egui::Ui, + ms: &MainState, + modal_state: &mut ModalState, + name: &str, + uuid: plate_tool_lib::uuid::Uuid, + selected: bool, +) -> (Option, bool) { + let button_padding = ui.spacing().button_padding; + let total_extra = button_padding + button_padding; + let wrap_width = ui.available_width() - total_extra.x; + let galley = + egui::WidgetText::from(name).into_galley(ui, None, wrap_width, egui::TextStyle::Button); + let desired_size = { + let mut desired_size = total_extra + galley.size(); + desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); + desired_size + }; + let (rect, response) = ui.allocate_at_least(desired_size, egui::Sense::click()); + let text_pos = ui + .layout() + .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) + .min; + let visuals = ui.style().interact_selectable(&response, selected); + if selected || response.hovered() { + let rect = rect.expand(visuals.expansion); + ui.painter().rect( + rect, + visuals.rounding, + visuals.weak_bg_fill, + visuals.bg_stroke, + ); + } + ui.painter().galley(text_pos, galley, visuals.text_color()); + + if response.clicked() { + return (Some(uuid), false); + } + if response.double_clicked() { + return (None, true); + } + (None, false) +} pub fn tree( ui: &mut egui::Ui, @@ -22,29 +63,23 @@ pub fn tree( ui.heading("Source Plates"); let mut new_uuid: Option = 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_uuid().is_some_and(|x| x == uuid) { - f = f.fill(*SELECT_COLOR); + let (potential_new_uuid, dbl_clicked) = tree_label( + ui, + ms, + modal_state, + name, + uuid, + ms.get_current_source_uuid().is_some_and(|x| x == uuid), + ); + if potential_new_uuid.is_some() { + new_uuid = potential_new_uuid; + } + if dbl_clicked { + let pi = ms.get_source_by_uuid(uuid); + if let Some(pi) = pi { + crate::modals::open_edit_plate_modal_plateinstance(modal_state, &pi); } - 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); - } - 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 { let current_source_uuid = ms.get_current_source_uuid(); @@ -68,29 +103,23 @@ pub fn tree( .iter() .map(|x| (&x.name, x.get_uuid())) { - let f = { - let mut f = egui::Frame::none(); - if ms.get_current_destination_uuid().is_some_and(|x| x == uuid) { - f = f.fill(*SELECT_COLOR); + let (potential_new_uuid, dbl_clicked) = tree_label( + ui, + ms, + modal_state, + name, + uuid, + ms.get_current_destination_uuid().is_some_and(|x| x == uuid), + ); + if potential_new_uuid.is_some() { + new_uuid = potential_new_uuid; + } + if dbl_clicked { + let pi = ms.get_destination_by_uuid(uuid); + if let Some(pi) = pi { + crate::modals::open_edit_plate_modal_plateinstance(modal_state, &pi); } - 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); - } - 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 { let current_destination_uuid = ms.get_current_destination_uuid(); @@ -120,24 +149,17 @@ pub fn tree( ui.heading("Transfers"); let mut new_uuid: Option = 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_uuid().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); - } - }); + let (potential_new_uuid, _) = tree_label( + ui, + ms, + modal_state, + name, + uuid, + ms.get_current_transfer_uuid().is_some_and(|x| x == uuid), + ); + if potential_new_uuid.is_some() { + new_uuid = potential_new_uuid; + } } if let Some(uuid) = new_uuid { ms.set_current_transfer(uuid);