use windows_sys::Win32::Graphics::Dwm::DwmGetColorizationColor;
use windows_sys::Win32::UI::Accessibility::HIGHCONTRASTW;
use windows_sys::Win32::UI::WindowsAndMessaging::{SystemParametersInfoW, SPI_GETHIGHCONTRAST};
use winspaces_win32::registry::{read_hkcu_string, read_hkcu_u32, write_hkcu_string};
pub use winspaces_win32::gdi::color::rgb;
use winspaces_win32::gdi::color::Tint;
const THEME_KEY: &str = "Software\\WinSpaces";
const THEME_VALUE: &str = "GuiTheme";
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ThemePref {
System,
Light,
Dark,
}
impl ThemePref {
pub fn as_str(self) -> &'static str {
match self {
ThemePref::System => "system",
ThemePref::Light => "light",
ThemePref::Dark => "dark",
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Self {
match s {
"light" => ThemePref::Light,
"dark" => ThemePref::Dark,
_ => ThemePref::System,
}
}
pub fn label(self) -> &'static str {
use winspaces_common::i18n::t;
use winspaces_common::Msg;
t(match self {
ThemePref::System => Msg::ThemeSystem,
ThemePref::Light => Msg::ThemeLight,
ThemePref::Dark => Msg::ThemeDark,
})
}
pub const ALL: [ThemePref; 3] = [ThemePref::System, ThemePref::Light, ThemePref::Dark];
pub fn index(self) -> usize {
Self::ALL.iter().position(|p| *p == self).unwrap_or(0)
}
}
#[derive(Clone)]
pub struct Palette {
pub light: bool,
pub high_contrast: bool,
pub base_r: u32,
pub base_g: u32,
pub base_b: u32,
pub base_alpha: u32,
pub text: u32,
pub text_dim: u32,
pub card: u32,
pub card_hover: u32,
pub card_pressed: u32,
pub card_stroke: u32,
pub ctl: u32,
pub ctl_hover: u32,
pub ctl_pressed: u32,
pub ctl_stroke: u32,
pub accent: u32,
pub accent_text: u32,
pub success: u32,
pub success_bg: u32,
pub critical: u32,
pub critical_bg: u32,
pub focus: u32,
}
impl Palette {
pub fn tint(&self) -> Tint {
Tint {
r: self.base_r,
g: self.base_g,
b: self.base_b,
alpha: self.base_alpha,
}
}
}
pub fn load_pref() -> ThemePref {
read_hkcu_string(THEME_KEY, THEME_VALUE)
.map(|s| ThemePref::from_str(&s))
.unwrap_or(ThemePref::System)
}
pub fn save_pref(pref: ThemePref) {
let _ = write_hkcu_string(THEME_KEY, THEME_VALUE, pref.as_str());
}
pub fn system_uses_light() -> bool {
read_hkcu_u32(
"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
"AppsUseLightTheme",
)
.map(|flag| flag != 0)
.unwrap_or(true)
}
pub fn high_contrast_active() -> bool {
unsafe {
let mut hc: HIGHCONTRASTW = std::mem::zeroed();
hc.cbSize = std::mem::size_of::<HIGHCONTRASTW>() as u32;
if SystemParametersInfoW(SPI_GETHIGHCONTRAST, hc.cbSize, &mut hc as *mut _ as _, 0) == 0 {
return false;
}
const HCF_HIGHCONTRASTON: u32 = 1;
hc.dwFlags & HCF_HIGHCONTRASTON != 0
}
}
fn accent_color() -> u32 {
unsafe {
let mut argb: u32 = 0;
let mut opaque: i32 = 0;
if DwmGetColorizationColor(&mut argb, &mut opaque) == 0 {
let r = (argb >> 16) & 0xFF;
let g = (argb >> 8) & 0xFF;
let b = argb & 0xFF;
return r | (g << 8) | (b << 16);
}
}
rgb(0x00, 0x67, 0xC0)
}
pub fn preview_accent() -> u32 {
if high_contrast_active() {
use windows_sys::Win32::Graphics::Gdi::GetSysColor;
const COLOR_HIGHLIGHT: i32 = 13;
return unsafe { GetSysColor(COLOR_HIGHLIGHT) } as u32;
}
accent_color()
}
pub fn resolves_light(pref: ThemePref) -> bool {
match pref {
ThemePref::Light => true,
ThemePref::Dark => false,
ThemePref::System => system_uses_light(),
}
}
pub fn build_palette(pref: ThemePref, mica: bool) -> Palette {
let high_contrast = high_contrast_active();
let light = resolves_light(pref);
let accent = accent_color();
if high_contrast {
use windows_sys::Win32::Graphics::Gdi::GetSysColor;
const COLOR_WINDOW: i32 = 5;
const COLOR_WINDOWTEXT: i32 = 8;
const COLOR_GRAYTEXT: i32 = 17;
const COLOR_HIGHLIGHT: i32 = 13;
const COLOR_HIGHLIGHTTEXT: i32 = 14;
const COLOR_BTNFACE: i32 = 15;
let win = unsafe { GetSysColor(COLOR_WINDOW) } as u32;
let text = unsafe { GetSysColor(COLOR_WINDOWTEXT) } as u32;
let gray = unsafe { GetSysColor(COLOR_GRAYTEXT) } as u32;
let hilite = unsafe { GetSysColor(COLOR_HIGHLIGHT) } as u32;
let hilite_text = unsafe { GetSysColor(COLOR_HIGHLIGHTTEXT) } as u32;
let btn = unsafe { GetSysColor(COLOR_BTNFACE) } as u32;
return Palette {
light,
high_contrast,
base_r: win & 0xFF,
base_g: (win >> 8) & 0xFF,
base_b: (win >> 16) & 0xFF,
base_alpha: 255,
text,
text_dim: gray,
card: win,
card_hover: btn,
card_pressed: btn,
card_stroke: text,
ctl: btn,
ctl_hover: hilite,
ctl_pressed: hilite,
ctl_stroke: text,
accent: hilite,
accent_text: hilite_text,
success: text,
success_bg: win,
critical: text,
critical_bg: win,
focus: text,
};
}
if light {
Palette {
light,
high_contrast,
base_r: 0xF3,
base_g: 0xF3,
base_b: 0xF3,
base_alpha: if mica { 216 } else { 255 },
text: rgb(0x1B, 0x1B, 0x1B),
text_dim: rgb(0x60, 0x60, 0x60),
card: rgb(0xFB, 0xFB, 0xFB),
card_hover: rgb(0xF6, 0xF6, 0xF6),
card_pressed: rgb(0xF0, 0xF0, 0xF0),
card_stroke: rgb(0xE5, 0xE5, 0xE5),
ctl: rgb(0xFB, 0xFB, 0xFB),
ctl_hover: rgb(0xF6, 0xF6, 0xF6),
ctl_pressed: rgb(0xF5, 0xF5, 0xF5),
ctl_stroke: rgb(0xD1, 0xD1, 0xD1),
accent,
accent_text: rgb(0xFF, 0xFF, 0xFF),
success: rgb(0x0F, 0x7B, 0x0F),
success_bg: rgb(0xDF, 0xF6, 0xDD),
critical: rgb(0xC4, 0x2B, 0x1C),
critical_bg: rgb(0xFD, 0xE7, 0xE9),
focus: rgb(0x1B, 0x1B, 0x1B),
}
} else {
Palette {
light,
high_contrast,
base_r: 0x20,
base_g: 0x20,
base_b: 0x20,
base_alpha: if mica { 216 } else { 255 },
text: rgb(0xF5, 0xF5, 0xF5),
text_dim: rgb(0xA6, 0xA6, 0xA6),
card: rgb(0x2B, 0x2B, 0x2B),
card_hover: rgb(0x32, 0x32, 0x32),
card_pressed: rgb(0x27, 0x27, 0x27),
card_stroke: rgb(0x1D, 0x1D, 0x1D),
ctl: rgb(0x34, 0x34, 0x34),
ctl_hover: rgb(0x3A, 0x3A, 0x3A),
ctl_pressed: rgb(0x30, 0x30, 0x30),
ctl_stroke: rgb(0x45, 0x45, 0x45),
accent,
accent_text: rgb(0x00, 0x00, 0x00),
success: rgb(0x6C, 0xCB, 0x5F),
success_bg: rgb(0x39, 0x3D, 0x1B),
critical: rgb(0xFF, 0x99, 0xA4),
critical_bg: rgb(0x44, 0x27, 0x26),
focus: rgb(0xF5, 0xF5, 0xF5),
}
}
}
pub struct MenuTheme {
pub text: u32,
pub dim: u32,
pub hover: u32,
pub separator: u32,
pub bg_r: u32,
pub bg_g: u32,
pub bg_b: u32,
}
pub struct IndicatorTheme {
pub bg: u32,
pub text: u32,
pub border: u32,
}
pub fn indicator_theme(light: bool) -> IndicatorTheme {
if high_contrast_active() {
use windows_sys::Win32::Graphics::Gdi::GetSysColor;
const COLOR_WINDOW: i32 = 5;
const COLOR_WINDOWTEXT: i32 = 8;
let text = unsafe { GetSysColor(COLOR_WINDOWTEXT) } as u32;
return IndicatorTheme {
bg: unsafe { GetSysColor(COLOR_WINDOW) } as u32,
text,
border: text,
};
}
if light {
IndicatorTheme {
bg: rgb(0xF9, 0xF9, 0xF9),
text: rgb(0x1B, 0x1B, 0x1B),
border: rgb(0xE0, 0xE0, 0xE0),
}
} else {
IndicatorTheme {
bg: rgb(0x2C, 0x2C, 0x2C),
text: rgb(0xF5, 0xF5, 0xF5),
border: rgb(0x45, 0x45, 0x45),
}
}
}
pub fn menu_theme(light: bool) -> MenuTheme {
if light {
MenuTheme {
text: rgb(0x1B, 0x1B, 0x1B),
dim: rgb(0x60, 0x60, 0x60),
hover: rgb(0xEA, 0xEA, 0xEA),
separator: rgb(0xE0, 0xE0, 0xE0),
bg_r: 0xF9,
bg_g: 0xF9,
bg_b: 0xF9,
}
} else {
MenuTheme {
text: rgb(0xF5, 0xF5, 0xF5),
dim: rgb(0xA6, 0xA6, 0xA6),
hover: rgb(0x3D, 0x3D, 0x3D),
separator: rgb(0x45, 0x45, 0x45),
bg_r: 0x2C,
bg_g: 0x2C,
bg_b: 0x2C,
}
}
}