ponsic_winsafe/win/
window.rs

1use ponsic_types::Recti;
2use std::cell::RefCell;
3use std::fmt::Debug;
4use std::ptr::{null, null_mut};
5use std::time::Duration;
6use winapi::shared::windef::*;
7use winapi::um::libloaderapi::GetModuleHandleW;
8use winapi::um::winuser::*;
9
10use crate::{SystemError, check_error, make_ptr};
11
12use super::timer::Timer;
13
14/// 参考 [WIN32 窗口样式](https://learn.microsoft.com/zh-cn/windows/win32/winmsg/window-styles)
15/// 及 [WIN32 扩展窗口样式](https://learn.microsoft.com/zh-cn/windows/win32/winmsg/extended-window-styles)
16#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
17pub enum WindowStyle {
18    AcceptFiles,
19    AppWindow,
20    ClientEdge,
21    Composited,
22    ContextHelp,
23    ControlParent,
24    DlgModalFrame,
25    Layered,
26    LayoutRtl,
27    Left,
28    LeftScrollBar,
29    LtrReading,
30    MdiChild,
31    NoActivate,
32    NoInheritLayout,
33    NoParentNotify,
34    NoRedirectionBitmap,
35    ExOverlappedWindow,
36    WindowEdge,
37    PaletteWindow,
38    ToolWindow,
39    TopMost,
40    Right,
41    RightScrollBar,
42    RtlReading,
43    StaticEdge,
44    Transparent,
45
46    ChildWindow,
47    ClipChildren,
48    ClipSiblings,
49    Disabled,
50    DlgFrame,
51    Group,
52    HScroll,
53    Maximize,
54    Minimize,
55    Iconic,
56    Child,
57    Popup,
58    Border,
59    PopupWindow,
60    TabStop,
61    SizeBox,
62    Tiled,
63    TiledWindow,
64    Overlapped,
65    Caption,
66    SysMenu,
67    ThickFrame,
68    MinimizeBox,
69    MaximizeBox,
70    OverlappedWindow,
71    Visible,
72    VScroll,
73}
74
75/// 窗口实例
76#[derive(Debug, Clone, Eq, PartialEq)]
77pub struct Window {
78    handle: HWND,
79}
80
81/// 窗口句柄
82///
83/// 若要在线程间传递窗口标识,应使用 `WindowId`
84#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
85pub struct WindowHandle {
86    pub(crate) handle: HWND,
87}
88
89#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
90pub struct WindowId {
91    handle: usize,
92}
93
94impl Window {
95    fn new(handle: HWND) -> Self {
96        Self { handle }
97    }
98
99    pub fn handle(&self) -> WindowHandle {
100        WindowHandle {
101            handle: self.handle,
102        }
103    }
104}
105
106pub trait WindowManager {
107    fn get_handle(&self) -> usize;
108
109    fn id(&self) -> WindowId {
110        WindowId {
111            handle: self.get_handle() as _,
112        }
113    }
114
115    /// 显示窗口
116    fn show(&self) {
117        let handle = self.get_handle() as HWND;
118        unsafe {
119            ShowWindow(handle, SW_SHOW);
120            UpdateWindow(handle);
121        }
122    }
123
124    /// 隐藏窗口
125    fn hide(&self) {
126        let handle = self.get_handle() as HWND;
127        unsafe {
128            ShowWindow(handle, SW_HIDE);
129        }
130    }
131
132    /// 设置窗口文本
133    ///
134    /// # Note
135    /// 此函数通过向回调函数发送请求信息来设置窗口文本,不应在回调函数中无条件调用,否则会引发无限递归而导致栈溢出
136    fn set_text(&self, title: &str) {
137        let handle = self.get_handle() as HWND;
138        let title: Vec<u16> = String::from(title).encode_utf16().chain(Some(0)).collect();
139        unsafe {
140            SetWindowTextW(handle, title.as_ptr());
141        }
142    }
143
144    /// 设置窗口文本
145    ///
146    /// # Note
147    /// 此函数通过向回调函数发送请求信息来设置窗口文本,不应在回调函数中无条件调用,否则会引发无限递归而导致栈溢出
148    #[deprecated(
149        since = "1.0.0",
150        note = "此方法已弃用,请使用 WindowManager::set_text() 代替"
151    )]
152    fn set_title(&self, title: &str) {
153        let handle = self.get_handle() as HWND;
154        let title: Vec<u16> = String::from(title).encode_utf16().chain(Some(0)).collect();
155        unsafe {
156            SetWindowTextW(handle, title.as_ptr());
157        }
158    }
159
160    /// 获取窗口文本
161    ///
162    /// # Note
163    /// 此函数通过向回调函数发送请求信息来获取窗口文本,不应在回调函数中无条件调用,否则会引发无限递归而导致栈溢出
164    fn text(&self) -> String {
165        let handle = self.get_handle() as HWND;
166        unsafe {
167            let len = GetWindowTextLengthW(handle) as usize + 1;
168            let mut title: Vec<u16> = vec![0; len];
169            GetWindowTextW(handle, title.as_mut_ptr(), len as i32);
170            String::from_utf16(&title[0..len - 1]).unwrap()
171        }
172    }
173
174    /// 获取窗口文本
175    ///
176    /// # Note
177    /// 此函数通过向回调函数发送请求信息来获取窗口文本,不应在回调函数中无条件调用,否则会引发无限递归而导致栈溢出
178    #[deprecated(
179        since = "1.0.0",
180        note = "此方法已弃用,请使用 WindowManager::text() 代替"
181    )]
182    fn title(&self) -> String {
183        let handle = self.get_handle() as HWND;
184        unsafe {
185            let len = GetWindowTextLengthW(handle) as usize + 1;
186            let mut title: Vec<u16> = vec![0; len];
187            GetWindowTextW(handle, title.as_mut_ptr(), len as i32);
188            String::from_utf16(&title[0..len - 1]).unwrap()
189        }
190    }
191
192    /// 设置窗口的父窗口
193    fn set_parent(&self, parent: WindowId) {
194        let handle = self.get_handle() as HWND;
195        let parent = parent.handle as HWND;
196        unsafe {
197            SetParent(handle, parent);
198        }
199    }
200
201    /// 获取窗口父窗口的句柄
202    fn parent(&self) -> Option<WindowHandle> {
203        let handle = self.get_handle() as HWND;
204        unsafe {
205            let parent = GetParent(handle);
206            if parent.is_null() {
207                None
208            } else {
209                Some(WindowHandle { handle })
210            }
211        }
212    }
213
214    /// 指示窗口应该被重新绘制
215    fn redraw(&self) {
216        unsafe {
217            RedrawWindow(
218                self.get_handle() as _,
219                null_mut(),
220                null_mut(),
221                RDW_UPDATENOW | RDW_INVALIDATE,
222            );
223        }
224    }
225
226    /// 获取窗口所在的矩形区域坐标
227    fn get_rect(&self) -> Recti {
228        let mut rect = unsafe { std::mem::zeroed::<RECT>() };
229        unsafe { GetWindowRect(self.get_handle() as _, &mut rect) };
230        Recti::new(rect.left, rect.top, rect.right, rect.bottom)
231    }
232
233    /// 获取窗口客户区矩形区域坐标
234    fn get_client_rect(&self) -> Recti {
235        let mut rect = unsafe { std::mem::zeroed::<RECT>() };
236        unsafe { GetClientRect(self.get_handle() as _, &mut rect) };
237        Recti::new(rect.left, rect.top, rect.right, rect.bottom)
238    }
239
240    /// 为窗口注册新的定时器
241    ///
242    /// # Param
243    /// - `duration` 定时器的间隔时间, 取值范围为 `10ms - 2147483647ms(24d20h31min23s647ms)`
244    /// 
245    /// # Note
246    /// 当定时器被注册时,会立即开始循环计时,每次计时结束后都会触发过程回调函数,直到计时器被取消或窗口被销毁
247    fn set_timer(&self, duration: Duration) -> super::Result<Timer> {
248        thread_local! {
249            static NEXT_TIMER_ID: RefCell<usize> = RefCell::new(0);
250        }
251        let uid = NEXT_TIMER_ID.with_borrow_mut(|f| {
252            *f += 1;
253            *f
254        });
255        unsafe {
256            if SetTimer(
257                self.get_handle() as _,
258                uid,
259                duration.as_millis().min(i32::MAX as _) as _,
260                None,
261            ) == 0
262            {
263                check_error()?;
264            }
265        };
266        Ok(Timer::new(self.get_handle() as _, uid))
267    }
268}
269
270impl Window {
271    /// 向指定窗口回调函数发送自定义消息
272    ///
273    /// # Note
274    /// 此函数的调用是同步的,它将等待回调函数对消息进行处理,并返回回调函数的返回值
275    pub unsafe fn send(window: WindowId, msg: u32, wparam: usize, lparam: isize) -> isize {
276        unsafe { SendMessageW(window.handle as _, msg, wparam, lparam) }
277    }
278
279    /// 向指定窗口回调函数发送自定义消息
280    ///
281    /// # Note
282    /// 此函数的调用是异步的,它不等待回调函数对消息进行处理,直接返回,此函数不应传递引用
283    pub unsafe fn post(
284        window: WindowId,
285        msg: u32,
286        wparam: usize,
287        lparam: isize,
288    ) -> Result<i32, SystemError> {
289        match unsafe { PostMessageW(window.handle as _, msg, wparam, lparam) } {
290            0 => Err(check_error().unwrap_err()),
291            res @ _ => Ok(res),
292        }
293    }
294
295    /// 用户自定义消息的起始值
296    pub const USER_DEF_BASE: u32 = WM_USER;
297    pub const APP_DEF_BASE: u32 = WM_APP;
298}
299
300impl WindowManager for Window {
301    fn get_handle(&self) -> usize {
302        self.handle as usize
303    }
304}
305
306impl WindowManager for WindowHandle {
307    fn get_handle(&self) -> usize {
308        self.handle as usize
309    }
310}
311
312/// 窗口构建器
313#[derive(Debug)]
314pub struct Builder {
315    pos_size: (i32, i32, u32, u32),
316    class_name: String,
317    extra_styles: u32,
318    style: u32,
319    title: String,
320    parent: Option<WindowId>,
321    ptr: usize,
322}
323
324impl Builder {
325    pub(super) fn new(class_name: &str, pos_size: (i32, i32, u32, u32)) -> Self {
326        Self {
327            pos_size,
328            class_name: class_name.into(),
329            extra_styles: 0,
330            style: 0,
331            title: "Window".into(),
332            parent: None,
333            ptr: 0,
334        }
335    }
336
337    /// 设置窗口的父窗口
338    pub fn set_parent(mut self, window: WindowId) -> Self {
339        self.parent = Some(window);
340        self
341    }
342
343    /// 设置窗口的样式
344    ///
345    /// 参考 [WindowStyle]
346    pub fn set_style(mut self, style: &[WindowStyle]) -> Self {
347        style.iter().for_each(|style| match style {
348            WindowStyle::AcceptFiles => {
349                self.extra_styles |= WS_EX_ACCEPTFILES;
350            }
351            WindowStyle::AppWindow => {
352                self.extra_styles |= WS_EX_APPWINDOW;
353            }
354            WindowStyle::ClientEdge => {
355                self.extra_styles |= WS_EX_CLIENTEDGE;
356            }
357            WindowStyle::Composited => {
358                self.extra_styles |= WS_EX_COMPOSITED;
359            }
360            WindowStyle::Transparent => {
361                self.extra_styles |= WS_EX_TRANSPARENT;
362            }
363            WindowStyle::ContextHelp => {
364                self.extra_styles |= WS_EX_CONTEXTHELP;
365            }
366            WindowStyle::ControlParent => {
367                self.extra_styles |= WS_EX_CONTROLPARENT;
368            }
369            WindowStyle::DlgModalFrame => {
370                self.extra_styles |= WS_EX_DLGMODALFRAME;
371            }
372            WindowStyle::Layered => {
373                self.extra_styles |= WS_EX_LAYERED;
374            }
375            WindowStyle::LayoutRtl => {
376                self.extra_styles |= WS_EX_LAYOUTRTL;
377            }
378            WindowStyle::Left => {
379                self.extra_styles |= WS_EX_LEFT;
380            }
381            WindowStyle::LeftScrollBar => {
382                self.extra_styles |= WS_EX_LEFTSCROLLBAR;
383            }
384            WindowStyle::LtrReading => {
385                self.extra_styles |= WS_EX_LTRREADING;
386            }
387            WindowStyle::MdiChild => {
388                self.extra_styles |= WS_EX_MDICHILD;
389            }
390            WindowStyle::NoActivate => {
391                self.extra_styles |= WS_EX_NOACTIVATE;
392            }
393            WindowStyle::NoInheritLayout => {
394                self.extra_styles |= WS_EX_NOINHERITLAYOUT;
395            }
396            WindowStyle::NoParentNotify => {
397                self.extra_styles |= WS_EX_NOPARENTNOTIFY;
398            }
399            WindowStyle::NoRedirectionBitmap => {
400                self.extra_styles |= WS_EX_NOREDIRECTIONBITMAP;
401            }
402            WindowStyle::ExOverlappedWindow => {
403                self.extra_styles |= WS_EX_OVERLAPPEDWINDOW;
404            }
405            WindowStyle::WindowEdge => {
406                self.extra_styles |= WS_EX_WINDOWEDGE;
407            }
408            WindowStyle::PaletteWindow => {
409                self.extra_styles |= WS_EX_PALETTEWINDOW;
410            }
411            WindowStyle::ToolWindow => {
412                self.extra_styles |= WS_EX_TOOLWINDOW;
413            }
414            WindowStyle::TopMost => {
415                self.extra_styles |= WS_EX_TOPMOST;
416            }
417            WindowStyle::Right => {
418                self.extra_styles |= WS_EX_RIGHT;
419            }
420            WindowStyle::RightScrollBar => {
421                self.extra_styles |= WS_EX_RIGHTSCROLLBAR;
422            }
423            WindowStyle::RtlReading => {
424                self.extra_styles |= WS_EX_RTLREADING;
425            }
426            WindowStyle::StaticEdge => {
427                self.extra_styles |= WS_EX_STATICEDGE;
428            }
429            WindowStyle::ChildWindow => {
430                self.style |= WS_CHILDWINDOW;
431            }
432            WindowStyle::ClipChildren => {
433                self.style |= WS_CLIPCHILDREN;
434            }
435            WindowStyle::ClipSiblings => {
436                self.style |= WS_CLIPSIBLINGS;
437            }
438            WindowStyle::Disabled => {
439                self.style |= WS_DISABLED;
440            }
441            WindowStyle::DlgFrame => {
442                self.style |= WS_DLGFRAME;
443            }
444            WindowStyle::Group => {
445                self.style |= WS_GROUP;
446            }
447            WindowStyle::HScroll => {
448                self.style |= WS_HSCROLL;
449            }
450            WindowStyle::Maximize => {
451                self.style |= WS_MAXIMIZE;
452            }
453            WindowStyle::Minimize => {
454                self.style |= WS_MINIMIZE;
455            }
456            WindowStyle::Iconic => {
457                self.style |= WS_ICONIC;
458            }
459            WindowStyle::Child => {
460                self.style |= WS_CHILD;
461            }
462            WindowStyle::Popup => {
463                self.style |= WS_POPUP;
464            }
465            WindowStyle::Border => {
466                self.style |= WS_BORDER;
467            }
468            WindowStyle::PopupWindow => {
469                self.style |= WS_POPUPWINDOW;
470            }
471            WindowStyle::TabStop => {
472                self.style |= WS_TABSTOP;
473            }
474            WindowStyle::SizeBox => {
475                self.style |= WS_SIZEBOX;
476            }
477            WindowStyle::Tiled => {
478                self.style |= WS_TILED;
479            }
480            WindowStyle::TiledWindow => {
481                self.style |= WS_TILEDWINDOW;
482            }
483            WindowStyle::Overlapped => {
484                self.style |= WS_OVERLAPPED;
485            }
486            WindowStyle::Caption => {
487                self.style |= WS_CAPTION;
488            }
489            WindowStyle::SysMenu => {
490                self.style |= WS_SYSMENU;
491            }
492            WindowStyle::ThickFrame => {
493                self.style |= WS_THICKFRAME;
494            }
495            WindowStyle::MinimizeBox => {
496                self.style |= WS_MINIMIZEBOX;
497            }
498            WindowStyle::MaximizeBox => {
499                self.style |= WS_MAXIMIZEBOX;
500            }
501            WindowStyle::OverlappedWindow => {
502                self.style |= WS_OVERLAPPEDWINDOW;
503            }
504            WindowStyle::Visible => {
505                self.style |= WS_VISIBLE;
506            }
507            WindowStyle::VScroll => {
508                self.style |= WS_VSCROLL;
509            }
510        });
511        self
512    }
513
514    /// 设置窗口标题
515    pub fn set_title(mut self, title: &str) -> Self {
516        self.title = title.into();
517        self
518    }
519
520    /// 绑定窗口的关联数据
521    pub fn bind_data<T>(mut self, data: T) -> Self {
522        self.ptr = make_ptr(data) as _;
523        self
524    }
525
526    /// 创建窗口
527    pub fn build(self) -> super::Result<Window> {
528        let class_name: Vec<u16> = self.class_name.encode_utf16().chain(Some(0)).collect();
529        let title: Vec<u16> = self.title.encode_utf16().chain(Some(0)).collect();
530        let handle = unsafe {
531            let handle = CreateWindowExW(
532                self.extra_styles,
533                class_name.as_ptr(),
534                title.as_ptr(),
535                self.style,
536                self.pos_size.0,
537                self.pos_size.1,
538                self.pos_size.2 as _,
539                self.pos_size.3 as _,
540                if let Some(window) = self.parent {
541                    window.handle as HWND
542                } else {
543                    null_mut()
544                },
545                null_mut(),
546                GetModuleHandleW(null()),
547                self.ptr as _,
548            );
549            if handle.is_null() {
550                check_error()?;
551            }
552            handle
553        };
554        Ok(Window::new(handle))
555    }
556}
557
558#[cfg(test)]
559mod tests {
560    use super::{super::Result, *};
561    use crate::win::class;
562    use ponsic_types::{Point, Recti as Rect, Size};
563
564    #[test]
565    fn window_builder_test() -> Result<()> {
566        let class = class::Registrar::new("window_builder_test").build()?;
567
568        let window = class
569            .make_window(Rect::from((Point::new(100, 100), Size::new(800, 600))))
570            .set_title("Test")
571            .set_style(&[WindowStyle::OverlappedWindow, WindowStyle::Border]);
572
573        #[allow(unused_variables)]
574        let window = window.build()?;
575
576        Ok(())
577    }
578}
579
580impl WindowHandle {
581    pub unsafe fn from_raw(handle: HWND) -> Self {
582        WindowHandle { handle }
583    }
584}
585
586impl WindowId {
587    pub unsafe fn handle(&self) -> usize {
588        self.handle
589    }
590
591    pub unsafe fn from_raw(handle: usize) -> Self {
592        WindowId { handle }
593    }
594}