Backed out changeset f8c294d1c24a (bug 1888664) for causing crashreporter windows...
[gecko.git] / toolkit / crashreporter / client / app / src / ui / windows / mod.rs
blob2ff4b3c12670e24be27a017a433d69ef048d0f14
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 //! A UI using the windows API.
6 //!
7 //! This UI contains some edge cases that aren't implemented, for instance:
8 //! * there are a few cases where specific hierarchies are handled differently (e.g. a Button
9 //!   containing Label, Scroll behavior, etc).
10 //! * not all controls handle all Property variants (e.g. Checkbox doesn't handle ReadOnly, TextBox
11 //!   doesn't handle Binding, etc).
12 //!
13 //! The error handling is also a _little_ fast-and-loose, as many functions return an error value
14 //! that is acceptable to following logic (though it still would be a good idea to improve this).
15 //!
16 //! The rendering treats VBox, HBox, and Scroll as strictly layout-only: they do not create any
17 //! associated windows, and the layout logic handles their behavior.
19 // Our windows-targets doesn't link uxtheme correctly for GetThemeSysFont/GetThemeSysColor.
20 // This was working in windows-sys 0.48.
21 #[link(name = "uxtheme", kind = "static")]
22 extern "C" {}
24 use super::model::{self, Application, Element, ElementStyle, TypedElement};
25 use crate::data::Property;
26 use font::Font;
27 use quit_token::QuitToken;
28 use std::cell::RefCell;
29 use std::collections::HashMap;
30 use std::pin::Pin;
31 use std::rc::Rc;
32 use widestring::WideString;
33 use window::{CustomWindowClass, Window, WindowBuilder};
34 use windows_sys::Win32::{
35     Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, RECT, WPARAM},
36     Graphics::Gdi,
37     System::{LibraryLoader::GetModuleHandleW, SystemServices, Threading::GetCurrentThreadId},
38     UI::{Controls, Input::KeyboardAndMouse, Shell, WindowsAndMessaging as win},
41 macro_rules! success {
42     ( nonzero $e:expr ) => {{
43         let value = $e;
44         assert_ne!(value, 0);
45         value
46     }};
47     ( lasterror $e:expr ) => {{
48         unsafe { windows_sys::Win32::Foundation::SetLastError(0) };
49         let value = $e;
50         assert!(value != 0 || windows_sys::Win32::Foundation::GetLastError() == 0);
51         value
52     }};
53     ( hresult $e:expr ) => {
54         assert_eq!($e, windows_sys::Win32::Foundation::S_OK);
55     };
56     ( pointer $e:expr ) => {{
57         let ptr = $e;
58         assert_ne!(ptr, 0);
59         ptr
60     }};
63 mod font;
64 mod gdi;
65 mod layout;
66 mod quit_token;
67 mod twoway;
68 mod widestring;
69 mod window;
71 /// A Windows API UI implementation.
72 pub struct UI {
73     thread_id: u32,
76 /// Custom user messages.
77 #[repr(u32)]
78 enum UserMessage {
79     Invoke = win::WM_USER,
82 fn get_invoke(msg: &win::MSG) -> Option<Box<model::InvokeFn>> {
83     if msg.message == UserMessage::Invoke as u32 {
84         Some(unsafe { Box::from_raw(msg.lParam as *mut model::InvokeFn) })
85     } else {
86         None
87     }
90 impl UI {
91     pub fn run_loop(&self, app: Application) {
92         // Initialize common controls.
93         {
94             let icc = Controls::INITCOMMONCONTROLSEX {
95                 dwSize: std::mem::size_of::<Controls::INITCOMMONCONTROLSEX>() as _,
96                 // Buttons, edit controls, and static controls are all included in 'standard'.
97                 dwICC: Controls::ICC_STANDARD_CLASSES | Controls::ICC_PROGRESS_CLASS,
98             };
99             success!(nonzero unsafe { Controls::InitCommonControlsEx(&icc) });
100         }
102         // Enable font smoothing (per
103         // https://learn.microsoft.com/en-us/windows/win32/gdi/cleartype-antialiasing ).
104         unsafe {
105             // We don't check for failure on these, they are best-effort.
106             win::SystemParametersInfoW(
107                 win::SPI_SETFONTSMOOTHING,
108                 1,
109                 std::ptr::null_mut(),
110                 win::SPIF_UPDATEINIFILE | win::SPIF_SENDCHANGE,
111             );
112             win::SystemParametersInfoW(
113                 win::SPI_SETFONTSMOOTHINGTYPE,
114                 0,
115                 win::FE_FONTSMOOTHINGCLEARTYPE as _,
116                 win::SPIF_UPDATEINIFILE | win::SPIF_SENDCHANGE,
117             );
118         }
120         // Enable correct layout direction.
121         if unsafe { win::SetProcessDefaultLayout(if app.rtl { Gdi::LAYOUT_RTL } else { 0 }) } == 0 {
122             log::warn!("failed to set process layout direction");
123         }
125         let module: HINSTANCE = unsafe { GetModuleHandleW(std::ptr::null()) };
127         // Register custom classes.
128         AppWindow::register(module).expect("failed to register AppWindow window class");
130         {
131             // The quit token is cloned for each top-level window and dropped at the end of this
132             // scope.
133             let quit_token = QuitToken::new();
135             for window in app.windows {
136                 let name = WideString::new(window.element_type.title.as_str());
137                 let w = top_level_window(
138                     module,
139                     AppWindow::new(
140                         WindowRenderer::new(module, window.element_type),
141                         Some(quit_token.clone()),
142                     ),
143                     &name,
144                     &window.style,
145                 );
147                 unsafe { win::ShowWindow(w.handle, win::SW_NORMAL) };
148                 unsafe { Gdi::UpdateWindow(w.handle) };
149             }
150         }
152         // Run the event loop.
153         let mut msg = unsafe { std::mem::zeroed::<win::MSG>() };
154         while unsafe { win::GetMessageW(&mut msg, 0, 0, 0) } > 0 {
155             if let Some(f) = get_invoke(&msg) {
156                 f();
157                 continue;
158             }
160             unsafe {
161                 // IsDialogMessageW is necessary to handle niceties like tab navigation
162                 if win::IsDialogMessageW(win::GetAncestor(msg.hwnd, win::GA_ROOT), &mut msg) == 0 {
163                     win::TranslateMessage(&msg);
164                     win::DispatchMessageW(&msg);
165                 }
166             }
167         }
169         // Flush queue to properly drop late invokes (this is a very unlikely case)
170         while unsafe { win::PeekMessageW(&mut msg, 0, 0, 0, win::PM_REMOVE) } > 0 {
171             if let Some(f) = get_invoke(&msg) {
172                 drop(f);
173             }
174         }
175     }
177     pub fn invoke(&self, f: model::InvokeFn) {
178         let ptr: *mut model::InvokeFn = Box::into_raw(Box::new(f));
179         if unsafe {
180             win::PostThreadMessageW(self.thread_id, UserMessage::Invoke as u32, 0, ptr as _)
181         } == 0
182         {
183             let _ = unsafe { Box::from_raw(ptr) };
184             log::warn!("failed to invoke function on thread message queue");
185         }
186     }
189 impl Default for UI {
190     fn default() -> Self {
191         UI {
192             thread_id: unsafe { GetCurrentThreadId() },
193         }
194     }
197 /// A reference to an Element.
198 #[derive(PartialEq, Eq, Hash, Clone, Copy)]
199 struct ElementRef(*const Element);
201 impl ElementRef {
202     pub fn new(element: &Element) -> Self {
203         ElementRef(element as *const Element)
204     }
206     /// # Safety
207     /// You must ensure the reference is still valid.
208     pub unsafe fn get(&self) -> &Element {
209         &*self.0
210     }
213 // Equivalent of win32 HIWORD macro
214 fn hiword(v: u32) -> u16 {
215     (v >> 16) as u16
218 // Equivalent of win32 LOWORD macro
219 fn loword(v: u32) -> u16 {
220     v as u16
223 // Equivalent of win32 MAKELONG macro
224 fn makelong(low: u16, high: u16) -> u32 {
225     (high as u32) << 16 | low as u32
228 fn top_level_window<W: window::WindowClass + window::WindowData>(
229     module: HINSTANCE,
230     class: W,
231     title: &WideString,
232     style: &ElementStyle,
233 ) -> Window<W> {
234     class
235         .builder(module)
236         .name(title)
237         .style(win::WS_OVERLAPPEDWINDOW)
238         .pos(win::CW_USEDEFAULT, win::CW_USEDEFAULT)
239         .size(
240             style
241                 .horizontal_size_request
242                 .and_then(|i| i.try_into().ok())
243                 .unwrap_or(win::CW_USEDEFAULT),
244             style
245                 .vertical_size_request
246                 .and_then(|i| i.try_into().ok())
247                 .unwrap_or(win::CW_USEDEFAULT),
248         )
249         .create()
252 window::basic_window_classes! {
253     /// Static control (text, image, etc) class.
254     struct Static => "STATIC";
256     /// Button control class.
257     struct Button => "BUTTON";
259     /// Edit control class.
260     struct Edit => "EDIT";
262     /// Progress control class.
263     struct Progress => "msctls_progress32";
266 /// A top-level application window.
268 /// This is used for the main window and modal windows.
269 struct AppWindow {
270     renderer: WindowRenderer,
271     _quit_token: Option<QuitToken>,
274 impl AppWindow {
275     pub fn new(renderer: WindowRenderer, quit_token: Option<QuitToken>) -> Self {
276         AppWindow {
277             renderer,
278             _quit_token: quit_token,
279         }
280     }
283 impl window::WindowClass for AppWindow {
284     fn class_name() -> WideString {
285         WideString::new("App Window")
286     }
289 impl CustomWindowClass for AppWindow {
290     fn message(
291         data: &RefCell<Self>,
292         hwnd: HWND,
293         umsg: u32,
294         wparam: WPARAM,
295         lparam: LPARAM,
296     ) -> Option<LRESULT> {
297         let me = data.borrow();
298         let model = me.renderer.model();
299         match umsg {
300             win::WM_CREATE => {
301                 if let Some(close) = &model.close {
302                     close.subscribe(move |&()| unsafe {
303                         win::SendMessageW(hwnd, win::WM_CLOSE, 0, 0);
304                     });
305                 }
307                 let mut renderer = me.renderer.child_renderer(hwnd);
308                 if let Some(child) = &model.content {
309                     renderer.render_child(child);
310                 }
312                 drop(model);
313                 let children = std::mem::take(&mut me.renderer.model_mut().children);
314                 for child in children {
315                     renderer.render_window(child);
316                 }
317             }
318             win::WM_CLOSE => {
319                 if model.modal {
320                     // Modal windows should hide themselves rather than closing/destroying.
321                     unsafe { win::ShowWindow(hwnd, win::SW_HIDE) };
322                     return Some(0);
323                 }
324             }
325             win::WM_SHOWWINDOW => {
326                 if model.modal {
327                     // Modal windows should disable/enable their parent as they are shown/hid,
328                     // respectively.
329                     let shown = wparam != 0;
330                     unsafe {
331                         KeyboardAndMouse::EnableWindow(
332                             win::GetWindow(hwnd, win::GW_OWNER),
333                             (!shown).into(),
334                         )
335                     };
336                     return Some(0);
337                 }
338             }
339             win::WM_SIZE => {
340                 // When resized, recompute the layout.
341                 let width = loword(lparam as _) as u32;
342                 let height = hiword(lparam as _) as u32;
344                 if let Some(child) = &model.content {
345                     me.renderer.layout(child, width, height);
346                     unsafe { Gdi::UpdateWindow(hwnd) };
347                 }
348                 return Some(0);
349             }
350             win::WM_GETFONT => return Some(**me.renderer.font() as _),
351             win::WM_COMMAND => {
352                 let child = lparam as HWND;
353                 let windows = me.renderer.windows.borrow();
354                 if let Some(&element) = windows.reverse().get(&child) {
355                     // # Safety
356                     // The ElementRefs all pertain to the model stored in the renderer.
357                     let element = unsafe { element.get() };
358                     // Handle button presses.
359                     use model::ElementType::*;
360                     match &element.element_type {
361                         Button(model::Button { click, .. }) => {
362                             let code = hiword(wparam as _) as u32;
363                             if code == win::BN_CLICKED {
364                                 click.fire(&());
365                                 return Some(0);
366                             }
367                         }
368                         Checkbox(model::Checkbox { checked, .. }) => {
369                             let code = hiword(wparam as _) as u32;
370                             if code == win::BN_CLICKED {
371                                 let check_state =
372                                     unsafe { win::SendMessageW(child, win::BM_GETCHECK, 0, 0) };
373                                 if let Property::Binding(s) = checked {
374                                     *s.borrow_mut() = check_state == Controls::BST_CHECKED as isize;
375                                 }
376                                 return Some(0);
377                             }
378                         }
379                         _ => (),
380                     }
381                 }
382             }
383             _ => (),
384         }
385         None
386     }
389 /// State used while creating and updating windows.
390 struct WindowRenderer {
391     // We wrap with an Rc to get weak references in property callbacks (like that of
392     // `ElementStyle::visible`).
393     inner: Rc<WindowRendererInner>,
396 impl std::ops::Deref for WindowRenderer {
397     type Target = WindowRendererInner;
399     fn deref(&self) -> &Self::Target {
400         &self.inner
401     }
404 struct WindowRendererInner {
405     pub module: HINSTANCE,
406     /// The model is pinned and boxed to ensure that references in `windows` remain valid.
407     ///
408     /// We need to keep the model around so we can correctly perform layout as the window size
409     /// changes. Unfortunately the win32 API doesn't have any nice ways to automatically perform
410     /// layout.
411     pub model: RefCell<Pin<Box<model::Window>>>,
412     /// Mapping between model elements and windows.
413     ///
414     /// Element references pertain to elements in `model`.
415     pub windows: RefCell<twoway::TwoWay<ElementRef, HWND>>,
416     pub font: Font,
417     pub bold_font: Font,
420 impl WindowRenderer {
421     pub fn new(module: HINSTANCE, model: model::Window) -> Self {
422         WindowRenderer {
423             inner: Rc::new(WindowRendererInner {
424                 module,
425                 model: RefCell::new(Box::pin(model)),
426                 windows: Default::default(),
427                 font: Font::caption(),
428                 bold_font: Font::caption_bold().unwrap_or_else(Font::caption),
429             }),
430         }
431     }
433     pub fn child_renderer(&self, window: HWND) -> WindowChildRenderer {
434         WindowChildRenderer {
435             renderer: &self.inner,
436             window,
437             child_id: 0,
438             scroll: false,
439         }
440     }
442     pub fn layout(&self, element: &Element, max_width: u32, max_height: u32) {
443         layout::Layout::new(self.inner.windows.borrow().forward())
444             .layout(element, max_width, max_height);
445     }
447     pub fn model(&self) -> std::cell::Ref<'_, model::Window> {
448         std::cell::Ref::map(self.inner.model.borrow(), |b| &**b)
449     }
451     pub fn model_mut(&self) -> std::cell::RefMut<'_, model::Window> {
452         std::cell::RefMut::map(self.inner.model.borrow_mut(), |b| &mut **b)
453     }
455     pub fn font(&self) -> &Font {
456         &self.inner.font
457     }
460 struct WindowChildRenderer<'a> {
461     renderer: &'a Rc<WindowRendererInner>,
462     window: HWND,
463     child_id: i32,
464     scroll: bool,
467 impl<'a> WindowChildRenderer<'a> {
468     fn add_child<W: window::WindowClass>(&mut self, class: W) -> WindowBuilder<W> {
469         let builder = class
470             .builder(self.renderer.module)
471             .style(win::WS_CHILD | win::WS_VISIBLE)
472             .parent(self.window)
473             .child_id(self.child_id);
474         self.child_id += 1;
475         builder
476     }
478     fn add_window<W: window::WindowClass>(&mut self, class: W) -> WindowBuilder<W> {
479         class
480             .builder(self.renderer.module)
481             .style(win::WS_OVERLAPPEDWINDOW)
482             .pos(win::CW_USEDEFAULT, win::CW_USEDEFAULT)
483             .parent(self.window)
484     }
486     fn render_window(&mut self, model: TypedElement<model::Window>) -> Window {
487         let name = WideString::new(model.element_type.title.as_str());
488         let style = model.style;
489         let w = self
490             .add_window(AppWindow::new(
491                 WindowRenderer::new(self.renderer.module, model.element_type),
492                 None,
493             ))
494             .size(
495                 style
496                     .horizontal_size_request
497                     .and_then(|i| i.try_into().ok())
498                     .unwrap_or(win::CW_USEDEFAULT),
499                 style
500                     .vertical_size_request
501                     .and_then(|i| i.try_into().ok())
502                     .unwrap_or(win::CW_USEDEFAULT),
503             )
504             .name(&name)
505             .create();
507         enabled_property(&style.enabled, w.handle);
509         let hwnd = w.handle;
510         let set_visible = move |visible| unsafe {
511             win::ShowWindow(hwnd, if visible { win::SW_SHOW } else { win::SW_HIDE });
512         };
514         match &style.visible {
515             Property::Static(false) => set_visible(false),
516             Property::Binding(s) => {
517                 s.on_change(move |v| set_visible(*v));
518                 if !*s.borrow() {
519                     set_visible(false);
520                 }
521             }
522             _ => (),
523         }
525         w.generic()
526     }
528     fn render_child(&mut self, element: &Element) {
529         if let Some(mut window) = self.render_element_type(&element.element_type) {
530             window.set_default_font(&self.renderer.font);
532             // Store the element to handle mapping.
533             self.renderer
534                 .windows
535                 .borrow_mut()
536                 .insert(ElementRef::new(element), window.handle);
538             enabled_property(&element.style.enabled, window.handle);
539         }
541         // Handle visibility properties.
542         match &element.style.visible {
543             Property::Static(false) => {
544                 set_visibility(element, false, self.renderer.windows.borrow().forward())
545             }
546             Property::Binding(s) => {
547                 let weak_renderer = Rc::downgrade(self.renderer);
548                 let element_ref = ElementRef::new(element);
549                 let parent = self.window;
550                 s.on_change(move |visible| {
551                     let Some(renderer) = weak_renderer.upgrade() else {
552                         return;
553                     };
554                     // # Safety
555                     // ElementRefs are valid as long as the renderer is (and we have a strong
556                     // reference to it).
557                     let element = unsafe { element_ref.get() };
558                     set_visibility(element, *visible, renderer.windows.borrow().forward());
559                     // Send WM_SIZE so that the parent recomputes the layout.
560                     unsafe {
561                         let mut rect = std::mem::zeroed::<RECT>();
562                         win::GetClientRect(parent, &mut rect);
563                         win::SendMessageW(
564                             parent,
565                             win::WM_SIZE,
566                             0,
567                             makelong(
568                                 (rect.right - rect.left) as u16,
569                                 (rect.bottom - rect.top) as u16,
570                             ) as isize,
571                         );
572                     }
573                 });
574                 if !*s.borrow() {
575                     set_visibility(element, false, self.renderer.windows.borrow().forward());
576                 }
577             }
578             _ => (),
579         }
580     }
582     fn render_element_type(&mut self, element_type: &model::ElementType) -> Option<Window> {
583         use model::ElementType as ET;
584         match element_type {
585             ET::Label(model::Label { text, bold }) => {
586                 let mut window = match text {
587                     Property::Static(text) => {
588                         let text = WideString::new(text.as_str());
589                         self.add_child(Static)
590                             .name(&text)
591                             .add_style(SystemServices::SS_LEFT | SystemServices::SS_NOPREFIX)
592                             .create()
593                     }
594                     Property::Binding(b) => {
595                         let text = WideString::new(b.borrow().as_str());
596                         let window = self
597                             .add_child(Static)
598                             .name(&text)
599                             .add_style(SystemServices::SS_LEFT | SystemServices::SS_NOPREFIX)
600                             .create();
601                         let handle = window.handle;
602                         b.on_change(move |text| {
603                             let text = WideString::new(text.as_str());
604                             unsafe { win::SetWindowTextW(handle, text.pcwstr()) };
605                         });
606                         window
607                     }
608                     Property::ReadOnly(_) => {
609                         unimplemented!("ReadOnly property not supported for Label::text")
610                     }
611                 };
612                 if *bold {
613                     window.set_font(&self.renderer.bold_font);
614                 }
615                 Some(window.generic())
616             }
617             ET::TextBox(model::TextBox {
618                 placeholder,
619                 content,
620                 editable,
621             }) => {
622                 let scroll = self.scroll;
623                 let window = self
624                     .add_child(Edit)
625                     .add_style(
626                         (win::ES_LEFT
627                             | win::ES_MULTILINE
628                             | win::ES_WANTRETURN
629                             | if *editable { 0 } else { win::ES_READONLY })
630                             as u32
631                             | win::WS_BORDER
632                             | win::WS_TABSTOP
633                             | if scroll { win::WS_VSCROLL } else { 0 },
634                     )
635                     .create();
637                 fn to_control_text(s: &str) -> String {
638                     s.replace("\n", "\r\n")
639                 }
641                 fn from_control_text(s: &str) -> String {
642                     s.replace("\r\n", "\n")
643                 }
645                 struct SubClassData {
646                     placeholder: Option<WideString>,
647                 }
649                 // EM_SETCUEBANNER doesn't work with multiline edit controls (for no particular
650                 // reason?), so we have to draw it ourselves.
651                 unsafe extern "system" fn subclass_proc(
652                     hwnd: HWND,
653                     msg: u32,
654                     wparam: WPARAM,
655                     lparam: LPARAM,
656                     _uidsubclass: usize,
657                     dw_ref_data: usize,
658                 ) -> LRESULT {
659                     let ret = Shell::DefSubclassProc(hwnd, msg, wparam, lparam);
660                     if msg == win::WM_PAINT
661                         && KeyboardAndMouse::GetFocus() != hwnd
662                         && win::GetWindowTextLengthW(hwnd) == 0
663                     {
664                         let data = (dw_ref_data as *const SubClassData).as_ref().unwrap();
665                         if let Some(placeholder) = &data.placeholder {
666                             let mut rect = std::mem::zeroed::<RECT>();
667                             win::GetClientRect(hwnd, &mut rect);
668                             Gdi::InflateRect(&mut rect, -2, -2);
670                             let dc = gdi::DC::new(hwnd).expect("failed to create GDI DC");
671                             dc.with_object_selected(
672                                 win::SendMessageW(hwnd, win::WM_GETFONT, 0, 0) as _,
673                                 |hdc| {
674                                     Gdi::SetTextColor(
675                                         hdc,
676                                         Controls::GetThemeSysColor(0, Gdi::COLOR_GRAYTEXT),
677                                     );
678                                     success!(nonzero Gdi::DrawTextW(
679                                         hdc,
680                                         placeholder.pcwstr(),
681                                         -1,
682                                         &mut rect,
683                                         Gdi::DT_LEFT | Gdi::DT_TOP | Gdi::DT_WORDBREAK,
684                                     ));
685                                 },
686                             )
687                             .expect("failed to select font gdi object");
688                         }
689                     }
690                     if msg == win::WM_DESTROY {
691                         drop(unsafe { Box::from_raw(dw_ref_data as *mut SubClassData) });
692                     }
693                     return ret;
694                 }
696                 let subclassdata = Box::into_raw(Box::new(SubClassData {
697                     placeholder: placeholder
698                         .as_ref()
699                         .map(|s| WideString::new(to_control_text(s))),
700                 }));
702                 unsafe {
703                     Shell::SetWindowSubclass(
704                         window.handle,
705                         Some(subclass_proc),
706                         0,
707                         subclassdata as _,
708                     );
709                 }
711                 // Set up content property.
712                 match content {
713                     Property::ReadOnly(od) => {
714                         let handle = window.handle;
715                         od.register(move |target| {
716                             // GetWindowText requires the buffer be large enough for the terminating
717                             // null character (otherwise it truncates the string), but
718                             // GetWindowTextLength returns the length without the null character, so we
719                             // add 1.
720                             let length = unsafe { win::GetWindowTextLengthW(handle) } + 1;
721                             let mut buf = vec![0u16; length as usize];
722                             unsafe { win::GetWindowTextW(handle, buf.as_mut_ptr(), length) };
723                             buf.pop(); // null character; `String` doesn't want that
724                             *target = from_control_text(&String::from_utf16_lossy(&buf));
725                         });
726                     }
727                     Property::Static(s) => {
728                         let text = WideString::new(to_control_text(s));
729                         unsafe { win::SetWindowTextW(window.handle, text.pcwstr()) };
730                     }
731                     Property::Binding(b) => {
732                         let handle = window.handle;
733                         b.on_change(move |text| {
734                             let text = WideString::new(to_control_text(text.as_str()));
735                             unsafe { win::SetWindowTextW(handle, text.pcwstr()) };
736                         });
737                         let text = WideString::new(to_control_text(b.borrow().as_str()));
738                         unsafe { win::SetWindowTextW(window.handle, text.pcwstr()) };
739                     }
740                 }
741                 Some(window.generic())
742             }
743             ET::Scroll(model::Scroll { content }) => {
744                 if let Some(content) = content {
745                     // Scrolling is implemented in a cooperative, non-universal way right now.
746                     self.scroll = true;
747                     self.render_child(content);
748                     self.scroll = false;
749                 }
750                 None
751             }
752             ET::Button(model::Button { content, .. }) => {
753                 if let Some(ET::Label(model::Label {
754                     text: Property::Static(text),
755                     ..
756                 })) = content.as_ref().map(|e| &e.element_type)
757                 {
758                     let text = WideString::new(text);
760                     let window = self
761                         .add_child(Button)
762                         .add_style(win::BS_PUSHBUTTON as u32 | win::WS_TABSTOP)
763                         .name(&text)
764                         .create();
765                     Some(window.generic())
766                 } else {
767                     None
768                 }
769             }
770             ET::Checkbox(model::Checkbox { checked, label }) => {
771                 let label = label.as_ref().map(WideString::new);
772                 let mut builder = self
773                     .add_child(Button)
774                     .add_style((win::BS_AUTOCHECKBOX | win::BS_MULTILINE) as u32 | win::WS_TABSTOP);
775                 if let Some(label) = &label {
776                     builder = builder.name(label);
777                 }
778                 let window = builder.create();
780                 fn set_check(handle: HWND, value: bool) {
781                     unsafe {
782                         win::SendMessageW(
783                             handle,
784                             win::BM_SETCHECK,
785                             if value {
786                                 Controls::BST_CHECKED
787                             } else {
788                                 Controls::BST_UNCHECKED
789                             } as usize,
790                             0,
791                         );
792                     }
793                 }
795                 match checked {
796                     Property::Static(checked) => set_check(window.handle, *checked),
797                     Property::Binding(s) => {
798                         let handle = window.handle;
799                         s.on_change(move |v| {
800                             set_check(handle, *v);
801                         });
802                         set_check(window.handle, *s.borrow());
803                     }
804                     _ => unimplemented!("ReadOnly properties not supported for Checkbox"),
805                 }
807                 Some(window.generic())
808             }
809             ET::Progress(model::Progress { amount }) => {
810                 let window = self
811                     .add_child(Progress)
812                     .add_style(Controls::PBS_MARQUEE)
813                     .create();
815                 fn set_amount(handle: HWND, value: Option<f32>) {
816                     match value {
817                         None => unsafe {
818                             win::SendMessageW(handle, Controls::PBM_SETMARQUEE, 1, 0);
819                         },
820                         Some(v) => unsafe {
821                             win::SendMessageW(handle, Controls::PBM_SETMARQUEE, 0, 0);
822                             win::SendMessageW(
823                                 handle,
824                                 Controls::PBM_SETPOS,
825                                 (v.clamp(0f32, 1f32) * 100f32) as usize,
826                                 0,
827                             );
828                         },
829                     }
830                 }
832                 match amount {
833                     Property::Static(v) => set_amount(window.handle, *v),
834                     Property::Binding(s) => {
835                         let handle = window.handle;
836                         s.on_change(move |v| set_amount(handle, *v));
837                         set_amount(window.handle, *s.borrow());
838                     }
839                     _ => unimplemented!("ReadOnly properties not supported for Progress"),
840                 }
842                 Some(window.generic())
843             }
844             // VBox/HBox are virtual, their behaviors are implemented entirely in the renderer layout.
845             // No need for additional windows.
846             ET::VBox(model::VBox { items, .. }) => {
847                 for item in items {
848                     self.render_child(item);
849                 }
850                 None
851             }
852             ET::HBox(model::HBox { items, .. }) => {
853                 for item in items {
854                     self.render_child(item);
855                 }
856                 None
857             }
858         }
859     }
862 /// Handle the enabled property.
864 /// This function assumes the default state of the window is enabled.
865 fn enabled_property(enabled: &Property<bool>, window: HWND) {
866     match enabled {
867         Property::Static(false) => unsafe {
868             KeyboardAndMouse::EnableWindow(window, false.into());
869         },
870         Property::Binding(s) => {
871             let handle = window;
872             s.on_change(move |enabled| {
873                 unsafe { KeyboardAndMouse::EnableWindow(handle, (*enabled).into()) };
874             });
875             if !*s.borrow() {
876                 unsafe { KeyboardAndMouse::EnableWindow(window, false.into()) };
877             }
878         }
879         _ => (),
880     }
883 /// Set the visibility of the given element. This recurses down the element tree and hides children
884 /// as necessary.
885 fn set_visibility(element: &Element, visible: bool, windows: &HashMap<ElementRef, HWND>) {
886     if let Some(&hwnd) = windows.get(&ElementRef::new(element)) {
887         unsafe {
888             win::ShowWindow(hwnd, if visible { win::SW_SHOW } else { win::SW_HIDE });
889         }
890     } else {
891         match &element.element_type {
892             model::ElementType::VBox(model::VBox { items, .. }) => {
893                 for item in items {
894                     set_visibility(item, visible, windows);
895                 }
896             }
897             model::ElementType::HBox(model::HBox { items, .. }) => {
898                 for item in items {
899                     set_visibility(item, visible, windows);
900                 }
901             }
902             model::ElementType::Scroll(model::Scroll {
903                 content: Some(content),
904             }) => {
905                 set_visibility(&*content, visible, windows);
906             }
907             _ => (),
908         }
909     }