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