winio_ui_windows_common/darkmode/
mod.rs1use 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#[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#[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
88pub 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
119pub 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
148pub 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}