2025-07-05 14:42:45 -04:00
|
|
|
use eframe::egui;
|
2025-07-22 23:09:01 -04:00
|
|
|
use egui_extras::syntax_highlighting::CodeTheme;
|
2025-07-05 14:42:45 -04:00
|
|
|
|
|
|
|
|
#[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<egui::Visuals> {
|
|
|
|
|
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<egui::Visuals> {
|
|
|
|
|
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<egui::Color32> {
|
|
|
|
|
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]))?;
|
2025-07-15 11:07:41 -04:00
|
|
|
let _secondary = parse_color(colors.get(2).unwrap_or(&colors[0]))?;
|
2025-07-05 14:42:45 -04:00
|
|
|
|
|
|
|
|
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<egui::Visuals> {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-22 23:09:01 -04:00
|
|
|
|
|
|
|
|
pub fn create_code_theme_from_visuals(visuals: &egui::Visuals, font_size: f32) -> CodeTheme {
|
|
|
|
|
if visuals.dark_mode {
|
|
|
|
|
CodeTheme::dark(font_size)
|
|
|
|
|
} else {
|
|
|
|
|
CodeTheme::light(font_size)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|