use crate::themes::{
mclaren_650gts_gt3, reverie, shade_sanctuary, tokyo_night, tokyo_night_light, wasp, wasp_light,
};
use crate::utils::*;
use crate::visuals::*;
use egui::{Color32, Context, Frame, Id, Margin, Style, Vec2, vec2};
#[cfg(feature = "elegance")]
use elegance::{Palette, Theme as EleganceTheme, Typography as EleganceTypography};
#[cfg(feature = "elegance")]
pub fn elegance_theme_key() -> Id {
Id::new("elegance::theme")
}
#[cfg(feature = "elegance")]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct EleganceThemeKey {
dark: bool,
bg: Color32,
widget_bg: Color32,
border: Color32,
text: Color32,
text_muted: Color32,
accent: Color32,
info: Color32,
success: Color32,
error: Color32,
warning: Color32,
}
#[cfg(feature = "elegance")]
impl EleganceThemeKey {
fn from_theme(theme: &Theme) -> Self {
let c = &theme.colors;
Self {
dark: theme.dark,
bg: c.bg,
widget_bg: c.widget_bg,
border: c.border,
text: c.text,
text_muted: c.text_muted,
accent: c.accent,
info: c.info,
success: c.success,
error: c.error,
warning: c.warning,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ThemeKind {
TokyoNight,
TokyoNightLight,
McLaren650Gts,
Reverie,
ShadeSanctuary,
Wasp,
WaspLight,
}
impl ThemeKind {
pub fn to_str(&self) -> &str {
match self {
ThemeKind::TokyoNight => "Tokyo Night",
ThemeKind::TokyoNightLight => "Tokyo Night Light",
ThemeKind::McLaren650Gts => "McLaren 650GTS",
ThemeKind::Reverie => "Reverie",
ThemeKind::ShadeSanctuary => "Shade Sanctuary",
ThemeKind::Wasp => "Wasp",
ThemeKind::WaspLight => "Wasp Light",
}
}
pub fn to_vec() -> Vec<Self> {
vec![
Self::TokyoNight,
Self::TokyoNightLight,
Self::McLaren650Gts,
Self::Reverie,
Self::ShadeSanctuary,
Self::Wasp,
Self::WaspLight,
]
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone)]
pub struct Theme {
pub dark: bool,
pub image_tint_recommended: bool,
pub kind: ThemeKind,
pub colors: ThemeColors,
pub visuals: ThemeVisuals,
pub typography: Typography,
#[cfg_attr(feature = "serde", serde(default))]
pub spacing: ThemeSpacing,
pub window_frame: Frame,
pub frame1: Frame,
pub frame2: Frame,
pub corner_radius: u8,
pub inner_margin: i8,
pub outer_margin: i8,
pub button_padding: Vec2,
#[cfg(feature = "elegance")]
#[cfg_attr(feature = "serde", serde(skip))]
pub elegance_key: Option<EleganceThemeKey>,
}
impl PartialEq for Theme {
fn eq(&self, other: &Self) -> bool {
self.dark == other.dark
&& self.kind == other.kind
&& self.colors == other.colors
&& self.typography == other.typography
&& self.spacing == other.spacing
&& self.window_frame == other.window_frame
&& self.frame1 == other.frame1
&& self.frame2 == other.frame2
&& self.visuals == other.visuals
}
}
impl Eq for Theme {}
impl Theme {
pub fn new(kind: ThemeKind) -> Self {
let theme = match kind {
ThemeKind::TokyoNight => tokyo_night::theme(),
ThemeKind::TokyoNightLight => tokyo_night_light::theme(),
ThemeKind::McLaren650Gts => mclaren_650gts_gt3::theme(),
ThemeKind::Reverie => reverie::theme(),
ThemeKind::ShadeSanctuary => shade_sanctuary::theme(),
ThemeKind::Wasp => wasp::theme(),
ThemeKind::WaspLight => wasp_light::theme(),
};
theme
}
pub fn style(&self) -> Style {
match self.kind {
ThemeKind::TokyoNight => tokyo_night::style(),
ThemeKind::TokyoNightLight => tokyo_night_light::style(),
ThemeKind::McLaren650Gts => mclaren_650gts_gt3::style(),
ThemeKind::Reverie => reverie::style(),
ThemeKind::ShadeSanctuary => shade_sanctuary::style(),
ThemeKind::Wasp => wasp::style(),
ThemeKind::WaspLight => wasp_light::style(),
}
}
pub fn frame1_visuals(&self) -> FrameVisuals {
self.visuals.frame1_visuals
}
pub fn frame2_visuals(&self) -> FrameVisuals {
self.visuals.frame2_visuals
}
pub fn button_visuals(&self) -> ButtonVisuals {
self.visuals.button_visuals
}
pub fn label_visuals(&self) -> LabelVisuals {
self.visuals.label_visuals
}
pub fn combo_box_visuals(&self) -> ComboBoxVisuals {
self.visuals.combo_box_visuals
}
pub fn text_edit_visuals(&self) -> TextEditVisuals {
self.visuals.text_edit_visuals
}
fn storage_id() -> Id {
Id::new("elements::theme")
}
fn button_visuals_id() -> Id {
Id::new("elements::button_visuals")
}
fn label_visuals_id() -> Id {
Id::new("elements::label_visuals")
}
fn combo_box_visuals_id() -> Id {
Id::new("elements::combo_box_visuals")
}
fn text_edit_visuals_id() -> Id {
Id::new("elements::text_edit_visuals")
}
pub fn button_visuals_from_ctx(ctx: &Context) -> Option<ButtonVisuals> {
ctx.data(|d| d.get_temp(Self::button_visuals_id()))
}
pub fn label_visuals_from_ctx(ctx: &Context) -> Option<LabelVisuals> {
ctx.data(|d| d.get_temp(Self::label_visuals_id()))
}
pub fn combo_box_visuals_from_ctx(ctx: &Context) -> Option<ComboBoxVisuals> {
ctx.data(|d| d.get_temp(Self::combo_box_visuals_id()))
}
pub fn text_edit_visuals_from_ctx(ctx: &Context) -> Option<TextEditVisuals> {
ctx.data(|d| d.get_temp(Self::text_edit_visuals_id()))
}
pub fn install(&mut self, ctx: &Context) {
#[cfg(feature = "elegance")]
self.install_elegance_theme(ctx);
let button_visuals = self.button_visuals();
let label_visuals = self.label_visuals();
let combo_box_visuals = self.combo_box_visuals();
let text_edit_visuals = self.text_edit_visuals();
let mut style = self.style();
self.apply_spacing(&mut style);
let theme = self.clone();
ctx.set_global_style(style);
ctx.data_mut(|d| d.insert_temp(Self::storage_id(), theme));
ctx.data_mut(|d| d.insert_temp(Self::button_visuals_id(), button_visuals));
ctx.data_mut(|d| d.insert_temp(Self::label_visuals_id(), label_visuals));
ctx.data_mut(|d| d.insert_temp(Self::combo_box_visuals_id(), combo_box_visuals));
ctx.data_mut(|d| d.insert_temp(Self::text_edit_visuals_id(), text_edit_visuals));
}
#[cfg(feature = "elegance")]
pub fn install_elegance_theme(&mut self, ctx: &Context) {
let key = EleganceThemeKey::from_theme(&self);
if self.elegance_key == Some(key) {
return;
}
let c = &self.colors;
let mut pal = if key.dark {
Palette::charcoal()
} else {
Palette::frost()
};
pal.is_dark = key.dark;
pal.bg = c.bg;
pal.card = c.widget_bg;
pal.input_bg = c.widget_bg;
pal.border = c.border;
pal.text = c.text;
pal.text_muted = c.text_muted;
pal.text_faint = c.text_muted;
pal.focus = c.accent;
pal.blue = c.info;
pal.green = c.success;
pal.green_hover = c.success;
pal.red = c.error;
pal.red_hover = c.error;
pal.amber = c.warning;
pal.amber_hover = c.warning;
pal.purple = c.accent;
pal.purple_hover = c.accent;
pal.success = c.success;
pal.danger = c.error;
pal.warning = c.warning;
let elegance_typography = EleganceTypography {
body: self.typography.normal,
button: self.typography.normal,
label: self.typography.normal,
small: self.typography.small,
heading: self.typography.heading,
monospace: self.typography.small,
};
let elegance_theme = EleganceTheme {
palette: pal,
control_radius: self.corner_radius as f32,
card_radius: self.corner_radius as f32,
card_padding: self.frame1.inner_margin.top as f32,
control_padding_y: self.button_padding.y as f32,
control_padding_x: self.button_padding.x as f32,
typography: elegance_typography,
};
ctx.data_mut(|d| d.insert_temp(elegance_theme_key(), elegance_theme));
self.elegance_key = Some(key);
}
pub fn current(ctx: &Context) -> Theme {
ctx.data(|d| {
d.get_temp::<Theme>(Self::storage_id())
.unwrap_or_else(|| Theme::new(ThemeKind::TokyoNight))
})
}
fn apply_spacing(&self, style: &mut Style) {
let s = &self.spacing;
style.spacing.item_spacing = vec2(s.md, s.md);
style.spacing.button_padding = self.button_padding;
style.spacing.window_margin = Margin::same(self.inner_margin);
}
pub fn remap_derived_frames(&mut self, old: &ThemeColors) {
let new = self.colors;
if !frame_palette_changed(old, &new) {
return;
}
remap_frame(
&mut self.window_frame,
old.title_bar,
new.title_bar,
old.border,
new.border,
);
remap_frame(
&mut self.frame1,
old.widget_bg,
new.widget_bg,
old.border,
new.border,
);
remap_frame(
&mut self.frame2,
old.bg,
new.bg,
old.border,
new.border,
);
remap_frame_visuals(
&mut self.visuals.frame1_visuals,
old.hover,
new.hover,
old.widget_bg,
new.widget_bg,
old.highlight,
new.highlight,
);
remap_frame_visuals(
&mut self.visuals.frame2_visuals,
old.hover,
new.hover,
old.bg,
new.bg,
old.highlight,
new.highlight,
);
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ThemeVisuals {
pub button_visuals: ButtonVisuals,
pub label_visuals: LabelVisuals,
pub combo_box_visuals: ComboBoxVisuals,
pub text_edit_visuals: TextEditVisuals,
pub frame1_visuals: FrameVisuals,
pub frame2_visuals: FrameVisuals,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ThemeColors {
pub title_bar: Color32,
pub bg: Color32,
pub widget_bg: Color32,
pub hover: Color32,
pub text: Color32,
pub text_muted: Color32,
pub highlight: Color32,
pub border: Color32,
pub accent: Color32,
pub error: Color32,
pub warning: Color32,
pub success: Color32,
pub info: Color32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ThemeSpacing {
pub xs: f32,
pub sm: f32,
pub md: f32,
pub lg: f32,
pub xl: f32,
}
impl Default for ThemeSpacing {
fn default() -> Self {
Self {
xs: 4.0,
sm: 8.0,
md: 12.0,
lg: 16.0,
xl: 24.0,
}
}
}
impl ThemeSpacing {
pub fn margin(self, size: f32) -> Margin {
Margin::same(size as i8)
}
pub fn margin_xy(self, x: f32, y: f32) -> Margin {
Margin::symmetric(x as i8, y as i8)
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Default, Debug, PartialEq)]
pub struct Typography {
pub very_small: f32,
pub small: f32,
pub normal: f32,
pub large: f32,
pub very_large: f32,
pub heading: f32,
}