awita/
window.rs

1use super::*;
2use once_cell::sync::OnceCell;
3use tokio::sync::{mpsc, oneshot};
4use windows::{
5    Win32::{
6        Foundation::*,
7        Graphics::Gdi::*,
8        System::LibraryLoader::GetModuleHandleW,
9        UI::{HiDpi::*, Shell::*, WindowsAndMessaging::*},
10    },
11};
12
13pub trait StyleObject {
14    fn value(&self) -> u32;
15    fn ex(&self) -> u32;
16}
17
18#[derive(Clone, Copy, Debug)]
19pub struct BorderlessStyle;
20
21impl StyleObject for BorderlessStyle {
22    fn value(&self) -> u32 {
23        WS_POPUP
24    }
25
26    fn ex(&self) -> u32 {
27        0
28    }
29}
30
31#[derive(Clone, Copy, Debug)]
32pub struct Style {
33    value: u32,
34    ex: u32,
35}
36
37impl Style {
38    #[inline]
39    pub const fn new() -> Self {
40        Self {
41            value: WS_OVERLAPPEDWINDOW,
42            ex: 0,
43        }
44    }
45
46    #[inline]
47    pub const fn dialog() -> Self {
48        Self {
49            value: WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
50            ex: 0,
51        }
52    }
53
54    #[inline]
55    pub const fn borderless() -> BorderlessStyle {
56        BorderlessStyle
57    }
58
59    #[inline]
60    pub const fn resizable(mut self, resizable: bool) -> Self {
61        if resizable {
62            self.value |= WS_THICKFRAME;
63        } else {
64            self.value &= !WS_THICKFRAME;
65        }
66        self
67    }
68
69    #[inline]
70    pub const fn has_minimize_box(mut self, flag: bool) -> Self {
71        if flag {
72            self.value |= WS_MINIMIZEBOX;
73        } else {
74            self.value &= !WS_MINIMIZEBOX;
75        }
76        self
77    }
78
79    #[inline]
80    pub const fn has_maximize_box(mut self, flag: bool) -> Self {
81        if flag {
82            self.value |= WS_MAXIMIZEBOX;
83        } else {
84            self.value &= !WS_MAXIMIZEBOX;
85        }
86        self
87    }
88
89    #[inline]
90    pub const fn no_redirection_bitmap(mut self, flag: bool) -> Self {
91        if flag {
92            self.ex |= WS_EX_NOREDIRECTIONBITMAP;
93        } else {
94            self.ex &= !WS_EX_NOREDIRECTIONBITMAP;
95        }
96        self
97    }
98}
99
100impl Default for Style {
101    fn default() -> Self {
102        Self::new()
103    }
104}
105
106impl StyleObject for Style {
107    fn value(&self) -> u32 {
108        self.value
109    }
110
111    fn ex(&self) -> u32 {
112        self.ex
113    }
114}
115
116pub struct Builder {
117    title: String,
118    position: ScreenPoint<i32>,
119    size: Box<dyn ToPhysical<Value = u32, Output = Size<u32>> + Send>,
120    visibility: bool,
121    icon: Option<Icon>,
122    cursor: Option<Cursor>,
123    enable_ime: bool,
124    ime_composition_window_visibility: bool,
125    ime_candidate_window_visibility: bool,
126    accept_drop_files: bool,
127    style: Style,
128}
129
130impl Builder {
131    #[inline]
132    pub fn new() -> Self {
133        Self {
134            title: "".into(),
135            position: Screen(Point::new(0, 0)),
136            size: Box::new(Logical(Size::new(640, 480))),
137            visibility: true,
138            icon: None,
139            cursor: Some(Cursor::Arrow),
140            enable_ime: true,
141            ime_composition_window_visibility: true,
142            ime_candidate_window_visibility: true,
143            accept_drop_files: false,
144            style: Style::new(),
145        }
146    }
147
148    #[inline]
149    pub fn title(mut self, title: impl AsRef<str>) -> Self {
150        self.title = title.as_ref().into();
151        self
152    }
153
154    #[inline]
155    pub fn position(mut self, position: ScreenPoint<i32>) -> Self {
156        self.position = position;
157        self
158    }
159
160    #[inline]
161    pub fn size<S>(mut self, size: S) -> Self
162    where
163        S: ToPhysical<Value = u32, Output = Size<u32>> + Send + 'static,
164    {
165        self.size = Box::new(size);
166        self
167    }
168
169    #[inline]
170    pub fn visible(mut self, visibility: bool) -> Self {
171        self.visibility = visibility;
172        self
173    }
174
175    #[inline]
176    pub fn icon(mut self, icon: Icon) -> Self {
177        self.icon = Some(icon);
178        self
179    }
180
181    #[inline]
182    pub fn cursor(mut self, cursor: Option<Cursor>) -> Self {
183        self.cursor = cursor;
184        self
185    }
186
187    #[inline]
188    pub fn enable_ime(mut self, enable: bool) -> Self {
189        self.enable_ime = enable;
190        self
191    }
192
193    #[inline]
194    pub fn visible_ime_composition_window(mut self, visibility: bool) -> Self {
195        self.ime_composition_window_visibility = visibility;
196        self
197    }
198
199    #[inline]
200    pub fn visible_ime_candidate_window(mut self, visibility: bool) -> Self {
201        self.ime_candidate_window_visibility = visibility;
202        self
203    }
204
205    #[inline]
206    pub fn accept_drop_files(mut self, flag: bool) -> Self {
207        self.accept_drop_files = flag;
208        self
209    }
210
211    #[inline]
212    pub fn style(mut self, object: impl StyleObject) -> Self {
213        self.style = Style {
214            value: object.value(),
215            ex: object.ex(),
216        };
217        self
218    }
219
220    #[inline]
221    pub async fn build(self) -> Result<Window, Error> {
222        Window::new(self).await
223    }
224}
225
226impl std::fmt::Debug for Builder {
227    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
228        write!(f, "window::Builder({})", self.title)
229    }
230}
231
232fn window_class() -> &'static Vec<u16> {
233    static CLASS_NAME: OnceCell<Vec<u16>> = OnceCell::new();
234    CLASS_NAME.get_or_init(|| unsafe {
235        let class_name = "awita_window_class"
236            .encode_utf16()
237            .chain(Some(0))
238            .collect::<Vec<_>>();
239        let wc = WNDCLASSEXW {
240            cbSize: std::mem::size_of::<WNDCLASSEXW>() as _,
241            style: CS_HREDRAW | CS_VREDRAW,
242            lpfnWndProc: Some(procedure::window_proc),
243            hInstance: GetModuleHandleW(None),
244            hbrBackground: HBRUSH(GetStockObject(WHITE_BRUSH).0),
245            lpszClassName: PWSTR(class_name.as_ptr() as _),
246            ..Default::default()
247        };
248        if RegisterClassExW(&wc) == 0 {
249            panic!("RegisterClassEx failed");
250        }
251        class_name
252    })
253}
254
255fn get_dpi_from_point(pt: ScreenPoint<i32>) -> u32 {
256    unsafe {
257        let mut dpi_x = 0;
258        let mut _dpi_y = 0;
259        GetDpiForMonitor(
260            MonitorFromPoint(POINT { x: pt.x, y: pt.y }, MONITOR_DEFAULTTONEAREST),
261            MDT_DEFAULT,
262            &mut dpi_x,
263            &mut _dpi_y,
264        )
265        .unwrap();
266        dpi_x
267    }
268}
269
270pub(crate) struct WindowState {
271    pub cursor: Option<Cursor>,
272    pub ime_composition_window_visibility: bool,
273    pub ime_candidate_window_visibility: bool,
274    pub ime_context: ime::ImmContext,
275    pub ime_position: PhysicalPoint<i32>,
276    pub draw_channel: event::Channel<()>,
277    pub cursor_entered_channel: event::Channel<MouseState>,
278    pub cursor_leaved_channel: event::Channel<MouseState>,
279    pub cursor_moved_chennel: event::Channel<MouseState>,
280    pub mouse_input_channel: event::Channel<event::MouseInput>,
281    pub mouse_wheel_channel: event::Channel<event::MouseWheel>,
282    pub mouse_h_wheel_channel: event::Channel<event::MouseWheel>,
283    pub key_input_channel: event::Channel<event::KeyInput>,
284    pub char_input_channel: event::Channel<char>,
285    pub ime_start_composition_channel: event::Channel<()>,
286    pub ime_composition_channel: event::Channel<(ime::Composition, Option<ime::CandidateList>)>,
287    pub ime_end_composition_channel: event::Channel<Option<String>>,
288    pub moved_channel: event::Channel<ScreenPoint<i32>>,
289    pub resizing_channel: event::Channel<PhysicalSize<u32>>,
290    pub resized_channel: event::Channel<PhysicalSize<u32>>,
291    pub activated_channel: event::Channel<()>,
292    pub inactivated_channel: event::Channel<()>,
293    pub dpi_changed_channel: event::Channel<u32>,
294    pub drop_files_channel: event::Channel<event::DropFiles>,
295    pub close_request_channel: Option<mpsc::Sender<event::CloseRequest>>,
296    pub closed_channel: event::Channel<()>,
297}
298
299#[derive(Clone, Copy, PartialEq, Eq, Debug)]
300pub struct Window {
301    hwnd: HWND,
302}
303
304impl Window {
305    async fn new(builder: Builder) -> Result<Self, Error> {
306        let (tx, rx) = tokio::sync::oneshot::channel();
307        UiThread::post_with_context(move |ctx| unsafe {
308            let dpi = get_dpi_from_point(builder.position);
309            let title = builder
310                .title
311                .encode_utf16()
312                .chain(Some(0))
313                .collect::<Vec<_>>();
314            let size = builder.size.to_physical(dpi);
315            let size =
316                utility::adjust_window_size(size, WS_OVERLAPPEDWINDOW, 0, dpi);
317            let hwnd = CreateWindowExW(
318                builder.style.ex,
319                PWSTR(window_class().as_ptr() as _),
320                PWSTR(title.as_ptr() as _),
321                builder.style.value,
322                builder.position.x,
323                builder.position.y,
324                size.width as _,
325                size.height as _,
326                None,
327                None,
328                GetModuleHandleW(None),
329                std::ptr::null_mut(),
330            );
331            if hwnd == HWND::default() {
332                tx.send(Err(windows::core::Error::from_win32().into())).ok();
333                return;
334            }
335            if let Some(icon) = builder.icon {
336                let big = LPARAM(icon.load().unwrap().0 as _);
337                SendMessageW(hwnd, WM_SETICON, WPARAM(ICON_BIG as _), big);
338                let small = LPARAM(icon.load_small().unwrap().0 as _);
339                SendMessageW(
340                    hwnd,
341                    WM_SETICON,
342                    WPARAM(ICON_SMALL as _),
343                    small,
344                );
345            }
346            DragAcceptFiles(hwnd, builder.accept_drop_files);
347            ShowWindow(hwnd, SW_SHOW);
348            ctx.insert_window(
349                hwnd,
350                WindowState {
351                    cursor: builder.cursor,
352                    ime_composition_window_visibility: builder.ime_composition_window_visibility,
353                    ime_candidate_window_visibility: builder.ime_candidate_window_visibility,
354                    ime_context: ime::ImmContext::new(hwnd),
355                    ime_position: Physical(Point::new(0, 0)),
356                    draw_channel: event::Channel::new(1),
357                    cursor_entered_channel: event::Channel::new(1),
358                    cursor_leaved_channel: event::Channel::new(1),
359                    cursor_moved_chennel: event::Channel::new(1),
360                    mouse_input_channel: event::Channel::new(1),
361                    mouse_wheel_channel: event::Channel::new(1),
362                    mouse_h_wheel_channel: event::Channel::new(1),
363                    key_input_channel: event::Channel::new(8),
364                    char_input_channel: event::Channel::new(8),
365                    ime_start_composition_channel: event::Channel::new(1),
366                    ime_composition_channel: event::Channel::new(1),
367                    ime_end_composition_channel: event::Channel::new(1),
368                    moved_channel: event::Channel::new(1),
369                    resizing_channel: event::Channel::new(1),
370                    resized_channel: event::Channel::new(1),
371                    activated_channel: event::Channel::new(1),
372                    inactivated_channel: event::Channel::new(1),
373                    dpi_changed_channel: event::Channel::new(1),
374                    drop_files_channel: event::Channel::new(1),
375                    close_request_channel: None,
376                    closed_channel: event::Channel::new(1),
377                },
378            );
379            if let Some(window) = ctx.get_window(hwnd) {
380                if builder.enable_ime {
381                    window.ime_context.enable();
382                } else {
383                    window.ime_context.disable();
384                }
385            }
386            tx.send(Ok(Window { hwnd })).ok();
387        });
388        rx.await?
389    }
390
391    #[inline]
392    pub fn builder() -> Builder {
393        Builder::new()
394    }
395
396    #[inline]
397    pub async fn title(&self) -> Result<String, Error> {
398        let hwnd = self.hwnd.clone();
399        let (tx, rx) = oneshot::channel();
400        UiThread::post(move || unsafe {
401            let len = GetWindowTextLengthW(hwnd) as usize;
402            if len == 0 {
403                tx.send(String::new()).ok();
404                return;
405            }
406            let len = len + 1;
407            let mut buf: Vec<u16> = Vec::with_capacity(len);
408            buf.set_len(len);
409            GetWindowTextW(hwnd, PWSTR(buf.as_mut_ptr()), len as _);
410            buf.pop();
411            tx.send(String::from_utf16_lossy(&buf)).ok();
412        });
413        Ok(rx.await?)
414    }
415
416    #[inline]
417    pub async fn set_title(&self, text: impl AsRef<str>) {
418        let hwnd = self.hwnd.clone();
419        let mut text = text
420            .as_ref()
421            .encode_utf16()
422            .chain(Some(0))
423            .collect::<Vec<_>>();
424        UiThread::post(move || unsafe {
425            SetWindowTextW(hwnd, PWSTR(text.as_mut_ptr()));
426        });
427    }
428
429    #[inline]
430    pub async fn position(&self) -> Result<Screen<Point<i32>>, Error> {
431        let hwnd = self.hwnd.clone();
432        let (tx, rx) = oneshot::channel();
433        UiThread::post(move || unsafe {
434            let mut rc = RECT::default();
435            GetWindowRect(hwnd, &mut rc);
436            tx.send(Screen(Point::new(rc.left, rc.top))).ok();
437        });
438        Ok(rx.await?)
439    }
440
441    #[inline]
442    pub fn set_position<T>(&self, position: T)
443    where
444        T: ToPhysical<Output = Point<i32>, Value = i32> + Send + 'static,
445    {
446        let hwnd = self.hwnd.clone();
447        UiThread::post(move || unsafe {
448            let dpi = GetDpiForWindow(hwnd) as i32;
449            let position = position.to_physical(dpi);
450            SetWindowPos(
451                hwnd,
452                HWND::default(),
453                position.x,
454                position.y,
455                0,
456                0,
457                SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE,
458            );
459        });
460    }
461
462    #[inline]
463    pub async fn inner_size(&self) -> Result<Physical<Size<u32>>, Error> {
464        let hwnd = self.hwnd.clone();
465        let (tx, rx) = oneshot::channel();
466        UiThread::post(move || unsafe {
467            let mut rc = RECT::default();
468            GetClientRect(hwnd, &mut rc);
469            tx.send(Physical(Size::new(rc.right as _, rc.bottom as _)))
470                .ok();
471        });
472        Ok(rx.await?)
473    }
474
475    #[inline]
476    pub fn set_inner_size<T>(&self, size: T)
477    where
478        T: ToPhysical<Output = Size<u32>, Value = u32> + Send + 'static,
479    {
480        let hwnd = self.hwnd.clone();
481        UiThread::post(move || unsafe {
482            let dpi = GetDpiForWindow(hwnd);
483            let size = size.to_physical(dpi);
484            SetWindowPos(
485                hwnd,
486                HWND::default(),
487                0,
488                0,
489                size.width as _,
490                size.height as _,
491                SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE,
492            );
493        });
494    }
495
496    #[inline]
497    pub async fn dpi(&self) -> Result<u32, Error> {
498        let hwnd = self.hwnd.clone();
499        let (tx, rx) = oneshot::channel();
500        UiThread::post(move || unsafe {
501            tx.send(GetDpiForWindow(hwnd)).ok();
502        });
503        Ok(rx.await?)
504    }
505
506    #[inline]
507    pub fn show(&self) {
508        unsafe {
509            ShowWindowAsync(self.hwnd, SW_SHOW);
510        }
511    }
512
513    #[inline]
514    pub fn hide(&self) {
515        unsafe {
516            ShowWindowAsync(self.hwnd, SW_HIDE);
517        }
518    }
519
520    #[inline]
521    pub fn redraw(&self) {
522        let hwnd = self.hwnd.clone();
523        UiThread::post(move || unsafe {
524            RedrawWindow(hwnd, std::ptr::null(), HRGN::default(), RDW_INTERNALPAINT);
525        });
526    }
527
528    #[inline]
529    pub fn set_cursor(&self, cursor: Option<Cursor>) {
530        let hwnd = self.hwnd.clone();
531        UiThread::post_with_context(move |ctx| {
532            if let Some(mut window) = ctx.get_window_mut(hwnd) {
533                window.cursor = cursor;
534            }
535        });
536    }
537
538    #[inline]
539    pub async fn is_enabled_ime(&self) -> Result<bool, Error> {
540        let hwnd = self.hwnd.clone();
541        let (tx, rx) = oneshot::channel();
542        UiThread::post_with_context(move |ctx| {
543            let enabled = if let Some(window) = ctx.get_window(hwnd) {
544                Ok(window.ime_context.is_enabled())
545            } else {
546                Err(Error::Closed)
547            };
548            tx.send(enabled).ok();
549        });
550        rx.await?
551    }
552
553    #[inline]
554    pub fn set_enable_ime(&self, enable: bool) {
555        let hwnd = self.hwnd.clone();
556        UiThread::post_with_context(move |ctx| {
557            if let Some(window) = ctx.get_window(hwnd) {
558                if enable {
559                    window.ime_context.enable();
560                } else {
561                    window.ime_context.disable();
562                }
563            }
564        });
565    }
566
567    #[inline]
568    pub fn set_ime_position<T>(&self, position: T)
569    where
570        T: ToPhysical<Output = Point<i32>, Value = i32> + Send + 'static,
571    {
572        let hwnd = self.hwnd.clone();
573        UiThread::post_with_context(move |ctx| unsafe {
574            if let Some(mut window) = ctx.get_window_mut(hwnd) {
575                let dpi = GetDpiForWindow(hwnd) as i32;
576                window.ime_position = position.to_physical(dpi);
577            }
578        });
579    }
580
581    #[inline]
582    pub async fn is_closed(&self) -> bool {
583        let hwnd = self.hwnd.clone();
584        let (tx, rx) = oneshot::channel();
585        UiThread::post_with_context(move |ctx| {
586            tx.send(ctx.get_window(hwnd).is_none()).ok();
587        });
588        rx.await.unwrap_or(true)
589    }
590
591    #[inline]
592    pub fn close_request(&self) {
593        unsafe {
594            PostMessageW(self.hwnd, WM_CLOSE, WPARAM(0), LPARAM(0));
595        }
596    }
597
598    #[inline]
599    pub fn close(&self) {
600        let hwnd = self.hwnd.clone();
601        UiThread::post(move || unsafe {
602            DestroyWindow(hwnd);
603        });
604    }
605
606    #[inline]
607    pub fn raw_handle(&self) -> *mut std::ffi::c_void {
608        self.hwnd.0 as _
609    }
610
611    async fn on_event<F, R>(&self, f: F) -> event::Receiver<R>
612    where
613        F: FnOnce(&WindowState) -> &event::Channel<R> + Send + 'static,
614        R: Clone + Send + 'static,
615    {
616        let hwnd = self.hwnd.clone();
617        let (tx, rx) = oneshot::channel();
618        UiThread::post_with_context(move |ctx| {
619            if let Some(state) = ctx.get_window(hwnd) {
620                tx.send(f(&state).rx.activate_cloned()).ok();
621            }
622        });
623        event::Receiver(rx.await.ok())
624    }
625
626    #[inline]
627    pub async fn draw_receiver(&self) -> event::Receiver<()> {
628        self.on_event(|state| &state.draw_channel).await
629    }
630
631    #[inline]
632    pub async fn cursor_entered_receiver(&self) -> event::Receiver<MouseState> {
633        self.on_event(|state| &state.cursor_entered_channel).await
634    }
635
636    #[inline]
637    pub async fn cursor_leaved_receiver(&self) -> event::Receiver<MouseState> {
638        self.on_event(|state| &state.cursor_leaved_channel).await
639    }
640
641    #[inline]
642    pub async fn cursor_moved_receiver(&self) -> event::Receiver<MouseState> {
643        self.on_event(|state| &state.cursor_moved_chennel).await
644    }
645
646    #[inline]
647    pub async fn mouse_input_receiver(&self) -> event::Receiver<event::MouseInput> {
648        self.on_event(|state| &state.mouse_input_channel).await
649    }
650
651    #[inline]
652    pub async fn mouse_wheel_receiver(&self) -> event::Receiver<event::MouseWheel> {
653        self.on_event(|state| &state.mouse_wheel_channel).await
654    }
655
656    #[inline]
657    pub async fn mouse_h_wheel_receiver(&self) -> event::Receiver<event::MouseWheel> {
658        self.on_event(|state| &state.mouse_h_wheel_channel).await
659    }
660
661    #[inline]
662    pub async fn key_input_receiver(&self) -> event::Receiver<event::KeyInput> {
663        self.on_event(|state| &state.key_input_channel).await
664    }
665
666    #[inline]
667    pub async fn char_input_receiver(&self) -> event::Receiver<char> {
668        self.on_event(|state| &state.char_input_channel).await
669    }
670
671    #[inline]
672    pub async fn ime_start_composition_receiver(&self) -> event::Receiver<()> {
673        self.on_event(|state| &state.ime_start_composition_channel)
674            .await
675    }
676
677    #[inline]
678    pub async fn ime_composition_receiver(
679        &self,
680    ) -> event::Receiver<(ime::Composition, Option<ime::CandidateList>)> {
681        self.on_event(|state| &state.ime_composition_channel).await
682    }
683
684    #[inline]
685    pub async fn ime_end_composition_receiver(&self) -> event::Receiver<Option<String>> {
686        self.on_event(|state| &state.ime_end_composition_channel)
687            .await
688    }
689
690    #[inline]
691    pub async fn moved_receiver(&self) -> event::Receiver<ScreenPoint<i32>> {
692        self.on_event(|state| &state.moved_channel).await
693    }
694
695    #[inline]
696    pub async fn resizing_receiver(&self) -> event::Receiver<PhysicalSize<u32>> {
697        self.on_event(|state| &state.resizing_channel).await
698    }
699
700    #[inline]
701    pub async fn resized_receiver(&self) -> event::Receiver<PhysicalSize<u32>> {
702        self.on_event(|state| &state.resized_channel).await
703    }
704
705    #[inline]
706    pub async fn activated_receiver(&self) -> event::Receiver<()> {
707        self.on_event(|state| &state.activated_channel).await
708    }
709
710    #[inline]
711    pub async fn inactivated_receiver(&self) -> event::Receiver<()> {
712        self.on_event(|state| &state.inactivated_channel).await
713    }
714
715    #[inline]
716    pub async fn dpi_changed_receiver(&self) -> event::Receiver<u32> {
717        self.on_event(|state| &state.dpi_changed_channel).await
718    }
719
720    #[inline]
721    pub async fn drop_files_receiver(&self) -> event::Receiver<event::DropFiles> {
722        self.on_event(|state| &state.drop_files_channel).await
723    }
724
725    #[inline]
726    pub async fn close_request_receiver(&self) -> event::CloseRequestReceiver {
727        let (tx, rx) = mpsc::channel(1);
728        let hwnd = self.hwnd.clone();
729        UiThread::post_with_context(move |ctx| {
730            if let Some(mut window) = ctx.get_window_mut(hwnd) {
731                assert!(window.close_request_channel.is_none());
732                window.close_request_channel = Some(tx);
733            }
734        });
735        event::CloseRequestReceiver::new(rx)
736    }
737
738    #[inline]
739    pub async fn closed_receiver(&self) -> event::Receiver<()> {
740        self.on_event(|state| &state.closed_channel).await
741    }
742}