1#![cfg(windows)]
13
14extern crate alloc;
15
16#[rustfmt::skip]
17mod auto;
18
19pub use auto::*;
20
21use core::fmt;
22use core::mem::MaybeUninit;
23use core::num::NonZeroIsize;
24use core::ptr::NonNull;
25use core::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
26
27use alloc::vec::Vec;
28
29use std::ffi::OsString;
30use std::os::windows::ffi::OsStringExt;
31use std::path::PathBuf;
32
33use windows_sys::w;
34
35use windows_sys::Win32::Foundation::{COLORREF, HINSTANCE, HWND, POINT, RECT, SIZE, S_OK};
36
37use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
38
39use windows_sys::Win32::Graphics::Gdi::{HBITMAP, LOGFONTW};
40
41use windows_sys::Win32::UI::WindowsAndMessaging::WNDCLASSEXW;
42use windows_sys::Win32::UI::WindowsAndMessaging::{
43 CloseWindow, CreateWindowExW, DefWindowProcW, RegisterClassExW,
44};
45
46use windows_sys::Win32::UI::Controls::{
47 CloseThemeData, GetThemeBitmap, GetThemeBool, GetThemeColor, GetThemeEnumValue,
48 GetThemeFilename, GetThemeFont, GetThemeInt, GetThemeIntList, GetThemeMargins,
49 GetThemePosition, GetThemeRect, GetThemeStream, GetThemeString, GetThemeSysBool,
50 GetThemeSysString, OpenThemeData,
51};
52use windows_sys::Win32::UI::Controls::{HTHEME, MARGINS};
53
54#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
56pub struct Color(COLORREF);
57
58impl Color {
59 pub const fn new(red: u8, green: u8, blue: u8) -> Self {
61 Self((red as u32) | ((green as u32) << 8) | ((blue as u32) << 16))
62 }
63
64 pub fn red(&self) -> u8 {
66 (self.0 & 0xFF) as u8
67 }
68
69 pub fn green(&self) -> u8 {
71 ((self.0 >> 8) & 0xFF) as u8
72 }
73
74 pub fn blue(&self) -> u8 {
76 ((self.0 >> 16) & 0xFF) as u8
77 }
78
79 pub fn raw(&self) -> COLORREF {
81 self.0
82 }
83
84 pub const fn from_raw(raw: COLORREF) -> Self {
86 Self(raw)
87 }
88}
89
90impl fmt::Debug for Color {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 f.debug_struct("Color")
93 .field("red", &self.red())
94 .field("green", &self.green())
95 .field("blue", &self.blue())
96 .finish()
97 }
98}
99
100#[derive(Clone, Copy)]
102pub struct LogicalFont(LOGFONTW);
103
104impl LogicalFont {
105 pub fn height(&self) -> i32 {
107 self.0.lfHeight
108 }
109
110 pub fn width(&self) -> i32 {
112 self.0.lfWidth
113 }
114
115 pub fn escapement(&self) -> i32 {
117 self.0.lfEscapement
118 }
119
120 pub fn orientation(&self) -> i32 {
122 self.0.lfOrientation
123 }
124
125 pub fn weight(&self) -> i32 {
127 self.0.lfWeight
128 }
129
130 pub fn italic(&self) -> bool {
132 self.0.lfItalic != 0
133 }
134
135 pub fn underline(&self) -> bool {
137 self.0.lfUnderline != 0
138 }
139
140 pub fn strike_out(&self) -> bool {
142 self.0.lfStrikeOut != 0
143 }
144
145 pub fn char_set(&self) -> u8 {
147 self.0.lfCharSet
148 }
149
150 pub fn output_precision(&self) -> OutputPrecision {
152 use windows_sys::Win32::Graphics::Gdi::OUT_DEFAULT_PRECIS;
153
154 match self.0.lfOutPrecision {
155 OUT_DEFAULT_PRECIS => OutputPrecision::Default,
156 _ => OutputPrecision::Unknown(self.0.lfOutPrecision),
157 }
158 }
159
160 pub fn clip_precision(&self) -> ClipPrecision {
162 use windows_sys::Win32::Graphics::Gdi::CLIP_DEFAULT_PRECIS;
163
164 match self.0.lfClipPrecision {
165 CLIP_DEFAULT_PRECIS => ClipPrecision::Default,
166 _ => ClipPrecision::Unknown(self.0.lfClipPrecision),
167 }
168 }
169
170 pub fn output_quality(&self) -> OutputQuality {
172 use windows_sys::Win32::Graphics::Gdi::{
173 ANTIALIASED_QUALITY, CLEARTYPE_QUALITY, DEFAULT_QUALITY, DRAFT_QUALITY,
174 };
175
176 match self.0.lfQuality {
177 ANTIALIASED_QUALITY => OutputQuality::AntiAliased,
178 CLEARTYPE_QUALITY => OutputQuality::ClearType,
179 DEFAULT_QUALITY => OutputQuality::Default,
180 DRAFT_QUALITY => OutputQuality::Draft,
181 _ => panic!("Unknown output quality"),
182 }
183 }
184
185 pub fn typeface_name(&self) -> OsString {
187 let len = self.0.lfFaceName.iter().position(|&c| c == 0).unwrap();
188 OsString::from_wide(&self.0.lfFaceName[..len])
189 }
190}
191
192impl fmt::Debug for LogicalFont {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.debug_struct("LogicalFont")
195 .field("height", &self.height())
196 .field("width", &self.width())
197 .field("escapement", &self.escapement())
198 .field("orientation", &self.orientation())
199 .field("weight", &self.weight())
200 .field("italic", &self.italic())
201 .field("underline", &self.underline())
202 .field("strike_out", &self.strike_out())
203 .field("char_set", &self.char_set())
204 .field("output_precision", &self.output_precision())
205 .field("clip_precision", &self.clip_precision())
206 .field("output_quality", &self.output_quality())
207 .field("typeface_name", &self.typeface_name())
208 .finish()
209 }
210}
211
212#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
214#[non_exhaustive]
215pub enum OutputPrecision {
216 Default,
218
219 Unknown(u8),
221}
222
223#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
225#[non_exhaustive]
226pub enum ClipPrecision {
227 Default,
229
230 Unknown(u8),
232}
233
234#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
236#[non_exhaustive]
237pub enum OutputQuality {
238 AntiAliased,
240
241 ClearType,
243
244 Default,
246
247 Draft,
249
250 Proof,
252
253 NonAntiAliased,
255}
256
257pub struct Bitmap {
259 handle: NonZeroIsize,
264}
265
266impl fmt::Debug for Bitmap {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 struct PtrWrapper(HBITMAP);
269
270 impl fmt::Debug for PtrWrapper {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 fmt::Pointer::fmt(&(self.0 as *const ()), f)
273 }
274 }
275
276 f.debug_tuple("Bitmap")
277 .field(&PtrWrapper(self.handle() as _))
278 .finish()
279 }
280}
281
282impl Bitmap {
283 pub fn handle(&self) -> HBITMAP {
285 self.handle.get() as _
286 }
287}
288
289#[derive(Clone, Copy)]
291pub struct Margins(MARGINS);
292
293impl Margins {
294 pub fn left(&self) -> i32 {
296 self.0.cxLeftWidth
297 }
298
299 pub fn right(&self) -> i32 {
301 self.0.cxRightWidth
302 }
303
304 pub fn top(&self) -> i32 {
306 self.0.cyTopHeight
307 }
308
309 pub fn bottom(&self) -> i32 {
311 self.0.cyBottomHeight
312 }
313}
314
315impl fmt::Debug for Margins {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 f.debug_struct("Margins")
318 .field("left", &self.left())
319 .field("right", &self.right())
320 .field("top", &self.top())
321 .field("bottom", &self.bottom())
322 .finish()
323 }
324}
325
326#[derive(Clone, Copy)]
328pub struct Point(POINT);
329
330impl Point {
331 pub fn x(&self) -> i32 {
333 self.0.x
334 }
335
336 pub fn y(&self) -> i32 {
338 self.0.y
339 }
340}
341
342impl fmt::Debug for Point {
343 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344 f.debug_struct("Point")
345 .field("x", &self.x())
346 .field("y", &self.y())
347 .finish()
348 }
349}
350
351#[derive(Clone, Copy)]
353pub struct Rect(RECT);
354
355impl Rect {
356 pub fn left(&self) -> i32 {
358 self.0.left
359 }
360
361 pub fn top(&self) -> i32 {
363 self.0.top
364 }
365
366 pub fn right(&self) -> i32 {
368 self.0.right
369 }
370
371 pub fn bottom(&self) -> i32 {
373 self.0.bottom
374 }
375}
376
377impl fmt::Debug for Rect {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 f.debug_struct("Rect")
380 .field("left", &self.left())
381 .field("top", &self.top())
382 .field("right", &self.right())
383 .field("bottom", &self.bottom())
384 .finish()
385 }
386}
387
388#[derive(Clone, Copy)]
390pub struct Size(SIZE);
391
392impl Size {
393 pub fn width(&self) -> i32 {
395 self.0.cx
396 }
397
398 pub fn height(&self) -> i32 {
400 self.0.cy
401 }
402}
403
404impl fmt::Debug for Size {
405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406 f.debug_struct("Size")
407 .field("width", &self.width())
408 .field("height", &self.height())
409 .finish()
410 }
411}
412
413pub struct Theme {
415 handle: NonZeroIsize,
420}
421
422impl fmt::Debug for Theme {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 struct PtrWrapper(HTHEME);
425
426 impl fmt::Debug for PtrWrapper {
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 fmt::Pointer::fmt(&(self.0 as *const ()), f)
429 }
430 }
431
432 f.debug_tuple("Theme")
433 .field(&PtrWrapper(self.handle() as _))
434 .finish()
435 }
436}
437
438impl Drop for Theme {
439 fn drop(&mut self) {
440 unsafe {
443 CloseThemeData(self.handle());
444 }
445 }
446}
447
448type GetThemeBoolRet = Option<bool>;
449type GetThemeColorRet = Option<Color>;
450type GetThemeStreamRet = Option<NonNull<[u8]>>;
451type GetThemeEnumValueRet = Option<i32>;
452type GetThemeFilenameRet = Option<PathBuf>;
453type GetThemeFontRet = Option<LogicalFont>;
454type GetThemeBitmapRet = Option<Bitmap>;
455type GetThemeIntRet = Option<i32>;
456type GetThemeIntListRet = Option<Vec<i32>>;
457type GetThemeMarginsRet = Option<Margins>;
458type GetThemePositionRet = Option<Point>;
459type GetThemeRectRet = Option<Rect>;
460type GetThemeStringRet = Option<OsString>;
461
462impl Theme {
463 fn handle(&self) -> HTHEME {
465 self.handle.get() as HTHEME
466 }
467
468 pub unsafe fn with_window(hwnd: HWND, class_name: &str) -> Option<Self> {
474 let mut name = class_name.encode_utf16().collect::<alloc::vec::Vec<_>>();
475 name.push(0u16);
476
477 let handle = OpenThemeData(hwnd, name.as_ptr());
478
479 NonZeroIsize::new(handle).map(|handle| Self { handle })
480 }
481
482 pub fn new(class_name: &str) -> Option<Self> {
484 let window = DummyWindow::new();
485
486 unsafe { Self::with_window(window.0, class_name) }
487 }
488
489 fn get_theme_bool(&self, part: Part, prop_id: u32) -> Option<bool> {
491 use windows_sys::Win32::Foundation::BOOL;
492 let mut return_value = MaybeUninit::<BOOL>::uninit();
493
494 let (part, state) = part.part_and_state();
496 let result = unsafe {
497 GetThemeBool(
498 self.handle(),
499 part,
500 state,
501 prop_id,
502 return_value.as_mut_ptr(),
503 )
504 };
505
506 if result == S_OK {
507 let return_value = unsafe { return_value.assume_init() };
510 Some(return_value != 0)
511 } else {
512 None
513 }
514 }
515
516 fn get_theme_sys_bool(&self, prop_id: u32) -> Option<bool> {
518 let result = unsafe { GetThemeSysBool(self.handle(), prop_id) };
520
521 if result == S_OK {
522 Some(result != 0)
523 } else {
524 None
525 }
526 }
527
528 fn get_theme_color(&self, part: Part, prop_id: u32) -> Option<Color> {
530 let mut return_value = MaybeUninit::<COLORREF>::uninit();
531
532 let (part, state) = part.part_and_state();
534 let result = unsafe {
535 GetThemeColor(
536 self.handle(),
537 part,
538 state,
539 prop_id,
540 return_value.as_mut_ptr(),
541 )
542 };
543
544 if result == S_OK {
545 let return_value = unsafe { return_value.assume_init() };
548 Some(Color::from_raw(return_value))
549 } else {
550 None
551 }
552 }
553
554 fn get_theme_stream(&self, part: Part, prop_id: u32) -> Option<NonNull<[u8]>> {
556 let mut return_ptr = MaybeUninit::<*mut u8>::uninit();
557 let mut return_len = MaybeUninit::<u32>::uninit();
558
559 let (part, state) = part.part_and_state();
561 let result = unsafe {
562 GetThemeStream(
563 self.handle(),
564 part,
565 state,
566 prop_id as _,
567 return_ptr.as_mut_ptr() as _,
568 return_len.as_mut_ptr(),
569 0,
570 )
571 };
572
573 if result == S_OK {
574 let (return_ptr, return_len) = unsafe {
575 (return_ptr.assume_init(), return_len.assume_init())
578 };
579
580 NonNull::new(return_ptr).map(|ptr| unsafe {
582 NonNull::new_unchecked(core::ptr::slice_from_raw_parts(
583 ptr.as_ptr(),
584 return_len as usize,
585 ) as _)
586 })
587 } else {
588 None
589 }
590 }
591
592 fn get_theme_enum_value(&self, part: Part, prop_id: u32) -> Option<i32> {
594 let mut return_value = MaybeUninit::<i32>::uninit();
595
596 let (part, state) = part.part_and_state();
598 let result = unsafe {
599 GetThemeEnumValue(
600 self.handle(),
601 part,
602 state,
603 prop_id,
604 return_value.as_mut_ptr(),
605 )
606 };
607
608 if result == S_OK {
609 let return_value = unsafe { return_value.assume_init() };
612 Some(return_value)
613 } else {
614 None
615 }
616 }
617
618 fn get_theme_filename(&self, part: Part, prop_id: u32) -> Option<PathBuf> {
620 const MAX_FILENAME_LENGTH: usize = 256;
622 let mut return_value = [0u16; MAX_FILENAME_LENGTH];
623
624 let (part, state) = part.part_and_state();
626 let result = unsafe {
627 GetThemeFilename(
628 self.handle(),
629 part,
630 state,
631 prop_id,
632 return_value.as_mut_ptr() as _,
633 MAX_FILENAME_LENGTH as _,
634 )
635 };
636
637 if result == S_OK {
638 let filename = {
639 let len = return_value
640 .iter()
641 .position(|&c| c == 0)
642 .unwrap_or(MAX_FILENAME_LENGTH);
643
644 OsString::from_wide(&return_value[..len])
645 };
646
647 Some(filename.into())
648 } else {
649 None
650 }
651 }
652
653 fn get_theme_font(&self, part: Part, prop_id: u32) -> Option<LogicalFont> {
655 let mut return_value = MaybeUninit::<LOGFONTW>::uninit();
656
657 let (part, state) = part.part_and_state();
659 let result = unsafe {
660 GetThemeFont(
661 self.handle(),
662 0,
663 part,
664 state,
665 prop_id as _,
666 return_value.as_mut_ptr(),
667 )
668 };
669
670 if result == S_OK {
671 let return_value = unsafe { return_value.assume_init() };
674 Some(LogicalFont(return_value))
675 } else {
676 None
677 }
678 }
679
680 fn get_theme_bitmap(&self, part: Part, prop_id: u32) -> Option<Bitmap> {
682 let mut return_value = MaybeUninit::<HBITMAP>::uninit();
683
684 let (part, state) = part.part_and_state();
686 let result = unsafe {
687 GetThemeBitmap(
688 self.handle(),
689 part,
690 state,
691 prop_id,
692 0,
693 return_value.as_mut_ptr(),
694 )
695 };
696
697 if result == S_OK {
698 let return_value = unsafe { return_value.assume_init() };
701 NonZeroIsize::new(return_value).map(|handle| Bitmap { handle })
702 } else {
703 None
704 }
705 }
706
707 fn get_theme_int(&self, part: Part, prop_id: u32) -> Option<i32> {
709 let mut return_value = MaybeUninit::<i32>::uninit();
710
711 let (part, state) = part.part_and_state();
713 let result = unsafe {
714 GetThemeInt(
715 self.handle(),
716 part,
717 state,
718 prop_id,
719 return_value.as_mut_ptr(),
720 )
721 };
722
723 if result == S_OK {
724 let return_value = unsafe { return_value.assume_init() };
727 Some(return_value)
728 } else {
729 None
730 }
731 }
732
733 fn get_theme_int_list(&self, part: Part, prop_id: u32) -> Option<Vec<i32>> {
735 use windows_sys::Win32::UI::Controls::INTLIST;
736
737 let mut return_list = MaybeUninit::<INTLIST>::uninit();
738
739 let (part, state) = part.part_and_state();
741 let result = unsafe {
742 GetThemeIntList(
743 self.handle(),
744 part,
745 state,
746 prop_id,
747 return_list.as_mut_ptr(),
748 )
749 };
750
751 if result == S_OK {
752 let return_list = unsafe { return_list.assume_init() };
754 let ints = &return_list.iValues[..return_list.iValueCount as _];
755 Some(ints.to_vec())
756 } else {
757 None
758 }
759 }
760
761 fn get_theme_margins(&self, part: Part, prop_id: u32) -> Option<Margins> {
763 let mut return_value = MaybeUninit::<MARGINS>::uninit();
764
765 let (part, state) = part.part_and_state();
767 let result = unsafe {
768 GetThemeMargins(
769 self.handle(),
770 0,
771 part,
772 state,
773 prop_id,
774 std::ptr::null(),
775 return_value.as_mut_ptr(),
776 )
777 };
778
779 if result == S_OK {
780 let return_value = unsafe { return_value.assume_init() };
783 Some(Margins(return_value))
784 } else {
785 None
786 }
787 }
788
789 fn get_theme_position(&self, part: Part, prop_id: u32) -> Option<Point> {
791 let mut return_value = MaybeUninit::<POINT>::uninit();
792
793 let (part, state) = part.part_and_state();
795 let result = unsafe {
796 GetThemePosition(
797 self.handle(),
798 part,
799 state,
800 prop_id,
801 return_value.as_mut_ptr(),
802 )
803 };
804
805 if result == S_OK {
806 let return_value = unsafe { return_value.assume_init() };
809 Some(Point(return_value))
810 } else {
811 None
812 }
813 }
814
815 fn get_theme_rect(&self, part: Part, prop_id: u32) -> Option<Rect> {
817 let mut return_value = MaybeUninit::<RECT>::uninit();
818
819 let (part, state) = part.part_and_state();
821 let result = unsafe {
822 GetThemeRect(
823 self.handle(),
824 part,
825 state,
826 prop_id as _,
827 return_value.as_mut_ptr(),
828 )
829 };
830
831 if result == S_OK {
832 let return_value = unsafe { return_value.assume_init() };
835 Some(Rect(return_value))
836 } else {
837 None
838 }
839 }
840
841 fn get_theme_string(&self, part: Part, prop_id: u32) -> Option<OsString> {
843 const MAX_STRING_LENGTH: usize = 1024;
844 let mut return_value = MaybeUninit::<[u16; MAX_STRING_LENGTH]>::uninit();
845
846 let (part, state) = part.part_and_state();
848 let result = unsafe {
849 GetThemeString(
850 self.handle(),
851 part,
852 state,
853 prop_id as _,
854 return_value.as_mut_ptr() as _,
855 MAX_STRING_LENGTH as _,
856 )
857 };
858
859 if result == S_OK {
860 let return_value = unsafe { return_value.assume_init() };
863 let slice = {
864 let len = return_value
865 .iter()
866 .position(|&c| c == 0)
867 .unwrap_or(MAX_STRING_LENGTH);
868
869 &return_value[..len]
870 };
871
872 Some(OsString::from_wide(slice))
873 } else {
874 None
875 }
876 }
877
878 fn get_theme_sys_string(&self, prop_id: u32) -> Option<OsString> {
880 const MAX_STRING_LENGTH: usize = 1024;
881 let mut return_value = MaybeUninit::<[u16; MAX_STRING_LENGTH]>::uninit();
882
883 let result = unsafe {
885 GetThemeSysString(
886 self.handle(),
887 prop_id as _,
888 return_value.as_mut_ptr() as _,
889 MAX_STRING_LENGTH as _,
890 )
891 };
892
893 if result == S_OK {
894 let return_value = unsafe { return_value.assume_init() };
897 let slice = {
898 let len = return_value
899 .iter()
900 .position(|&c| c == 0)
901 .unwrap_or(MAX_STRING_LENGTH);
902
903 &return_value[..len]
904 };
905
906 Some(OsString::from_wide(slice))
907 } else {
908 None
909 }
910 }
911}
912
913struct DummyWindow(HWND);
915
916impl Drop for DummyWindow {
917 fn drop(&mut self) {
918 unsafe {
920 CloseWindow(self.0);
921 }
922 }
923}
924
925impl DummyWindow {
926 fn new() -> Self {
928 static CLASS_CREATED: AtomicBool = AtomicBool::new(false);
929 const CLASS_NAME: *const u16 = w!("notgull::wintheme::DummyWindow");
930
931 if !CLASS_CREATED.load(Ordering::Relaxed) {
933 let class = WNDCLASSEXW {
934 cbSize: core::mem::size_of::<WNDCLASSEXW>() as u32,
935 style: 0,
936 lpfnWndProc: Some(DefWindowProcW),
937 lpszClassName: CLASS_NAME,
938 ..unsafe { core::mem::zeroed() }
939 };
940
941 unsafe {
944 RegisterClassExW(&class);
945 }
946
947 CLASS_CREATED.store(true, Ordering::Release);
948 }
949
950 let hwnd = unsafe {
952 CreateWindowExW(
953 0,
954 CLASS_NAME,
955 CLASS_NAME,
956 0,
957 0,
958 0,
959 1,
960 1,
961 0,
962 0,
963 instance(),
964 core::ptr::null(),
965 )
966 };
967
968 if hwnd == 0 {
969 panic!("CreateWindowExW failed");
970 }
971
972 Self(hwnd)
973 }
974}
975
976fn instance() -> HINSTANCE {
977 static INSTANCE: AtomicIsize = AtomicIsize::new(0);
978
979 let instance = INSTANCE.load(Ordering::Relaxed);
980 if instance != 0 {
981 return instance;
982 }
983
984 let handle = unsafe { GetModuleHandleW(core::ptr::null()) };
986
987 if handle == 0 {
988 panic!("GetModuleHandleW failed");
989 }
990
991 INSTANCE
993 .compare_exchange(instance, handle, Ordering::SeqCst, Ordering::SeqCst)
994 .unwrap_or_else(|x| x)
995}