From f88c6df269b86b455705c8a7c1d6c3e28b3aa75e Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Mon, 20 Oct 2025 17:07:39 +0200 Subject: [PATCH 1/6] window abstraction --- desktop/src/app.rs | 27 ++---- desktop/src/lib.rs | 2 +- desktop/src/native_window.rs | 71 --------------- desktop/src/render/graphics_state.rs | 12 ++- desktop/src/window.rs | 86 +++++++++++++++++++ desktop/src/window/linux.rs | 28 ++++++ desktop/src/window/mac.rs | 20 +++++ desktop/src/window/win.rs | 24 ++++++ .../src/{native_window => window}/windows.rs | 0 9 files changed, 173 insertions(+), 97 deletions(-) delete mode 100644 desktop/src/native_window.rs create mode 100644 desktop/src/window.rs create mode 100644 desktop/src/window/linux.rs create mode 100644 desktop/src/window/mac.rs create mode 100644 desktop/src/window/win.rs rename desktop/src/{native_window => window}/windows.rs (100%) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 0e2d9d86ca..0ad28d6c34 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,7 +1,6 @@ use rfd::AsyncFileDialog; use std::fs; use std::path::PathBuf; -use std::sync::Arc; use std::sync::mpsc::Receiver; use std::sync::mpsc::Sender; use std::sync::mpsc::SyncSender; @@ -12,22 +11,20 @@ use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::event_loop::ControlFlow; -use winit::window::Window; use winit::window::WindowId; use crate::cef; use crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS; use crate::event::{AppEvent, AppEventScheduler}; -use crate::native_window; use crate::persist::PersistentData; use crate::render::GraphicsState; +use crate::window::Window; use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform}; use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages}; pub(crate) struct App { cef_context: Box, - window: Option>, - native_window: native_window::NativeWindowHandle, + window: Option, cef_schedule: Option, cef_window_size_sender: Sender, graphics_state: Option, @@ -82,7 +79,6 @@ impl App { web_communication_initialized: false, web_communication_startup_buffer: Vec::new(), persistent_data, - native_window: Default::default(), launch_documents, } } @@ -173,18 +169,17 @@ impl App { } DesktopFrontendMessage::MinimizeWindow => { if let Some(window) = &self.window { - window.set_minimized(true); + window.minimize(); } } DesktopFrontendMessage::MaximizeWindow => { if let Some(window) = &self.window { - let maximized = !window.is_maximized(); - window.set_maximized(maximized); + window.toggle_maximize(); } } DesktopFrontendMessage::DragWindow => { if let Some(window) = &self.window { - let _ = window.drag_window(); + let _ = window.start_drag(); } } DesktopFrontendMessage::CloseWindow => { @@ -348,15 +343,11 @@ impl App { } impl ApplicationHandler for App { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { - let window_attributes = self.native_window.build(event_loop); - - let window: Arc = Arc::from(event_loop.create_window(window_attributes).unwrap()); - - self.native_window.setup(window.as_ref()); + let window = Window::new(event_loop); + self.window = Some(window); - let graphics_state = GraphicsState::new(window.clone(), self.wgpu_context.clone()); + let graphics_state = GraphicsState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone()); - self.window = Some(window); self.graphics_state = Some(graphics_state); tracing::info!("Winit window created and ready"); @@ -397,7 +388,7 @@ impl ApplicationHandler for App { let Some(ref mut graphics_state) = self.graphics_state else { return }; // Only rerender once we have a new UI texture to display if let Some(window) = &self.window { - match graphics_state.render(window.as_ref()) { + match graphics_state.render(window) { Ok(_) => {} Err(wgpu::SurfaceError::Lost) => { tracing::warn!("lost surface"); diff --git a/desktop/src/lib.rs b/desktop/src/lib.rs index 04a99182af..0accce4e3c 100644 --- a/desktop/src/lib.rs +++ b/desktop/src/lib.rs @@ -10,7 +10,7 @@ mod cef; mod cli; mod dirs; mod event; -mod native_window; +mod window; mod persist; mod render; diff --git a/desktop/src/native_window.rs b/desktop/src/native_window.rs deleted file mode 100644 index 154ffcb316..0000000000 --- a/desktop/src/native_window.rs +++ /dev/null @@ -1,71 +0,0 @@ -use winit::event_loop::ActiveEventLoop; -use winit::window::{Window, WindowAttributes}; - -use crate::consts::APP_NAME; - -#[cfg(target_os = "windows")] -mod windows; - -pub(crate) enum NativeWindowHandle { - #[cfg(target_os = "windows")] - #[expect(private_interfaces, dead_code)] - Windows(windows::WindowsNativeWindowHandle), - None, -} -impl Default for NativeWindowHandle { - fn default() -> Self { - Self::None - } -} -impl NativeWindowHandle { - #[allow(unused_variables)] - pub(super) fn build(&mut self, event_loop: &dyn ActiveEventLoop) -> WindowAttributes { - let mut window = WindowAttributes::default() - .with_title(APP_NAME) - .with_min_surface_size(winit::dpi::LogicalSize::new(400, 300)) - .with_surface_size(winit::dpi::LogicalSize::new(1200, 800)) - .with_resizable(true) - .with_theme(Some(winit::window::Theme::Dark)); - - #[cfg(target_os = "linux")] - { - use crate::consts::{APP_ID, APP_NAME}; - use winit::platform::wayland::ActiveEventLoopExtWayland; - use winit::platform::wayland::WindowAttributesWayland; - use winit::platform::x11::WindowAttributesX11; - window = if event_loop.is_wayland() { - let wayland_window = WindowAttributesWayland::default().with_name(APP_ID, "").with_prefer_csd(true); - window.with_platform_attributes(Box::new(wayland_window)) - } else { - let x11_window = WindowAttributesX11::default().with_name(APP_ID, APP_NAME); - window.with_platform_attributes(Box::new(x11_window)) - }; - } - - #[cfg(target_os = "windows")] - { - if let Ok(win_icon) = winit::platform::windows::WinIcon::from_resource(1, None) { - let icon = winit::icon::Icon(std::sync::Arc::new(win_icon)); - window = window.with_window_icon(Some(icon)); - } - } - - #[cfg(target_os = "macos")] - { - let mac_window = winit::platform::macos::WindowAttributesMacOS::default() - .with_titlebar_transparent(true) - .with_fullsize_content_view(true) - .with_title_hidden(true); - window = window.with_platform_attributes(Box::new(mac_window)); - } - - window - } - #[allow(unused_variables)] - pub(crate) fn setup(&mut self, window: &dyn Window) { - #[cfg(target_os = "windows")] - { - *self = NativeWindowHandle::Windows(windows::WindowsNativeWindowHandle::new(window)); - } - } -} diff --git a/desktop/src/render/graphics_state.rs b/desktop/src/render/graphics_state.rs index 1d1d1aef00..31a95173cd 100644 --- a/desktop/src/render/graphics_state.rs +++ b/desktop/src/render/graphics_state.rs @@ -1,5 +1,4 @@ -use std::sync::Arc; -use winit::window::Window; +use crate::window::Window; use graphite_desktop_wrapper::{Color, WgpuContext, WgpuExecutor}; @@ -24,10 +23,9 @@ pub(crate) struct GraphicsState { } impl GraphicsState { - pub(crate) fn new(window: Arc, context: WgpuContext) -> Self { + pub(crate) fn new(window: &Window, context: WgpuContext) -> GraphicsState { let size = window.surface_size(); - - let surface = context.instance.create_surface(window).unwrap(); + let surface = window.create_surface(context.instance.clone()); let surface_caps = surface.get_capabilities(&context.adapter); let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]); @@ -165,7 +163,7 @@ impl GraphicsState { let wgpu_executor = WgpuExecutor::with_context(context.clone()).expect("Failed to create WgpuExecutor"); - Self { + GraphicsState { surface, context, executor: wgpu_executor, @@ -232,7 +230,7 @@ impl GraphicsState { self.bind_overlays_texture(texture); } - pub(crate) fn render(&mut self, window: &dyn Window) -> Result<(), wgpu::SurfaceError> { + pub(crate) fn render(&mut self, window: &Window) -> Result<(), wgpu::SurfaceError> { if let Some(scene) = self.overlays_scene.take() { self.render_overlays(scene); } diff --git a/desktop/src/window.rs b/desktop/src/window.rs new file mode 100644 index 0000000000..881803d7a0 --- /dev/null +++ b/desktop/src/window.rs @@ -0,0 +1,86 @@ +use std::sync::Arc; +use winit::event_loop::ActiveEventLoop; +use winit::window::{Window as WinitWindow, WindowAttributes}; + +use crate::consts::APP_NAME; + +pub(crate) trait NativeWindow { + fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes; + fn new(window: &dyn WinitWindow) -> Self; +} + +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "linux")] +use linux as native; + +#[cfg(target_os = "macos")] +mod mac; +#[cfg(target_os = "macos")] +use mac as native; + +#[cfg(target_os = "windows")] +mod win; +#[cfg(target_os = "windows")] +use win as native; + +pub(crate) struct Window { + winit_window: Arc, + native_handle: native::NativeWindowImpl, +} + +impl Window { + pub(crate) fn new(event_loop: &dyn ActiveEventLoop) -> Self { + let mut attributes = WindowAttributes::default() + .with_title(APP_NAME) + .with_min_surface_size(winit::dpi::LogicalSize::new(400, 300)) + .with_surface_size(winit::dpi::LogicalSize::new(1200, 800)) + .with_resizable(true) + .with_theme(Some(winit::window::Theme::Dark)); + + attributes = native::NativeWindowImpl::configure(attributes, event_loop); + + let winit_window = event_loop.create_window(attributes).unwrap(); + let native_handle = native::NativeWindowImpl::new(winit_window.as_ref()); + Self { + winit_window: winit_window.into(), + native_handle, + } + } + + pub(crate) fn request_redraw(&self) { + self.winit_window.request_redraw(); + } + + pub(crate) fn create_surface(&self, instance: Arc) -> wgpu::Surface<'static> { + instance.create_surface(self.winit_window.clone()).unwrap() + } + + pub(crate) fn pre_present_notify(&self) { + self.winit_window.pre_present_notify(); + } + + pub(crate) fn surface_size(&self) -> winit::dpi::PhysicalSize { + self.winit_window.surface_size() + } + + pub(crate) fn minimize(&self) { + self.winit_window.set_minimized(true); + } + + pub(crate) fn toggle_maximize(&self) { + self.winit_window.set_maximized(!self.winit_window.is_maximized()); + } + + pub(crate) fn is_maximized(&self) -> bool { + self.winit_window.is_maximized() + } + + pub(crate) fn start_drag(&self) { + let _ = self.winit_window.drag_window(); + } + + pub(crate) fn set_cursor(&self, cursor: winit::cursor::Cursor) { + self.winit_window.set_cursor(cursor); + } +} diff --git a/desktop/src/window/linux.rs b/desktop/src/window/linux.rs new file mode 100644 index 0000000000..52d237bcdc --- /dev/null +++ b/desktop/src/window/linux.rs @@ -0,0 +1,28 @@ +use winit::event_loop::ActiveEventLoop; +use winit::platform::wayland::ActiveEventLoopExtWayland; +use winit::platform::wayland::WindowAttributesWayland; +use winit::platform::x11::WindowAttributesX11; +use winit::window::{Window, WindowAttributes}; + +use crate::consts::{APP_ID, APP_NAME}; + +use super::NativeWindow; + +pub(super) struct NativeWindowImpl {} + +impl NativeWindow for NativeWindowImpl { + fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { + if event_loop.is_wayland() { + let wayland_attributes = WindowAttributesWayland::default().with_name(APP_ID, "").with_prefer_csd(true); + attributes.with_platform_attributes(Box::new(wayland_attributes)) + } else { + let x11_attributes = WindowAttributesX11::default().with_name(APP_ID, APP_NAME); + attributes.with_platform_attributes(Box::new(x11_attributes)) + } + attributes.with_platform_attributes(Box::new(mac_window)) + } + + fn new(_window: &dyn Window) -> Self { + NativeWindowImpl {} + } +} diff --git a/desktop/src/window/mac.rs b/desktop/src/window/mac.rs new file mode 100644 index 0000000000..83386909b5 --- /dev/null +++ b/desktop/src/window/mac.rs @@ -0,0 +1,20 @@ +use winit::event_loop::ActiveEventLoop; +use winit::window::{Window, WindowAttributes}; + +use super::NativeWindow; + +pub(super) struct NativeWindowImpl {} + +impl NativeWindow for NativeWindowImpl { + fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { + let mac_window = winit::platform::macos::WindowAttributesMacOS::default() + .with_titlebar_transparent(true) + .with_fullsize_content_view(true) + .with_title_hidden(true); + attributes.with_platform_attributes(Box::new(mac_window)) + } + + fn new(_window: &dyn Window) -> Self { + NativeWindowImpl {} + } +} diff --git a/desktop/src/window/win.rs b/desktop/src/window/win.rs new file mode 100644 index 0000000000..0231d7ddee --- /dev/null +++ b/desktop/src/window/win.rs @@ -0,0 +1,24 @@ +use winit::event_loop::ActiveEventLoop; +use winit::window::{Window, WindowAttributes}; + +use super::NativeWindow; + +pub(super) struct NativeWindowImpl { + native_handle: windows::WindowsNativeWindowHandle, +} + +impl NativeWindow for NativeWindowImpl { + fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { + if let Ok(win_icon) = winit::platform::windows::WinIcon::from_resource(1, None) { + let icon = winit::icon::Icon(std::sync::Arc::new(win_icon)); + attributes.with_window_icon(Some(icon)) + } else { + attributes + } + } + + fn new(_window: &dyn Window) -> Self { + let native_handle = windows::WindowsNativeWindowHandle::new(window); + NativeWindowImpl { native_handle } + } +} diff --git a/desktop/src/native_window/windows.rs b/desktop/src/window/windows.rs similarity index 100% rename from desktop/src/native_window/windows.rs rename to desktop/src/window/windows.rs From c6e6813b53d7a6d156e3efa6917ba29027703938 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Mon, 20 Oct 2025 15:13:02 +0000 Subject: [PATCH 2/6] fix linux --- desktop/src/window.rs | 1 + desktop/src/window/linux.rs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/src/window.rs b/desktop/src/window.rs index 881803d7a0..8445e55f2a 100644 --- a/desktop/src/window.rs +++ b/desktop/src/window.rs @@ -26,6 +26,7 @@ use win as native; pub(crate) struct Window { winit_window: Arc, + #[allow(dead_code)] native_handle: native::NativeWindowImpl, } diff --git a/desktop/src/window/linux.rs b/desktop/src/window/linux.rs index 52d237bcdc..5a48385acc 100644 --- a/desktop/src/window/linux.rs +++ b/desktop/src/window/linux.rs @@ -11,7 +11,7 @@ use super::NativeWindow; pub(super) struct NativeWindowImpl {} impl NativeWindow for NativeWindowImpl { - fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { + fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes { if event_loop.is_wayland() { let wayland_attributes = WindowAttributesWayland::default().with_name(APP_ID, "").with_prefer_csd(true); attributes.with_platform_attributes(Box::new(wayland_attributes)) @@ -19,7 +19,6 @@ impl NativeWindow for NativeWindowImpl { let x11_attributes = WindowAttributesX11::default().with_name(APP_ID, APP_NAME); attributes.with_platform_attributes(Box::new(x11_attributes)) } - attributes.with_platform_attributes(Box::new(mac_window)) } fn new(_window: &dyn Window) -> Self { From e647e179e6bd4c9251facbd564d4b028cfa05fe4 Mon Sep 17 00:00:00 2001 From: Timon Date: Mon, 20 Oct 2025 08:44:10 -0700 Subject: [PATCH 3/6] fix windows --- desktop/src/window/win.rs | 14 +- desktop/src/window/win/native_handle.rs | 319 ++++++++++++++++++++++++ 2 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 desktop/src/window/win/native_handle.rs diff --git a/desktop/src/window/win.rs b/desktop/src/window/win.rs index 0231d7ddee..8f23866f08 100644 --- a/desktop/src/window/win.rs +++ b/desktop/src/window/win.rs @@ -4,7 +4,7 @@ use winit::window::{Window, WindowAttributes}; use super::NativeWindow; pub(super) struct NativeWindowImpl { - native_handle: windows::WindowsNativeWindowHandle, + native_handle: native_handle::NativeWindowHandle, } impl NativeWindow for NativeWindowImpl { @@ -17,8 +17,16 @@ impl NativeWindow for NativeWindowImpl { } } - fn new(_window: &dyn Window) -> Self { - let native_handle = windows::WindowsNativeWindowHandle::new(window); + fn new(window: &dyn Window) -> Self { + let native_handle = native_handle::NativeWindowHandle::new(window); NativeWindowImpl { native_handle } } } + +impl Drop for NativeWindowImpl { + fn drop(&mut self) { + self.native_handle.destroy(); + } +} + +mod native_handle; diff --git a/desktop/src/window/win/native_handle.rs b/desktop/src/window/win/native_handle.rs new file mode 100644 index 0000000000..17ff1f19fa --- /dev/null +++ b/desktop/src/window/win/native_handle.rs @@ -0,0 +1,319 @@ +//! Implements a Windows-specific custom window frame (no titlebar, but native boarder, shadows and resize). +//! Look and feel should be similar to a standard window. +//! +//! Implementation notes: +//! - Windows that don't use standard decorations don't get native resize handles or shadows by default. +//! - We implement resize handles (outside the main window) by creating an invisible "helper" window that +//! is a little larger than the main window and positioned on top of it. The helper window does hit-testing +//! and triggers native resize operations on the main window when the user clicks and drags a resize area. +//! - The helper window is a invisible window that never activates, so it doesn't steal focus from the main window. +//! - The main window needs to update the helper window's position and size whenever it moves or resizes. + +use std::sync::OnceLock; +use wgpu::rwh::{HasWindowHandle, RawWindowHandle}; +use windows::Win32::Foundation::*; +use windows::Win32::Graphics::Dwm::*; +use windows::Win32::Graphics::Gdi::*; +use windows::Win32::System::LibraryLoader::GetModuleHandleW; +use windows::Win32::UI::Controls::MARGINS; +use windows::Win32::UI::HiDpi::*; +use windows::Win32::UI::WindowsAndMessaging::*; +use windows::core::PCWSTR; +use winit::window::Window; + +#[derive(Clone)] +pub(super) struct NativeWindowHandle { + main: HWND, + helper: HWND, + prev_window_message_handler: isize, +} +impl NativeWindowHandle { + pub(super) fn new(window: &dyn Window) -> NativeWindowHandle { + // Extract Win32 HWND from winit. + let hwnd = match window.window_handle().expect("No window handle").as_raw() { + RawWindowHandle::Win32(h) => HWND(h.hwnd.get() as *mut std::ffi::c_void), + _ => panic!("Not a Win32 window"), + }; + + // Register the invisible helper (resize ring) window class. + unsafe { ensure_helper_class() }; + + // Create the helper as a popup tool window that never activates. + // WS_EX_NOACTIVATE keeps focus on the main window; WS_EX_TOOLWINDOW hides it from Alt+Tab. + // https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles + let ex = WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW; + let style = WS_POPUP; + let helper = unsafe { + CreateWindowExW( + ex, + PCWSTR(HELPER_CLASS_NAME.encode_utf16().collect::>().as_ptr()), + PCWSTR::null(), + style, + 0, + 0, + 0, + 0, + None, + None, + HINSTANCE(std::ptr::null_mut()), + // Pass the main window's HWND to WM_NCCREATE so the helper can store it. + Some(&hwnd as *const _ as _), + ) + } + .expect("CreateWindowExW failed"); + + // Subclass the main window. + // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setwindowlongptra + let prev_window_message_handler = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, main_window_handle_message as isize) }; + if prev_window_message_handler == 0 { + let _ = unsafe { DestroyWindow(helper) }; + panic!("SetWindowLongPtrW failed"); + } + + let inner = NativeWindowHandle { + main: hwnd, + helper, + prev_window_message_handler, + }; + registry::insert(&inner); + + // Place the helper over the main window and show it without activation. + unsafe { position_helper(hwnd, helper) }; + let _ = unsafe { ShowWindow(helper, SW_SHOWNOACTIVATE) }; + + // DwmExtendFrameIntoClientArea is needed to keep native window frame (but no titlebar). + // https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea + // https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute + let mut boarder_size: u32 = 1; + let _ = unsafe { DwmGetWindowAttribute(hwnd, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &mut boarder_size as *mut _ as *mut _, size_of::() as u32) }; + let margins = MARGINS { + cxLeftWidth: 0, + cxRightWidth: 0, + cyBottomHeight: 0, + cyTopHeight: boarder_size as i32, + }; + let _ = unsafe { DwmExtendFrameIntoClientArea(hwnd, &margins) }; + + // Force window update + let _ = unsafe { SetWindowPos(hwnd, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) }; + + inner + } + + pub(super) fn destroy(&self) { + registry::remove_by_main(self.main); + + // Undo subclassing and destroy the helper window. + let _ = unsafe { SetWindowLongPtrW(self.main, GWLP_WNDPROC, self.prev_window_message_handler) }; + if self.helper.0 != std::ptr::null_mut() { + let _ = unsafe { DestroyWindow(self.helper) }; + } + } +} + +mod registry { + use std::cell::RefCell; + use windows::Win32::Foundation::HWND; + + use super::NativeWindowHandle; + + thread_local! { + static STORE: RefCell> = RefCell::new(Vec::new()); + } + + pub(super) fn find_by_main(main: HWND) -> Option { + STORE.with_borrow(|vec| vec.iter().find(|h| h.main == main).cloned()) + } + pub(super) fn remove_by_main(main: HWND) { + STORE.with_borrow_mut(|vec| { + vec.retain(|h| h.main != main); + }); + } + pub(super) fn insert(handle: &NativeWindowHandle) { + STORE.with_borrow_mut(|vec| { + vec.push(handle.clone()); + }); + } +} + +const HELPER_CLASS_NAME: &str = "Helper\0"; + +static HELPER_CLASS_LOCK: OnceLock = OnceLock::new(); +unsafe fn ensure_helper_class() { + // Register a window class for the invisible resize helper. + let _ = *HELPER_CLASS_LOCK.get_or_init(|| { + let class_name: Vec = HELPER_CLASS_NAME.encode_utf16().collect(); + let wc = WNDCLASSW { + style: CS_HREDRAW | CS_VREDRAW, + lpfnWndProc: Some(helper_window_handle_message), + hInstance: unsafe { GetModuleHandleW(None).unwrap().into() }, + hIcon: HICON::default(), + hCursor: unsafe { LoadCursorW(HINSTANCE(std::ptr::null_mut()), IDC_ARROW).unwrap() }, + // No painting; the ring is invisible. + hbrBackground: HBRUSH::default(), + lpszClassName: PCWSTR(class_name.as_ptr()), + ..Default::default() + }; + unsafe { RegisterClassW(&wc) } + }); +} + +// Main window message handler, called on the UI thread for every message the main window receives. +unsafe extern "system" fn main_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + if msg == WM_NCCALCSIZE && wparam.0 != 0 { + // When maximized, shrink to visible frame so content doesn't extend beyond it. + if unsafe { IsZoomed(hwnd).as_bool() } { + let params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) }; + + let dpi = unsafe { GetDpiForWindow(hwnd) }; + let size = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) }; + let pad = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; + let inset = (size + pad) as i32; + + params.rgrc[0].left += inset; + params.rgrc[0].top += inset; + params.rgrc[0].right -= inset; + params.rgrc[0].bottom -= inset; + } + + // Return 0 to to tell Windows to skip the default non-client area calculation and drawing. + return LRESULT(0); + } + + let Some(handle) = registry::find_by_main(hwnd) else { + return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; + }; + + match msg { + // Keep the invisible resize helper in sync with moves/resizes/visibility. + WM_MOVE | WM_MOVING | WM_SIZE | WM_SIZING | WM_WINDOWPOSCHANGED | WM_SHOWWINDOW => { + if msg == WM_SHOWWINDOW { + if wparam.0 == 0 { + let _ = unsafe { ShowWindow(handle.helper, SW_HIDE) }; + } else { + let _ = unsafe { ShowWindow(handle.helper, SW_SHOWNOACTIVATE) }; + } + } + unsafe { position_helper(hwnd, handle.helper) }; + } + + // If the main window is destroyed, destroy the helper too. + // Should only be needed if windows forcefully destroys the main window. + WM_DESTROY => { + let _ = unsafe { DestroyWindow(handle.helper) }; + } + + _ => {} + } + + // Ensure the previous window message handler is not null. + assert_ne!(handle.prev_window_message_handler, 0); + + // Call the previous window message handler, this is a standard subclassing pattern. + let prev_window_message_handler_fn_ptr: *const () = std::ptr::without_provenance(handle.prev_window_message_handler as usize); + let prev_window_message_handler_fn = unsafe { std::mem::transmute::<_, _>(prev_window_message_handler_fn_ptr) }; + return unsafe { CallWindowProcW(Some(prev_window_message_handler_fn), hwnd, msg, wparam, lparam) }; +} + +// Helper window message handler, called on the UI thread for every message the helper window receives. +unsafe extern "system" fn helper_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + match msg { + // Helper window creation, should be the first message that the helper window receives. + WM_NCCREATE => { + // Main window HWND is provided when creating the helper window with `CreateWindowExW` + // Save main window HWND in GWLP_USERDATA so we can extract it later + let crate_struct = lparam.0 as *const CREATESTRUCTW; + let create_param = unsafe { (*crate_struct).lpCreateParams as *const HWND }; + unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, (*create_param).0 as isize) }; + return LRESULT(1); + } + + // Invisible; no background erase. + WM_ERASEBKGND => return LRESULT(1), + + // Tell windows what resize areas we are hitting, this is used to decide what cursor to show. + WM_NCHITTEST => { + let ht = unsafe { calculate_hit(hwnd, lparam) }; + return LRESULT(ht as isize); + } + + // This starts the system's resize loop for the main window if a resize area is hit. + // Helper window button down translates to SC_SIZE | WMSZ_* on the main window. + WM_NCLBUTTONDOWN | WM_NCRBUTTONDOWN | WM_NCMBUTTONDOWN => { + // Extract the main window's HWND from GWLP_USERDATA that we saved earlier. + let main_ptr = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) } as *mut std::ffi::c_void; + let main = HWND(main_ptr); + if unsafe { IsWindow(main).as_bool() } { + let Some(wmsz) = (unsafe { calculate_resize_direction(hwnd, lparam) }) else { + return LRESULT(0); + }; + + // Ensure that the main window can receive WM_SYSCOMMAND. + let _ = unsafe { SetForegroundWindow(main) }; + + // Start sizing on the main window in the calculated direction. (SC_SIZE + WMSZ_*) + let _ = unsafe { PostMessageW(main, WM_SYSCOMMAND, WPARAM((SC_SIZE + wmsz) as usize), lparam) }; + } + return LRESULT(0); + } + + // Never activate the helper window, allows all inputs that don't hit the resize areas to pass through. + WM_MOUSEACTIVATE => return LRESULT(MA_NOACTIVATE as isize), + _ => {} + } + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } +} + +// Position the helper window to match the main window's location and size (plus the resize band size). +unsafe fn position_helper(main: HWND, helper: HWND) { + let mut r = RECT::default(); + let _ = unsafe { GetWindowRect(main, &mut r) }; + + const RESIZE_BAND_SIZE: i32 = 8; + let x = r.left - RESIZE_BAND_SIZE; + let y = r.top - RESIZE_BAND_SIZE; + let w = (r.right - r.left) + RESIZE_BAND_SIZE * 2; + let h = (r.bottom - r.top) + RESIZE_BAND_SIZE * 2; + + let _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE) }; +} + +unsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> u32 { + let x = (lparam.0 & 0xFFFF) as i16 as u32; + let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as u32; + + let mut r = RECT::default(); + let _ = unsafe { GetWindowRect(helper, &mut r) }; + + const RESIZE_BAND_THICKNESS: i32 = 8; + let on_top = y < (r.top + RESIZE_BAND_THICKNESS) as u32; + let on_right = x >= (r.right - RESIZE_BAND_THICKNESS) as u32; + let on_bottom = y >= (r.bottom - RESIZE_BAND_THICKNESS) as u32; + let on_left = x < (r.left + RESIZE_BAND_THICKNESS) as u32; + + match (on_top, on_right, on_bottom, on_left) { + (true, _, _, true) => HTTOPLEFT, + (true, true, _, _) => HTTOPRIGHT, + (_, true, true, _) => HTBOTTOMRIGHT, + (_, _, true, true) => HTBOTTOMLEFT, + (true, _, _, _) => HTTOP, + (_, true, _, _) => HTRIGHT, + (_, _, true, _) => HTBOTTOM, + (_, _, _, true) => HTLEFT, + _ => HTTRANSPARENT as u32, + } +} + +unsafe fn calculate_resize_direction(helper: HWND, lparam: LPARAM) -> Option { + match unsafe { calculate_hit(helper, lparam) } { + HTLEFT => Some(WMSZ_LEFT), + HTRIGHT => Some(WMSZ_RIGHT), + HTTOP => Some(WMSZ_TOP), + HTBOTTOM => Some(WMSZ_BOTTOM), + HTTOPLEFT => Some(WMSZ_TOPLEFT), + HTTOPRIGHT => Some(WMSZ_TOPRIGHT), + HTBOTTOMLEFT => Some(WMSZ_BOTTOMLEFT), + HTBOTTOMRIGHT => Some(WMSZ_BOTTOMRIGHT), + _ => None, + } +} From fec71c4e22670201199df0c58055f6154c7dbb48 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Mon, 20 Oct 2025 16:15:19 +0000 Subject: [PATCH 4/6] fix fmt --- desktop/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/lib.rs b/desktop/src/lib.rs index 0accce4e3c..9f1591675b 100644 --- a/desktop/src/lib.rs +++ b/desktop/src/lib.rs @@ -10,9 +10,9 @@ mod cef; mod cli; mod dirs; mod event; -mod window; mod persist; mod render; +mod window; mod gpu_context; From 7154cd6914d35817a1b3b56e84f60e36b0519e91 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Tue, 21 Oct 2025 11:01:32 +0200 Subject: [PATCH 5/6] remove windows file that was left --- desktop/src/window/windows.rs | 334 ---------------------------------- 1 file changed, 334 deletions(-) delete mode 100644 desktop/src/window/windows.rs diff --git a/desktop/src/window/windows.rs b/desktop/src/window/windows.rs deleted file mode 100644 index 849de302fe..0000000000 --- a/desktop/src/window/windows.rs +++ /dev/null @@ -1,334 +0,0 @@ -//! Implements a Windows-specific custom window frame (no titlebar, but native boarder, shadows and resize). -//! Look and feel should be similar to a standard window. -//! -//! Implementation notes: -//! - Windows that don't use standard decorations don't get native resize handles or shadows by default. -//! - We implement resize handles (outside the main window) by creating an invisible "helper" window that -//! is a little larger than the main window and positioned on top of it. The helper window does hit-testing -//! and triggers native resize operations on the main window when the user clicks and drags a resize area. -//! - The helper window is a invisible window that never activates, so it doesn't steal focus from the main window. -//! - The main window needs to update the helper window's position and size whenever it moves or resizes. - -use std::sync::OnceLock; -use wgpu::rwh::{HasWindowHandle, RawWindowHandle}; -use windows::Win32::Foundation::*; -use windows::Win32::Graphics::Dwm::*; -use windows::Win32::Graphics::Gdi::*; -use windows::Win32::System::LibraryLoader::GetModuleHandleW; -use windows::Win32::UI::Controls::MARGINS; -use windows::Win32::UI::HiDpi::*; -use windows::Win32::UI::WindowsAndMessaging::*; -use windows::core::PCWSTR; -use winit::window::Window; - -pub(super) struct WindowsNativeWindowHandle { - inner: WindowsNativeWindowHandleInner, -} -impl WindowsNativeWindowHandle { - pub(super) fn new(window: &dyn Window) -> Self { - let inner = WindowsNativeWindowHandleInner::new(window); - WindowsNativeWindowHandle { inner } - } -} -impl Drop for WindowsNativeWindowHandle { - fn drop(&mut self) { - self.inner.destroy(); - } -} - -#[derive(Clone)] -struct WindowsNativeWindowHandleInner { - main: HWND, - helper: HWND, - prev_window_message_handler: isize, -} -impl WindowsNativeWindowHandleInner { - fn new(window: &dyn Window) -> WindowsNativeWindowHandleInner { - // Extract Win32 HWND from winit. - let hwnd = match window.window_handle().expect("No window handle").as_raw() { - RawWindowHandle::Win32(h) => HWND(h.hwnd.get() as *mut std::ffi::c_void), - _ => panic!("Not a Win32 window"), - }; - - // Register the invisible helper (resize ring) window class. - unsafe { ensure_helper_class() }; - - // Create the helper as a popup tool window that never activates. - // WS_EX_NOACTIVATE keeps focus on the main window; WS_EX_TOOLWINDOW hides it from Alt+Tab. - // https://learn.microsoft.com/windows/win32/winmsg/extended-window-styles - let ex = WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW; - let style = WS_POPUP; - let helper = unsafe { - CreateWindowExW( - ex, - PCWSTR(HELPER_CLASS_NAME.encode_utf16().collect::>().as_ptr()), - PCWSTR::null(), - style, - 0, - 0, - 0, - 0, - None, - None, - HINSTANCE(std::ptr::null_mut()), - // Pass the main window's HWND to WM_NCCREATE so the helper can store it. - Some(&hwnd as *const _ as _), - ) - } - .expect("CreateWindowExW failed"); - - // Subclass the main window. - // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setwindowlongptra - let prev_window_message_handler = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, main_window_handle_message as isize) }; - if prev_window_message_handler == 0 { - let _ = unsafe { DestroyWindow(helper) }; - panic!("SetWindowLongPtrW failed"); - } - - let inner = WindowsNativeWindowHandleInner { - main: hwnd, - helper, - prev_window_message_handler, - }; - registry::insert(&inner); - - // Place the helper over the main window and show it without activation. - unsafe { position_helper(hwnd, helper) }; - let _ = unsafe { ShowWindow(helper, SW_SHOWNOACTIVATE) }; - - // DwmExtendFrameIntoClientArea is needed to keep native window frame (but no titlebar). - // https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea - // https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute - let mut boarder_size: u32 = 1; - let _ = unsafe { DwmGetWindowAttribute(hwnd, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &mut boarder_size as *mut _ as *mut _, size_of::() as u32) }; - let margins = MARGINS { - cxLeftWidth: 0, - cxRightWidth: 0, - cyBottomHeight: 0, - cyTopHeight: boarder_size as i32, - }; - let _ = unsafe { DwmExtendFrameIntoClientArea(hwnd, &margins) }; - - // Force window update - let _ = unsafe { SetWindowPos(hwnd, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) }; - - inner - } - - fn destroy(&self) { - registry::remove_by_main(self.main); - - // Undo subclassing and destroy the helper window. - let _ = unsafe { SetWindowLongPtrW(self.main, GWLP_WNDPROC, self.prev_window_message_handler) }; - if self.helper.0 != std::ptr::null_mut() { - let _ = unsafe { DestroyWindow(self.helper) }; - } - } -} - -mod registry { - use std::cell::RefCell; - use windows::Win32::Foundation::HWND; - - use crate::native_window::windows::WindowsNativeWindowHandleInner; - - thread_local! { - static STORE: RefCell> = RefCell::new(Vec::new()); - } - - pub(super) fn find_by_main(main: HWND) -> Option { - STORE.with_borrow(|vec| vec.iter().find(|h| h.main == main).cloned()) - } - pub(super) fn remove_by_main(main: HWND) { - STORE.with_borrow_mut(|vec| { - vec.retain(|h| h.main != main); - }); - } - pub(super) fn insert(handle: &WindowsNativeWindowHandleInner) { - STORE.with_borrow_mut(|vec| { - vec.push(handle.clone()); - }); - } -} - -const HELPER_CLASS_NAME: &str = "Helper\0"; - -static HELPER_CLASS_LOCK: OnceLock = OnceLock::new(); -unsafe fn ensure_helper_class() { - // Register a window class for the invisible resize helper. - let _ = *HELPER_CLASS_LOCK.get_or_init(|| { - let class_name: Vec = HELPER_CLASS_NAME.encode_utf16().collect(); - let wc = WNDCLASSW { - style: CS_HREDRAW | CS_VREDRAW, - lpfnWndProc: Some(helper_window_handle_message), - hInstance: unsafe { GetModuleHandleW(None).unwrap().into() }, - hIcon: HICON::default(), - hCursor: unsafe { LoadCursorW(HINSTANCE(std::ptr::null_mut()), IDC_ARROW).unwrap() }, - // No painting; the ring is invisible. - hbrBackground: HBRUSH::default(), - lpszClassName: PCWSTR(class_name.as_ptr()), - ..Default::default() - }; - unsafe { RegisterClassW(&wc) } - }); -} - -// Main window message handler, called on the UI thread for every message the main window receives. -unsafe extern "system" fn main_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { - if msg == WM_NCCALCSIZE && wparam.0 != 0 { - // When maximized, shrink to visible frame so content doesn't extend beyond it. - if unsafe { IsZoomed(hwnd).as_bool() } { - let params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) }; - - let dpi = unsafe { GetDpiForWindow(hwnd) }; - let size = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) }; - let pad = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) }; - let inset = (size + pad) as i32; - - params.rgrc[0].left += inset; - params.rgrc[0].top += inset; - params.rgrc[0].right -= inset; - params.rgrc[0].bottom -= inset; - } - - // Return 0 to to tell Windows to skip the default non-client area calculation and drawing. - return LRESULT(0); - } - - let Some(handle) = registry::find_by_main(hwnd) else { - return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; - }; - - match msg { - // Keep the invisible resize helper in sync with moves/resizes/visibility. - WM_MOVE | WM_MOVING | WM_SIZE | WM_SIZING | WM_WINDOWPOSCHANGED | WM_SHOWWINDOW => { - if msg == WM_SHOWWINDOW { - if wparam.0 == 0 { - let _ = unsafe { ShowWindow(handle.helper, SW_HIDE) }; - } else { - let _ = unsafe { ShowWindow(handle.helper, SW_SHOWNOACTIVATE) }; - } - } - unsafe { position_helper(hwnd, handle.helper) }; - } - - // If the main window is destroyed, destroy the helper too. - // Should only be needed if windows forcefully destroys the main window. - WM_DESTROY => { - let _ = unsafe { DestroyWindow(handle.helper) }; - } - - _ => {} - } - - // Ensure the previous window message handler is not null. - assert_ne!(handle.prev_window_message_handler, 0); - - // Call the previous window message handler, this is a standard subclassing pattern. - let prev_window_message_handler_fn_ptr: *const () = std::ptr::without_provenance(handle.prev_window_message_handler as usize); - let prev_window_message_handler_fn = unsafe { std::mem::transmute::<_, _>(prev_window_message_handler_fn_ptr) }; - return unsafe { CallWindowProcW(Some(prev_window_message_handler_fn), hwnd, msg, wparam, lparam) }; -} - -// Helper window message handler, called on the UI thread for every message the helper window receives. -unsafe extern "system" fn helper_window_handle_message(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { - match msg { - // Helper window creation, should be the first message that the helper window receives. - WM_NCCREATE => { - // Main window HWND is provided when creating the helper window with `CreateWindowExW` - // Save main window HWND in GWLP_USERDATA so we can extract it later - let crate_struct = lparam.0 as *const CREATESTRUCTW; - let create_param = unsafe { (*crate_struct).lpCreateParams as *const HWND }; - unsafe { SetWindowLongPtrW(hwnd, GWLP_USERDATA, (*create_param).0 as isize) }; - return LRESULT(1); - } - - // Invisible; no background erase. - WM_ERASEBKGND => return LRESULT(1), - - // Tell windows what resize areas we are hitting, this is used to decide what cursor to show. - WM_NCHITTEST => { - let ht = unsafe { calculate_hit(hwnd, lparam) }; - return LRESULT(ht as isize); - } - - // This starts the system's resize loop for the main window if a resize area is hit. - // Helper window button down translates to SC_SIZE | WMSZ_* on the main window. - WM_NCLBUTTONDOWN | WM_NCRBUTTONDOWN | WM_NCMBUTTONDOWN => { - // Extract the main window's HWND from GWLP_USERDATA that we saved earlier. - let main_ptr = unsafe { GetWindowLongPtrW(hwnd, GWLP_USERDATA) } as *mut std::ffi::c_void; - let main = HWND(main_ptr); - if unsafe { IsWindow(main).as_bool() } { - let Some(wmsz) = (unsafe { calculate_resize_direction(hwnd, lparam) }) else { - return LRESULT(0); - }; - - // Ensure that the main window can receive WM_SYSCOMMAND. - let _ = unsafe { SetForegroundWindow(main) }; - - // Start sizing on the main window in the calculated direction. (SC_SIZE + WMSZ_*) - let _ = unsafe { PostMessageW(main, WM_SYSCOMMAND, WPARAM((SC_SIZE + wmsz) as usize), lparam) }; - } - return LRESULT(0); - } - - // Never activate the helper window, allows all inputs that don't hit the resize areas to pass through. - WM_MOUSEACTIVATE => return LRESULT(MA_NOACTIVATE as isize), - _ => {} - } - unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } -} - -// Position the helper window to match the main window's location and size (plus the resize band size). -unsafe fn position_helper(main: HWND, helper: HWND) { - let mut r = RECT::default(); - let _ = unsafe { GetWindowRect(main, &mut r) }; - - const RESIZE_BAND_SIZE: i32 = 8; - let x = r.left - RESIZE_BAND_SIZE; - let y = r.top - RESIZE_BAND_SIZE; - let w = (r.right - r.left) + RESIZE_BAND_SIZE * 2; - let h = (r.bottom - r.top) + RESIZE_BAND_SIZE * 2; - - let _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE) }; -} - -unsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> u32 { - let x = (lparam.0 & 0xFFFF) as i16 as u32; - let y = ((lparam.0 >> 16) & 0xFFFF) as i16 as u32; - - let mut r = RECT::default(); - let _ = unsafe { GetWindowRect(helper, &mut r) }; - - const RESIZE_BAND_THICKNESS: i32 = 8; - let on_top = y < (r.top + RESIZE_BAND_THICKNESS) as u32; - let on_right = x >= (r.right - RESIZE_BAND_THICKNESS) as u32; - let on_bottom = y >= (r.bottom - RESIZE_BAND_THICKNESS) as u32; - let on_left = x < (r.left + RESIZE_BAND_THICKNESS) as u32; - - match (on_top, on_right, on_bottom, on_left) { - (true, _, _, true) => HTTOPLEFT, - (true, true, _, _) => HTTOPRIGHT, - (_, true, true, _) => HTBOTTOMRIGHT, - (_, _, true, true) => HTBOTTOMLEFT, - (true, _, _, _) => HTTOP, - (_, true, _, _) => HTRIGHT, - (_, _, true, _) => HTBOTTOM, - (_, _, _, true) => HTLEFT, - _ => HTTRANSPARENT as u32, - } -} - -unsafe fn calculate_resize_direction(helper: HWND, lparam: LPARAM) -> Option { - match unsafe { calculate_hit(helper, lparam) } { - HTLEFT => Some(WMSZ_LEFT), - HTRIGHT => Some(WMSZ_RIGHT), - HTTOP => Some(WMSZ_TOP), - HTBOTTOM => Some(WMSZ_BOTTOM), - HTTOPLEFT => Some(WMSZ_TOPLEFT), - HTTOPRIGHT => Some(WMSZ_TOPRIGHT), - HTBOTTOMLEFT => Some(WMSZ_BOTTOMLEFT), - HTBOTTOMRIGHT => Some(WMSZ_BOTTOMRIGHT), - _ => None, - } -} From b21e94ce5929802e59abae3a8deccd97685cd2f3 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Tue, 21 Oct 2025 19:08:04 +0200 Subject: [PATCH 6/6] use self --- desktop/src/render/graphics_state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/src/render/graphics_state.rs b/desktop/src/render/graphics_state.rs index 31a95173cd..d6797f937e 100644 --- a/desktop/src/render/graphics_state.rs +++ b/desktop/src/render/graphics_state.rs @@ -23,7 +23,7 @@ pub(crate) struct GraphicsState { } impl GraphicsState { - pub(crate) fn new(window: &Window, context: WgpuContext) -> GraphicsState { + pub(crate) fn new(window: &Window, context: WgpuContext) -> Self { let size = window.surface_size(); let surface = window.create_surface(context.instance.clone()); @@ -163,7 +163,7 @@ impl GraphicsState { let wgpu_executor = WgpuExecutor::with_context(context.clone()).expect("Failed to create WgpuExecutor"); - GraphicsState { + Self { surface, context, executor: wgpu_executor,