Skip to main content

winio_ui_windows_common/darkmode/
mod.rs

1use std::{mem::MaybeUninit, sync::LazyLock};
2
3use widestring::U16CStr;
4use windows_sys::Win32::{
5    Foundation::{COLORREF, HWND, LRESULT},
6    Globalization::{
7        CSTR_EQUAL, CompareStringW, FIND_STARTSWITH, FindStringOrdinal, LOCALE_ALL, NORM_IGNORECASE,
8    },
9    Graphics::Gdi::{
10        BLACK_BRUSH, CreateSolidBrush, DeleteObject, GetStockObject, HDC, HGDIOBJ, NULL_BRUSH,
11        ScreenToClient, SetBkColor, SetBkMode, SetTextColor, TRANSPARENT, WHITE_BRUSH,
12    },
13    System::SystemServices::MAX_CLASS_NAME,
14    UI::{
15        Controls::{WC_EDITW, WC_STATICW},
16        WindowsAndMessaging::{
17            ChildWindowFromPoint, GWL_EXSTYLE, GetClassNameW, GetCursorPos, GetWindowLongPtrW,
18            WS_EX_TRANSPARENT,
19        },
20    },
21};
22
23pub struct WinBrush(pub HGDIOBJ);
24
25impl Drop for WinBrush {
26    fn drop(&mut self) {
27        unsafe { DeleteObject(self.0) };
28    }
29}
30
31unsafe impl Send for WinBrush {}
32unsafe impl Sync for WinBrush {}
33
34cfg_if::cfg_if! {
35    if #[cfg(feature = "dark-mode")] {
36        #[path = "hook.rs"]
37        mod imp;
38        pub use imp::*;
39    } else {
40        #[path = "fallback.rs"]
41        mod imp;
42        pub use imp::*;
43    }
44}
45
46#[repr(u32)]
47#[allow(dead_code)]
48#[derive(Debug, PartialEq)]
49pub enum PreferredAppMode {
50    Default    = 0,
51    AllowDark  = 1,
52    ForceDark  = 2,
53    ForceLight = 3,
54}
55
56/// # Safety
57///
58/// `s2` should be a valid null-terminated UTF-16 string.
59#[inline]
60unsafe fn u16_string_eq_ignore_case(s1: &U16CStr, s2: *const u16) -> bool {
61    unsafe {
62        CompareStringW(
63            LOCALE_ALL,
64            NORM_IGNORECASE,
65            s1.as_ptr(),
66            s1.len() as _,
67            s2,
68            -1,
69        ) == CSTR_EQUAL
70    }
71}
72
73/// # Safety
74///
75/// `s2` should be a valid null-terminated UTF-16 string.
76#[inline]
77#[allow(unused)]
78unsafe fn u16_string_starts_with_ignore_case(s1: &U16CStr, s2: *const u16) -> bool {
79    unsafe { FindStringOrdinal(FIND_STARTSWITH, s1.as_ptr(), s1.len() as _, s2, -1, 1) >= 0 }
80}
81
82const WHITE: COLORREF = 0x00FFFFFF;
83const BLACK: COLORREF = 0x00000000;
84
85static EDIT_NORMAL_BACK: LazyLock<WinBrush> =
86    LazyLock::new(|| WinBrush(unsafe { CreateSolidBrush(0x00212121) }));
87
88/// # Safety
89/// It should only be called inside an wndproc, and `hwnd` & `hdc` should be
90/// valid.
91pub unsafe fn control_color_static(hwnd: HWND, hdc: HDC) -> LRESULT {
92    unsafe {
93        let dark = is_dark_mode_allowed_for_app();
94
95        let mut class = [0u16; MAX_CLASS_NAME as usize];
96        GetClassNameW(hwnd, class.as_mut_ptr(), MAX_CLASS_NAME);
97        let class = U16CStr::from_ptr_str(class.as_ptr());
98        let is_static = u16_string_eq_ignore_case(class, WC_STATICW)
99            && ((GetWindowLongPtrW(hwnd, GWL_EXSTYLE) as u32 & WS_EX_TRANSPARENT) != 0);
100
101        SetBkMode(hdc, TRANSPARENT as _);
102        if dark {
103            SetTextColor(hdc, WHITE);
104            if !is_static {
105                SetBkColor(hdc, BLACK);
106            }
107        }
108        let res = if is_static {
109            GetStockObject(NULL_BRUSH)
110        } else if dark {
111            GetStockObject(BLACK_BRUSH)
112        } else {
113            GetStockObject(WHITE_BRUSH)
114        };
115        res as _
116    }
117}
118
119/// # Safety
120/// It should only be called inside an wndproc, and `hwnd` & `hdc` should be
121/// valid.
122pub unsafe fn control_color_link_label(hwnd: HWND, hdc: HDC) -> LRESULT {
123    unsafe {
124        let dark = is_dark_mode_allowed_for_app();
125
126        let is_transparent = (GetWindowLongPtrW(hwnd, GWL_EXSTYLE) as u32 & WS_EX_TRANSPARENT) != 0;
127
128        SetBkMode(hdc, TRANSPARENT as _);
129        if dark {
130            SetTextColor(hdc, 0xFFFC96);
131            if !is_transparent {
132                SetBkColor(hdc, BLACK);
133            }
134        } else {
135            SetTextColor(hdc, 0xCC6600);
136        }
137        let res = if is_transparent {
138            GetStockObject(NULL_BRUSH)
139        } else if dark {
140            GetStockObject(BLACK_BRUSH)
141        } else {
142            GetStockObject(WHITE_BRUSH)
143        };
144        res as _
145    }
146}
147
148/// # Safety
149/// It should only be called inside an wndproc, and `hparent` & `hwnd` & `hdc`
150/// should be valid.
151pub unsafe fn control_color_edit(hparent: HWND, hwnd: HWND, hdc: HDC) -> Option<LRESULT> {
152    unsafe {
153        if is_dark_mode_allowed_for_app() {
154            let mut class = [0u16; MAX_CLASS_NAME as usize];
155            GetClassNameW(hwnd, class.as_mut_ptr(), MAX_CLASS_NAME);
156            let class = U16CStr::from_ptr_str(class.as_ptr());
157
158            SetTextColor(hdc, WHITE);
159            SetBkColor(hdc, BLACK);
160            let is_hover = if u16_string_eq_ignore_case(class, WC_EDITW) {
161                let mut p = MaybeUninit::uninit();
162                GetCursorPos(p.as_mut_ptr());
163                let mut p = p.assume_init();
164                ScreenToClient(hwnd, &mut p);
165                std::ptr::eq(hwnd, ChildWindowFromPoint(hparent, p))
166            } else {
167                false
168            };
169            Some(if is_hover {
170                GetStockObject(BLACK_BRUSH)
171            } else {
172                EDIT_NORMAL_BACK.0
173            } as _)
174        } else {
175            None
176        }
177    }
178}