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 Err(e) if e.code() == E_NOINTERFACE => unsafe {
69 window_use_dark_mode(hwnd.0)?;
70 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 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 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}