Skip to main content

winio_ui_winui/widgets/
window.rs

1use std::{cell::RefCell, mem::MaybeUninit, rc::Rc, sync::Arc};
2
3use compio_log::error;
4use inherit_methods_macro::inherit_methods;
5use send_wrapper::SendWrapper;
6use windows::{
7    Foundation::TypedEventHandler,
8    UI::ViewManagement::UISettings,
9    Win32::Foundation::E_NOINTERFACE,
10    core::{Interface, Ref},
11};
12use windows_sys::Win32::UI::{
13    HiDpi::GetDpiForWindow,
14    WindowsAndMessaging::{
15        GetClientRect, IMAGE_ICON, LR_DEFAULTCOLOR, LR_DEFAULTSIZE, LR_SHARED, LoadImageW,
16    },
17};
18use winio_callback::{Callback, SyncCallback};
19use winio_handle::{AsContainer, AsWindow, BorrowedContainer, BorrowedWindow};
20use winio_primitive::{Point, Size};
21use winio_ui_windows_common::{
22    Backdrop, get_current_module_handle, set_backdrop, syscall, window_use_dark_mode,
23};
24use winui3::{
25    IWindowNative,
26    Microsoft::UI::{
27        Composition::SystemBackdrops::MicaKind,
28        IconId, WindowId,
29        Windowing::{
30            AppWindow, AppWindowChangedEventArgs, AppWindowClosingEventArgs, TitleBarTheme,
31        },
32        Xaml::{
33            self as MUX, Controls as MUXC,
34            Media::{MicaBackdrop, SystemBackdrop},
35            RoutedEventHandler,
36        },
37    },
38};
39
40use crate::{
41    Error, GlobalRuntime, Result, Widget, platform::backdrop::CustomDesktopAcrylicBackdrop,
42    widgets::Convertible,
43};
44
45#[derive(Debug)]
46pub struct Window {
47    on_size: SendWrapper<Rc<Callback>>,
48    on_move: SendWrapper<Rc<Callback>>,
49    on_close: SendWrapper<Rc<Callback>>,
50    theme_watcher: ColorThemeWatcher,
51    handle: MUX::Window,
52    app_window: AppWindow,
53    canvas: MUXC::Canvas,
54    closing_token: i64,
55}
56
57impl Window {
58    pub fn new() -> Result<Self> {
59        let handle = MUX::Window::new()?;
60        ROOT_WINDOWS.with_borrow_mut(|map| map.push(handle.clone()));
61
62        let hwnd = unsafe { handle.cast::<IWindowNative>()?.WindowHandle()? };
63        let app_window = AppWindow::GetFromWindowId(WindowId { Value: hwnd.0 as _ })?;
64        let titlebar = app_window.TitleBar()?;
65        match titlebar.SetPreferredTheme(TitleBarTheme::UseDefaultAppMode) {
66            Ok(()) => {}
67            // Available since 1.7
68            Err(e) if e.code() == E_NOINTERFACE => unsafe {
69                window_use_dark_mode(hwnd.0)?;
70                // Set to DWMSBT_AUTO.
71                set_backdrop(hwnd.0, Backdrop::None)?;
72            },
73            Err(e) => return Err(e),
74        }
75
76        let canvas = MUXC::Canvas::new()?;
77        canvas.SetVerticalAlignment(MUX::VerticalAlignment::Stretch)?;
78        canvas.SetHorizontalAlignment(MUX::HorizontalAlignment::Stretch)?;
79
80        handle.SetContent(&canvas)?;
81
82        let on_close = SendWrapper::new(Rc::new(Callback::new()));
83        let closing_token = {
84            let on_close = on_close.clone();
85            app_window.Closing(&TypedEventHandler::new(
86                move |_, args: Ref<AppWindowClosingEventArgs>| {
87                    let args = args.ok()?;
88                    on_close.signal::<GlobalRuntime>(());
89                    // Prevent the window from closing
90                    args.SetCancel(true)?;
91                    Ok(())
92                },
93            ))?
94        };
95        let on_size = SendWrapper::new(Rc::new(Callback::new()));
96        let on_move = SendWrapper::new(Rc::new(Callback::new()));
97        {
98            let on_size = on_size.clone();
99            let on_move = on_move.clone();
100            app_window.Changed(&TypedEventHandler::new(
101                move |_, args: Ref<AppWindowChangedEventArgs>| {
102                    let args = args.ok()?;
103                    if args.DidPositionChange()? {
104                        on_move.signal::<GlobalRuntime>(());
105                    }
106                    if args.DidSizeChange()? {
107                        on_size.signal::<GlobalRuntime>(());
108                    }
109                    Ok(())
110                },
111            ))?;
112        }
113        {
114            let on_size = on_size.clone();
115            canvas.Loaded(&RoutedEventHandler::new(move |_, _| {
116                on_size.signal::<GlobalRuntime>(());
117                Ok(())
118            }))?;
119        }
120        let theme_watcher = ColorThemeWatcher::new()?;
121
122        Ok(Self {
123            on_size,
124            on_move,
125            on_close,
126            theme_watcher,
127            handle,
128            app_window,
129            canvas,
130            closing_token,
131        })
132    }
133
134    pub fn is_visible(&self) -> Result<bool> {
135        self.app_window.IsVisible()
136    }
137
138    pub fn set_visible(&self, v: bool) -> Result<()> {
139        if v {
140            self.app_window.Show()?;
141        } else {
142            self.app_window.Hide()?;
143        }
144        Ok(())
145    }
146
147    fn dpi(&self) -> u32 {
148        if let Ok(id) = self.app_window.Id() {
149            unsafe { GetDpiForWindow(id.Value as _) }
150        } else {
151            96
152        }
153    }
154
155    fn scale(&self) -> f64 {
156        self.dpi() as f64 / 96.0
157    }
158
159    pub fn loc(&self) -> Result<Point> {
160        Ok(Point::from_native(self.app_window.Position()?) / self.scale())
161    }
162
163    pub fn set_loc(&mut self, p: Point) -> Result<()> {
164        self.app_window.Move((p * self.scale()).to_native())?;
165        Ok(())
166    }
167
168    pub fn size(&self) -> Result<Size> {
169        Ok(Size::from_native(self.app_window.Size()?) / self.scale())
170    }
171
172    pub fn set_size(&mut self, s: Size) -> Result<()> {
173        self.app_window.Resize((s * self.scale()).to_native())?;
174        Ok(())
175    }
176
177    pub fn client_size(&self) -> Result<Size> {
178        let size = match self.app_window.ClientSize() {
179            Ok(s) => Size::from_native(s),
180            // Available since 1.1
181            Err(e) if e.code() == E_NOINTERFACE => {
182                let mut rect = MaybeUninit::uninit();
183                syscall!(
184                    BOOL,
185                    GetClientRect(self.app_window.Id()?.Value as _, rect.as_mut_ptr())
186                )?;
187                let rect = unsafe { rect.assume_init() };
188                Size::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _)
189            }
190            Err(e) => return Err(e),
191        };
192        Ok(size / self.scale())
193    }
194
195    pub fn text(&self) -> Result<String> {
196        Ok(self.handle.Title()?.to_string_lossy())
197    }
198
199    pub fn set_text(&mut self, text: impl AsRef<str>) -> Result<()> {
200        self.handle.SetTitle(&text.as_ref().into())?;
201        Ok(())
202    }
203
204    pub fn set_icon_by_id(&mut self, id: u16) -> Result<()> {
205        let icon = unsafe {
206            LoadImageW(
207                get_current_module_handle(),
208                id as _,
209                IMAGE_ICON,
210                0,
211                0,
212                LR_DEFAULTCOLOR | LR_DEFAULTSIZE | LR_SHARED,
213            )
214        };
215        if icon.is_null() {
216            return Err(Error::from_thread());
217        }
218        self.app_window
219            .SetIconWithIconId(IconId { Value: icon as _ })?;
220        Ok(())
221    }
222
223    pub fn backdrop(&self) -> Result<Backdrop> {
224        match self.handle.SystemBackdrop() {
225            Ok(brush) => {
226                if let Ok(brush) = brush.cast::<MicaBackdrop>() {
227                    match brush.Kind() {
228                        Ok(MicaKind::Base) => Ok(Backdrop::Mica),
229                        Ok(MicaKind::BaseAlt) => Ok(Backdrop::MicaAlt),
230                        _ => Ok(Backdrop::None),
231                    }
232                } else {
233                    Ok(Backdrop::Acrylic)
234                }
235            }
236            Err(e) if e.code().0 == 0 => Ok(Backdrop::None),
237            Err(e) => Err(e),
238        }
239    }
240
241    pub fn set_backdrop(&mut self, backdrop: Backdrop) -> Result<()> {
242        match backdrop {
243            Backdrop::Acrylic => {
244                let brush = acrylic_backdrop()?;
245                self.handle.SetSystemBackdrop(&brush)?;
246            }
247            Backdrop::Mica => {
248                let brush = mica_backdrop()?;
249                self.handle.SetSystemBackdrop(&brush)?;
250            }
251            Backdrop::MicaAlt => {
252                let brush = mica_alt_backdrop()?;
253                self.handle.SetSystemBackdrop(&brush)?;
254            }
255            _ => {
256                self.handle.SetSystemBackdrop(None)?;
257            }
258        }
259        unsafe {
260            let hwnd = self.app_window.Id()?.Value as _;
261            set_backdrop(hwnd, backdrop)?;
262        }
263        Ok(())
264    }
265
266    pub async fn wait_size(&self) {
267        self.on_size.wait().await
268    }
269
270    pub async fn wait_move(&self) {
271        self.on_move.wait().await
272    }
273
274    pub async fn wait_close(&self) {
275        self.on_close.wait().await
276    }
277
278    pub async fn wait_theme_changed(&self) {
279        self.theme_watcher.wait().await
280    }
281}
282
283impl AsWindow for Window {
284    fn as_window(&self) -> BorrowedWindow<'_> {
285        BorrowedWindow::winui(&self.handle)
286    }
287}
288
289impl AsContainer for Window {
290    fn as_container(&self) -> BorrowedContainer<'_> {
291        BorrowedContainer::winui(&self.canvas)
292    }
293}
294
295impl Drop for Window {
296    fn drop(&mut self) {
297        ROOT_WINDOWS.with_borrow_mut(|map| {
298            map.retain(|w| w != &self.handle);
299        });
300        self.app_window.RemoveClosing(self.closing_token).ok();
301        self.handle.Close().ok();
302    }
303}
304
305thread_local! {
306    pub(crate) static ROOT_WINDOWS: RefCell<Vec<MUX::Window>> = const { RefCell::new(vec![]) };
307}
308
309pub(crate) fn get_root_window(e: &MUX::FrameworkElement) -> Option<MUX::Window> {
310    let e_root = e.XamlRoot().ok()?;
311    ROOT_WINDOWS.with_borrow(|windows| {
312        for w in windows {
313            if let Ok(c) = w.Content()
314                && let Ok(r) = c.XamlRoot()
315                && r == e_root
316            {
317                return Some(w.clone());
318            }
319        }
320        None
321    })
322}
323
324#[derive(Debug)]
325struct ColorThemeWatcher {
326    settings: UISettings,
327    notify: Arc<SyncCallback>,
328    token: i64,
329}
330
331impl ColorThemeWatcher {
332    pub fn new() -> Result<Self> {
333        let settings = UISettings::new()?;
334        let notify = Arc::new(SyncCallback::new());
335        let token = {
336            let notify = notify.clone();
337            settings.ColorValuesChanged(&TypedEventHandler::new(move |_, _| {
338                notify.signal(());
339                Ok(())
340            }))?
341        };
342        Ok(Self {
343            settings,
344            notify,
345            token,
346        })
347    }
348
349    pub async fn wait(&self) {
350        self.notify.wait().await
351    }
352}
353
354impl Drop for ColorThemeWatcher {
355    fn drop(&mut self) {
356        match self.settings.RemoveColorValuesChanged(self.token) {
357            Ok(()) => {}
358            Err(_e) => {
359                error!("RemoveColorValuesChanged: {_e:?}");
360            }
361        }
362    }
363}
364
365fn acrylic_backdrop() -> Result<SystemBackdrop> {
366    CustomDesktopAcrylicBackdrop::compose()
367}
368
369fn mica_backdrop() -> Result<MicaBackdrop> {
370    let brush = MicaBackdrop::new()?;
371    brush.SetKind(MicaKind::Base)?;
372    Ok(brush)
373}
374
375fn mica_alt_backdrop() -> Result<MicaBackdrop> {
376    let brush = MicaBackdrop::new()?;
377    brush.SetKind(MicaKind::BaseAlt)?;
378    Ok(brush)
379}
380
381#[derive(Debug)]
382pub struct View {
383    handle: Widget,
384    canvas: MUXC::Canvas,
385}
386
387#[inherit_methods(from = "self.handle")]
388impl View {
389    pub fn new(parent: impl AsContainer) -> Result<Self> {
390        let canvas = MUXC::Canvas::new()?;
391        Ok(Self {
392            handle: Widget::new(parent, canvas.cast()?)?,
393            canvas,
394        })
395    }
396
397    pub fn is_visible(&self) -> Result<bool>;
398
399    pub fn set_visible(&mut self, v: bool) -> Result<()>;
400
401    pub fn is_enabled(&self) -> Result<bool>;
402
403    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
404
405    pub fn loc(&self) -> Result<Point>;
406
407    pub fn set_loc(&mut self, p: Point) -> Result<()>;
408
409    pub fn size(&self) -> Result<Size>;
410
411    pub fn set_size(&mut self, v: Size) -> Result<()>;
412}
413
414winio_handle::impl_as_widget!(View, handle);
415
416impl AsContainer for View {
417    fn as_container(&self) -> BorrowedContainer<'_> {
418        BorrowedContainer::winui(&self.canvas)
419    }
420}