use eframe::egui; #[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Default)] pub enum Theme { #[default] System, Light, Dark, } pub fn apply(theme: Theme, ctx: &egui::Context) { match theme { Theme::System => { if let Some(system_visuals) = get_system_colors() { ctx.set_visuals(system_visuals); } else { let is_dark = detect_system_dark_mode(); if is_dark { ctx.set_visuals(egui::Visuals::dark()); } else { ctx.set_visuals(egui::Visuals::light()); } } } Theme::Light => { ctx.set_visuals(egui::Visuals::light()); } Theme::Dark => { ctx.set_visuals(egui::Visuals::dark()); } } } fn get_system_colors() -> Option { if let Some(visuals) = get_pywal_colors() { return Some(visuals); } #[cfg(target_os = "linux")] { if let Some(visuals) = get_gtk_colors() { return Some(visuals); } } None } fn get_pywal_colors() -> Option { use std::fs; use std::path::Path; let home = std::env::var("HOME").ok()?; let colors_path = Path::new(&home).join(".cache/wal/colors"); if !colors_path.exists() { return None; } let colors_content = fs::read_to_string(&colors_path).ok()?; let colors: Vec<&str> = colors_content.lines().collect(); if colors.len() < 8 { return None; } let parse_color = |hex: &str| -> Option { if hex.len() != 7 || !hex.starts_with('#') { return None; } let r = u8::from_str_radix(&hex[1..3], 16).ok()?; let g = u8::from_str_radix(&hex[3..5], 16).ok()?; let b = u8::from_str_radix(&hex[5..7], 16).ok()?; Some(egui::Color32::from_rgb(r, g, b)) }; let bg = parse_color(colors[0])?; let fg = parse_color(colors.get(7).unwrap_or(&colors[0]))?; let bg_alt = parse_color(colors.get(8).unwrap_or(&colors[0]))?; let accent = parse_color(colors.get(1).unwrap_or(&colors[0]))?; let secondary = parse_color(colors.get(2).unwrap_or(&colors[0]))?; let mut visuals = if is_dark_color(bg) { egui::Visuals::dark() } else { egui::Visuals::light() }; visuals.window_fill = bg; visuals.extreme_bg_color = bg; visuals.code_bg_color = bg; visuals.panel_fill = bg; visuals.faint_bg_color = blend_colors(bg, bg_alt, 0.15); visuals.error_fg_color = parse_color(colors.get(1).unwrap_or(&colors[0]))?; visuals.override_text_color = Some(fg); visuals.hyperlink_color = accent; visuals.selection.bg_fill = blend_colors(accent, bg, 0.3); visuals.selection.stroke.color = accent; let separator_color = blend_colors(fg, bg, 0.3); visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, separator_color); visuals.widgets.noninteractive.bg_fill = bg; visuals.widgets.noninteractive.fg_stroke.color = fg; visuals.widgets.inactive.bg_fill = blend_colors(bg, accent, 0.2); visuals.widgets.inactive.fg_stroke.color = fg; visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, blend_colors(accent, bg, 0.4)); visuals.widgets.inactive.weak_bg_fill = blend_colors(bg, accent, 0.1); visuals.widgets.hovered.bg_fill = blend_colors(bg, accent, 0.3); visuals.widgets.hovered.fg_stroke.color = fg; visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.0, accent); visuals.widgets.hovered.weak_bg_fill = blend_colors(bg, accent, 0.15); visuals.widgets.active.bg_fill = blend_colors(bg, accent, 0.4); visuals.widgets.active.fg_stroke.color = fg; visuals.widgets.active.bg_stroke = egui::Stroke::new(1.0, accent); visuals.widgets.active.weak_bg_fill = blend_colors(bg, accent, 0.2); visuals.window_stroke = egui::Stroke::new(1.0, separator_color); visuals.widgets.open.bg_fill = blend_colors(bg, accent, 0.25); visuals.widgets.open.fg_stroke.color = fg; visuals.widgets.open.bg_stroke = egui::Stroke::new(1.0, accent); visuals.widgets.open.weak_bg_fill = blend_colors(bg, accent, 0.15); visuals.striped = true; visuals.button_frame = true; visuals.collapsing_header_frame = false; Some(visuals) } fn get_gtk_colors() -> Option { None } fn is_dark_color(color: egui::Color32) -> bool { let r = color.r() as f32 / 255.0; let g = color.g() as f32 / 255.0; let b = color.b() as f32 / 255.0; let r_lin = if r <= 0.04045 { r / 12.92 } else { ((r + 0.055) / 1.055).powf(2.4) }; let g_lin = if g <= 0.04045 { g / 12.92 } else { ((g + 0.055) / 1.055).powf(2.4) }; let b_lin = if b <= 0.04045 { b / 12.92 } else { ((b + 0.055) / 1.055).powf(2.4) }; let luminance = 0.2126 * r_lin + 0.7152 * g_lin + 0.0722 * b_lin; luminance < 0.5 } fn blend_colors(base: egui::Color32, blend: egui::Color32, factor: f32) -> egui::Color32 { let factor = factor.clamp(0.0, 1.0); let inv_factor = 1.0 - factor; egui::Color32::from_rgb( (base.r() as f32 * inv_factor + blend.r() as f32 * factor) as u8, (base.g() as f32 * inv_factor + blend.g() as f32 * factor) as u8, (base.b() as f32 * inv_factor + blend.b() as f32 * factor) as u8, ) } fn detect_system_dark_mode() -> bool { #[cfg(target_os = "windows")] { true } #[cfg(target_os = "macos")] { true } #[cfg(target_os = "linux")] { true } #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] { true } }