1use crate::window_modifiers::WindowModifiers;
2use glutin::context::GlProfile;
3use vizia_core::context::TreeProps;
4use vizia_window::{AnchorTarget, WindowDescription};
5#[cfg(target_os = "windows")]
6use winit::platform::windows::WindowExtWindows;
7#[cfg(target_os = "windows")]
8use winit::{platform::windows::WindowAttributesExtWindows, raw_window_handle::RawWindowHandle};
9
10use crate::convert::cursor_icon_to_cursor_icon;
11use hashbrown::HashMap;
12use std::error::Error;
13use std::num::NonZeroU32;
14use std::{ffi::CString, sync::Arc};
15use winit::raw_window_handle::HasWindowHandle;
16
17use gl_rs as gl;
18use glutin::config::Config;
19use glutin_winit::DisplayBuilder;
20
21use gl::types::*;
22
23use glutin::{
24 config::ConfigTemplateBuilder,
25 context::{ContextApi, ContextAttributesBuilder},
26 display::GetGlDisplay,
27 prelude::*,
28 surface::{SurfaceAttributesBuilder, WindowSurface},
29};
30
31use skia_safe::{
32 ColorSpace, ColorType, PixelGeometry, Surface, SurfaceProps, SurfacePropsFlags,
33 gpu::{
34 self, ContextOptions, SurfaceOrigin, backend_render_targets, ganesh::context_options,
35 gl::FramebufferInfo,
36 },
37};
38
39use vizia_core::prelude::*;
40use winit::event_loop::ActiveEventLoop;
41use winit::window::{CursorGrabMode, CursorIcon, CustomCursor, WindowAttributes, WindowLevel};
42use winit::{dpi::*, window::WindowId};
43
44pub struct WinState {
45 pub entity: Entity,
46 pub id: WindowId,
47 pub surface: skia_safe::Surface,
48 pub dirty_surface: skia_safe::Surface,
49 pub gr_context: skia_safe::gpu::DirectContext,
50 pub gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
51 gl_context: glutin::context::PossiblyCurrentContext,
52 gl_config: Config,
53 pub window: Arc<winit::window::Window>,
54 pub should_close: bool,
55 #[cfg(target_os = "windows")]
56 pub is_initially_cloaked: bool,
57}
58
59impl Drop for WinState {
60 fn drop(&mut self) {
61 let _ = self.gl_context.make_current(&self.gl_surface);
62 }
63}
64
65impl WinState {
66 pub fn new(
67 event_loop: &ActiveEventLoop,
68 entity: Entity,
69 #[allow(unused_mut)] mut window_attributes: WindowAttributes,
70 skia_resource_cache_limit: usize,
71 #[allow(unused_variables)] owner: Option<Arc<winit::window::Window>>,
72 ) -> Result<Self, Box<dyn Error>> {
73 #[cfg(target_os = "windows")]
74 let (window, gl_config) = {
75 if let Some(owner) = owner {
76 let RawWindowHandle::Win32(handle) = owner.window_handle().unwrap().as_raw() else {
77 unreachable!();
78 };
79 window_attributes = window_attributes.with_owner_window(handle.hwnd.get());
80 }
81
82 let window_attributes = window_attributes.with_visible(false);
85
86 let (window, config) = build_window(event_loop, window_attributes);
87
88 let window = window.expect("Could not create window with OpenGL context");
89 set_cloak(&window, true);
94
95 (window, config)
96 };
97
98 #[cfg(not(target_os = "windows"))]
99 let (window, gl_config) = {
100 let (window, config) = build_window(event_loop, window_attributes);
101 let window = window.expect("Could not create window with OpenGL context");
102 (window, config)
103 };
104
105 window.set_ime_allowed(true);
106
107 let raw_window_handle = window.window_handle().unwrap().as_raw();
108
109 let gl_display = gl_config.display();
110
111 let context_attributes = ContextAttributesBuilder::new()
112 .with_profile(GlProfile::Core)
113 .with_context_api(ContextApi::OpenGl(None))
114 .build(Some(raw_window_handle));
115
116 let fallback_context_attributes = ContextAttributesBuilder::new()
117 .with_profile(GlProfile::Core)
118 .with_context_api(ContextApi::Gles(None))
119 .build(Some(raw_window_handle));
120
121 let not_current_gl_context = unsafe {
122 gl_display.create_context(&gl_config, &context_attributes).unwrap_or_else(|_| {
123 gl_display
124 .create_context(&gl_config, &fallback_context_attributes)
125 .expect("failed to create context")
126 })
127 };
128
129 let (width, height): (u32, u32) = window.inner_size().into();
130
131 let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().with_srgb(Some(true)).build(
132 raw_window_handle,
133 NonZeroU32::new(width.max(1)).unwrap(),
134 NonZeroU32::new(height.max(1)).unwrap(),
135 );
136
137 let gl_surface =
138 unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() };
139
140 let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
141
142 gl::load_with(|s| {
150 gl_config.display().get_proc_address(CString::new(s).unwrap().as_c_str())
151 });
152
153 let interface = skia_safe::gpu::gl::Interface::new_load_with(|name| {
154 if name == "eglGetCurrentDisplay" {
155 return std::ptr::null();
156 }
157 gl_config.display().get_proc_address(CString::new(name).unwrap().as_c_str())
158 })
159 .expect("Could not create interface");
160
161 let mut context_options = ContextOptions::new();
163 context_options.skip_gl_error_checks = context_options::Enable::Yes;
164
165 let mut gr_context = skia_safe::gpu::direct_contexts::make_gl(interface, &context_options)
166 .expect("Could not create direct context");
167 gr_context.set_resource_cache_limit(skia_resource_cache_limit);
168
169 let fb_info = {
170 let mut fboid: GLint = 0;
171 unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
172
173 FramebufferInfo {
174 fboid: fboid.try_into().unwrap(),
175 format: skia_safe::gpu::gl::Format::RGBA8.into(),
176 ..Default::default()
177 }
178 };
179
180 let num_samples = gl_config.num_samples() as usize;
181 let stencil_size = gl_config.stencil_size() as usize;
182
183 let mut surface =
184 create_surface(&window, fb_info, &mut gr_context, num_samples, stencil_size);
185
186 let inner_size = window.inner_size();
187
188 let dirty_surface = surface
189 .new_surface_with_dimensions((inner_size.width as i32, inner_size.height as i32))
190 .unwrap();
191
192 Ok(WinState {
194 entity,
195 gl_config,
196 gl_context,
197 id: window.id(),
198 gr_context,
199 gl_surface,
200 window: Arc::new(window),
201 surface,
202 dirty_surface,
203 should_close: false,
204 #[cfg(target_os = "windows")]
205 is_initially_cloaked: true,
206 })
207 }
208
209 pub fn window(&self) -> &winit::window::Window {
211 &self.window
212 }
213
214 pub fn make_current(&mut self) {
215 self.gl_context.make_current(&self.gl_surface).unwrap();
216 }
217
218 pub fn resize(&mut self, size: PhysicalSize<u32>) {
219 self.gl_context.make_current(&self.gl_surface).unwrap();
220 let (width, height): (u32, u32) = size.into();
221
222 if width == 0 || height == 0 {
223 return;
224 }
225
226 let fb_info = {
227 let mut fboid: GLint = 0;
228 unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
229
230 FramebufferInfo {
231 fboid: fboid.try_into().unwrap(),
232 format: skia_safe::gpu::gl::Format::RGBA8.into(),
233 ..Default::default()
234 }
235 };
236
237 self.surface = create_surface(
238 &self.window,
239 fb_info,
240 &mut self.gr_context,
241 self.gl_config.num_samples() as usize,
242 self.gl_config.stencil_size() as usize,
243 );
244
245 self.dirty_surface = self
246 .surface
247 .new_surface_with_dimensions((width.max(1) as i32, height.max(1) as i32))
248 .unwrap();
249
250 self.gl_surface.resize(
251 &self.gl_context,
252 NonZeroU32::new(width.max(1)).unwrap(),
253 NonZeroU32::new(height.max(1)).unwrap(),
254 );
255 }
256
257 pub fn swap_buffers(&mut self) {
258 self.gr_context.flush_and_submit();
259 self.gl_surface.swap_buffers(&self.gl_context).expect("Failed to swap buffers");
260 }
261}
262
263fn build_window(
264 event_loop: &ActiveEventLoop,
265 window_attributes: WindowAttributes,
266) -> (Option<winit::window::Window>, Config) {
267 let template = ConfigTemplateBuilder::new().with_alpha_size(8).with_transparency(true);
268 let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes));
269
270 display_builder
271 .build(event_loop, template, |configs| {
272 configs
275 .reduce(|accum, config| {
276 let transparency_check = config.supports_transparency().unwrap_or(false)
277 & !accum.supports_transparency().unwrap_or(false);
278
279 if transparency_check || config.num_samples() < accum.num_samples() {
280 config
281 } else {
282 accum
283 }
284 })
285 .unwrap()
286 })
287 .unwrap()
288}
289
290#[cfg(target_os = "windows")]
295pub fn set_cloak(window: &winit::window::Window, state: bool) -> bool {
296 use windows_sys::{
297 Win32::{
298 Foundation::{FALSE, HWND, TRUE},
299 Graphics::Dwm::{DWMWA_CLOAK, DwmSetWindowAttribute},
300 },
301 core::BOOL,
302 };
303
304 let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() else {
305 unreachable!();
306 };
307
308 let value = if state { TRUE } else { FALSE };
309
310 let result = unsafe {
311 DwmSetWindowAttribute(
312 handle.hwnd.get() as HWND,
313 DWMWA_CLOAK as u32,
314 std::ptr::from_ref(&value).cast(),
315 std::mem::size_of::<BOOL>() as u32,
316 )
317 };
318
319 result == 0 }
321
322pub fn create_surface(
323 window: &winit::window::Window,
324 fb_info: FramebufferInfo,
325 gr_context: &mut skia_safe::gpu::DirectContext,
326 num_samples: usize,
327 stencil_size: usize,
328) -> Surface {
329 let size = window.inner_size();
330 let size = (
331 size.width.try_into().expect("Could not convert width"),
332 size.height.try_into().expect("Could not convert height"),
333 );
334
335 let backend_render_target =
336 backend_render_targets::make_gl(size, num_samples, stencil_size, fb_info);
337
338 let surface_props = SurfaceProps::new_with_text_properties(
339 SurfacePropsFlags::default(),
340 PixelGeometry::default(),
341 0.5,
342 0.0,
343 );
344
345 gpu::surfaces::wrap_backend_render_target(
346 gr_context,
347 &backend_render_target,
348 SurfaceOrigin::BottomLeft,
349 ColorType::RGBA8888,
350 ColorSpace::new_srgb(),
351 Some(surface_props).as_ref(),
352 )
354 .expect("Could not create skia surface")
355}
356
357type WindowCallback = Option<Box<dyn Fn(&mut EventContext)>>;
358
359pub struct Window {
360 pub window: Option<Arc<winit::window::Window>>,
361 pub on_close: WindowCallback,
362 pub on_create: WindowCallback,
363 pub should_close: bool,
364 pub(crate) custom_cursors: Arc<HashMap<CursorIcon, CustomCursor>>,
365}
366
367impl Window {
368 fn window(&self) -> &winit::window::Window {
369 self.window.as_ref().unwrap()
370 }
371
372 pub fn new(cx: &mut Context, content: impl 'static + Fn(&mut Context)) -> Handle<Self> {
373 Self {
374 window: None,
375 on_close: None,
376 on_create: None,
377 should_close: false,
378 custom_cursors: Default::default(),
379 }
380 .build(cx, |cx| {
381 cx.windows.insert(
382 cx.current(),
383 WindowState {
384 content: Some(Arc::new(content)),
385 window_description: WindowDescription::new(),
386 ..Default::default()
387 },
388 );
389 cx.tree.set_window(cx.current(), true);
390 })
391 .position_type(PositionType::Absolute)
392 .anchor_target(AnchorTarget::Window)
393 }
394
395 pub fn popup(
396 cx: &mut Context,
397 is_modal: bool,
398 content: impl 'static + Fn(&mut Context),
399 ) -> Handle<Self> {
400 Self {
401 window: None,
402 on_close: None,
403 on_create: None,
404 should_close: false,
405 custom_cursors: Default::default(),
406 }
407 .build(cx, |cx| {
408 let parent_window = cx.parent_window();
409 if is_modal {
410 cx.emit_to(parent_window, WindowEvent::SetEnabled(false));
411 }
412
413 cx.windows.insert(
414 cx.current(),
415 WindowState {
416 owner: Some(parent_window),
417 is_modal: true,
418 content: Some(Arc::new(content)),
419 window_description: WindowDescription::new(),
420 ..Default::default()
421 },
422 );
423 cx.tree.set_window(cx.current(), true);
424 })
425 .position_type(PositionType::Absolute)
426 .anchor_target(AnchorTarget::Window)
427 .lock_focus_to_within()
428 }
429}
430
431impl View for Window {
432 fn element(&self) -> Option<&'static str> {
433 Some("window")
434 }
435
436 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
437 event.map(|window_event, meta| match window_event {
438 WindowEvent::Destroyed => {
439 let parent_window = cx.parent_window();
440 cx.emit_to(parent_window, WindowEvent::SetEnabled(true));
441 }
442
443 WindowEvent::GrabCursor(flag) => {
444 let grab_mode = if *flag { CursorGrabMode::Locked } else { CursorGrabMode::None };
445 self.window().set_cursor_grab(grab_mode).expect("Failed to set cursor grab");
446 }
447
448 WindowEvent::SetCursorPosition(x, y) => {
449 self.window()
450 .set_cursor_position(winit::dpi::Position::Physical(PhysicalPosition::new(
451 *x as i32, *y as i32,
452 )))
453 .expect("Failed to set cursor position");
454 }
455
456 WindowEvent::SetCursor(cursor) => {
457 let Some(icon) = cursor_icon_to_cursor_icon(*cursor) else {
458 self.window().set_cursor_visible(false);
459 return;
460 };
461
462 if let Some(custom_icon) = self.custom_cursors.get(&icon) {
463 self.window().set_cursor(custom_icon.clone());
464 } else {
465 self.window().set_cursor(icon);
466 }
467
468 self.window().set_cursor_visible(true);
469 }
470
471 WindowEvent::SetTitle(title) => {
472 if let Some(window_state) = cx.windows.get_mut(&cx.current()) {
473 window_state.window_description.title = title.clone();
474 }
475
476 if let Some(window) = &self.window {
477 window.set_title(title);
478 }
479 }
480
481 WindowEvent::SetSize(size) => {
482 let _ = self.window().request_inner_size(LogicalSize::new(size.width, size.height));
483 }
484
485 WindowEvent::SetMinSize(size) => {
486 self.window()
487 .set_min_inner_size(size.map(|size| LogicalSize::new(size.width, size.height)));
488 }
489
490 WindowEvent::SetMaxSize(size) => {
491 self.window()
492 .set_max_inner_size(size.map(|size| LogicalSize::new(size.width, size.height)));
493 }
494
495 WindowEvent::SetPosition(pos) => {
496 self.window().set_outer_position(LogicalPosition::new(pos.x, pos.y));
497 meta.consume();
498 }
499
500 WindowEvent::SetResizable(flag) => {
501 self.window().set_resizable(*flag);
502 }
503
504 WindowEvent::SetMinimized(flag) => {
505 self.window().set_minimized(*flag);
506 }
507
508 WindowEvent::SetMaximized(flag) => {
509 self.window().set_maximized(*flag);
510 }
511
512 WindowEvent::SetVisible(flag) => {
513 self.window().set_visible(*flag);
514
515 meta.consume();
516 }
517
518 WindowEvent::SetDecorations(flag) => {
519 self.window().set_decorations(*flag);
520 }
521
522 WindowEvent::ReloadStyles => {
523 cx.reload_styles().unwrap();
524 }
525
526 WindowEvent::WindowClose => {
527 self.should_close = true;
528
529 cx.close_window();
530
531 if let Some(callback) = &self.on_close {
532 callback(cx);
533 }
534
535 meta.consume();
536 }
537
538 WindowEvent::FocusNext => {
539 cx.focus_next();
540 }
541
542 WindowEvent::FocusPrev => {
543 cx.focus_prev();
544 }
545
546 WindowEvent::Redraw => {
547 self.window().request_redraw();
548 }
549
550 #[allow(unused_variables)]
551 WindowEvent::SetEnabled(flag) => {
552 #[cfg(target_os = "windows")]
553 self.window().set_enable(*flag);
554
555 self.window().focus_window();
556 }
557
558 WindowEvent::DragWindow => {
559 self.window().drag_window().expect("Failed to init drag window");
560 meta.consume();
561 }
562
563 WindowEvent::SetAlwaysOnTop(flag) => {
564 self.window().set_window_level(if *flag {
565 WindowLevel::AlwaysOnTop
566 } else {
567 WindowLevel::Normal
568 });
569 }
570
571 WindowEvent::SetImeCursorArea(position, size) => {
572 let position = PhysicalPosition::new(position.0 as i32, position.1 as i32);
573 let size = PhysicalSize::new(size.0, size.1);
574 self.window().set_ime_cursor_area(position, size);
575 }
576
577 _ => {}
578 })
579 }
580}
581
582impl WindowModifiers for Handle<'_, Window> {
583 fn on_close(self, callback: impl Fn(&mut EventContext) + 'static) -> Self {
584 self.modify(|window| window.on_close = Some(Box::new(callback)))
585 }
586
587 fn on_create(self, callback: impl Fn(&mut EventContext) + 'static) -> Self {
588 self.modify(|window| window.on_create = Some(Box::new(callback)))
589 }
590
591 fn title<T: ToStringLocalized>(mut self, title: impl Res<T> + Clone + 'static) -> Self {
592 let entity = self.entity();
593 let initial_title = title.get_value(&self).to_string_local(&self);
594 if let Some(win_state) = self.context().windows.get_mut(&entity) {
595 win_state.window_description.title = initial_title;
596 }
597
598 let getter_for_locale = title.clone();
599
600 self.context().with_current(entity, |cx| {
601 title.set_or_bind(cx, move |cx, val| {
602 let title_str = val.get_value(cx).to_string_local(cx);
603 cx.emit(WindowEvent::SetTitle(title_str));
604 });
605
606 let locale = cx.environment().locale;
607 locale.set_or_bind(cx, move |cx, _| {
608 let title = getter_for_locale.get_value(cx).to_string_local(cx);
609 cx.emit(WindowEvent::SetTitle(title));
610 });
611 });
612
613 self
614 }
615
616 fn inner_size<S: Into<WindowSize>>(mut self, size: impl Res<S>) -> Self {
617 let entity = self.entity();
618 let size = size.get_value(&self).into();
619 if let Some(win_state) = self.context().windows.get_mut(&entity) {
620 win_state.window_description.inner_size = size;
621 }
622
623 self
624 }
625
626 fn min_inner_size<S: Into<WindowSize>>(mut self, size: impl Res<Option<S>>) -> Self {
627 let entity = self.entity();
628 let size = size.get_value(&self).map(|size| size.into());
629 if let Some(win_state) = self.context().windows.get_mut(&entity) {
630 win_state.window_description.min_inner_size = size;
631 }
632
633 self
634 }
635
636 fn max_inner_size<S: Into<WindowSize>>(mut self, size: impl Res<Option<S>>) -> Self {
637 let entity = self.entity();
638 let size = size.get_value(&self).map(|size| size.into());
639 if let Some(win_state) = self.context().windows.get_mut(&entity) {
640 win_state.window_description.max_inner_size = size;
641 }
642
643 self
644 }
645
646 fn position<P: Into<vizia_window::WindowPosition>>(mut self, position: impl Res<P>) -> Self {
647 let entity = self.entity();
648 let pos = Some(position.get_value(&self).into());
649 if let Some(win_state) = self.context().windows.get_mut(&entity) {
650 win_state.window_description.position = pos;
651 }
652
653 self
654 }
655
656 fn offset<P: Into<vizia_window::WindowPosition>>(mut self, offset: impl Res<P>) -> Self {
657 let entity = self.entity();
658 let offset = Some(offset.get_value(&self).into());
659 if let Some(win_state) = self.context().windows.get_mut(&entity) {
660 win_state.window_description.offset = offset;
661 }
662
663 self
664 }
665
666 fn anchor<P: Into<vizia_window::Anchor>>(mut self, anchor: impl Res<P>) -> Self {
667 let entity = self.entity();
668 let anchor = Some(anchor.get_value(&self).into());
669 if let Some(win_state) = self.context().windows.get_mut(&entity) {
670 win_state.window_description.anchor = anchor;
671 }
672
673 self
674 }
675
676 fn anchor_target<P: Into<vizia_window::AnchorTarget>>(
677 mut self,
678 anchor_target: impl Res<P>,
679 ) -> Self {
680 let entity = self.entity();
681 let anchor_target = Some(anchor_target.get_value(&self).into());
682 if let Some(win_state) = self.context().windows.get_mut(&entity) {
683 win_state.window_description.anchor_target = anchor_target;
684 }
685
686 self
687 }
688
689 fn parent_anchor<P: Into<Anchor>>(mut self, parent_anchor: impl Res<P>) -> Self {
690 let entity = self.entity();
691 let parent_anchor = Some(parent_anchor.get_value(&self).into());
692 if let Some(win_state) = self.context().windows.get_mut(&entity) {
693 win_state.window_description.parent_anchor = parent_anchor;
694 }
695
696 self
697 }
698
699 fn resizable(mut self, flag: impl Res<bool>) -> Self {
700 let entity = self.entity();
701 let flag = flag.get_value(&self);
702 if let Some(win_state) = self.context().windows.get_mut(&entity) {
703 win_state.window_description.resizable = flag;
704 }
705
706 self
707 }
708
709 fn minimized(mut self, flag: impl Res<bool>) -> Self {
710 let entity = self.entity();
711 let flag = flag.get_value(&self);
712 if let Some(win_state) = self.context().windows.get_mut(&entity) {
713 win_state.window_description.minimized = flag;
714 }
715
716 self
717 }
718
719 fn maximized(mut self, flag: impl Res<bool>) -> Self {
720 let entity = self.entity();
721 let flag = flag.get_value(&self);
722 if let Some(win_state) = self.context().windows.get_mut(&entity) {
723 win_state.window_description.maximized = flag;
724 }
725
726 self
727 }
728
729 fn visible(mut self, flag: impl Res<bool>) -> Self {
730 let entity = self.entity();
731 let flag = flag.get_value(&self);
732 if let Some(win_state) = self.context().windows.get_mut(&entity) {
733 win_state.window_description.visible = flag
734 }
735
736 self
737 }
738
739 fn transparent(mut self, flag: bool) -> Self {
740 let entity = self.entity();
741 if let Some(win_state) = self.context().windows.get_mut(&entity) {
742 win_state.window_description.transparent = flag
743 }
744
745 self
746 }
747
748 fn decorations(mut self, flag: bool) -> Self {
749 let entity = self.entity();
750 if let Some(win_state) = self.context().windows.get_mut(&entity) {
751 win_state.window_description.decorations = flag
752 }
753
754 self
755 }
756
757 fn always_on_top(mut self, flag: bool) -> Self {
758 let entity = self.entity();
759 if let Some(win_state) = self.context().windows.get_mut(&entity) {
760 win_state.window_description.always_on_top = flag
761 }
762
763 self
764 }
765
766 fn vsync(mut self, flag: bool) -> Self {
767 let entity = self.entity();
768 if let Some(win_state) = self.context().windows.get_mut(&entity) {
769 win_state.window_description.vsync = flag
770 }
771
772 self
773 }
774
775 fn skia_resource_cache_limit(mut self, limit: usize) -> Self {
776 let entity = self.entity();
777 if let Some(win_state) = self.context().windows.get_mut(&entity) {
778 win_state.window_description.skia_resource_cache_limit = limit;
779 }
780
781 self
782 }
783
784 fn icon(mut self, width: u32, height: u32, image: Vec<u8>) -> Self {
785 let entity = self.entity();
786 if let Some(win_state) = self.context().windows.get_mut(&entity) {
787 win_state.window_description.icon = Some(image);
788 win_state.window_description.icon_width = width;
789 win_state.window_description.icon_height = height;
790 }
791
792 self
793 }
794
795 fn enabled_window_buttons(mut self, window_buttons: WindowButtons) -> Self {
796 let entity = self.entity();
797 if let Some(win_state) = self.context().windows.get_mut(&entity) {
798 win_state.window_description.enabled_window_buttons = window_buttons;
799 }
800
801 self
802 }
803}