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}