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