Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ windows = { version = "0.58.0", features = [
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_System_LibraryLoader",
"Win32_System_Com",
"Win32_UI_Controls",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_HiDpi",
"Win32_UI_Shell",
] }

# macOS-specific dependencies
Expand Down
6 changes: 3 additions & 3 deletions desktop/platform/win/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ fn main() {
// TODO: Replace with actual version
res.set_version_info(winres::VersionInfo::FILEVERSION, {
const MAJOR: u64 = 0;
const MINOR: u64 = 999;
const MINOR: u64 = 0;
const PATCH: u64 = 0;
const RELEASE: u64 = 0;
(MAJOR << 48) | (MINOR << 32) | (PATCH << 16) | RELEASE
});
res.set("FileVersion", "0.999.0.0");
res.set("ProductVersion", "0.999.0.0");
res.set("FileVersion", "0.0.0.0");
res.set("ProductVersion", "0.0.0.0");

res.set("OriginalFilename", "Graphite.exe");

Expand Down
4 changes: 4 additions & 0 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ impl ApplicationHandler for App {
let render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone());
self.render_state = Some(render_state);

if let Some(window) = &self.window.as_ref() {
window.show();
}

self.resize();

self.desktop_wrapper.init(self.wgpu_context.clone());
Expand Down
2 changes: 1 addition & 1 deletion desktop/src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub(crate) const APP_NAME: &str = "Graphite";
#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "windows"))]
pub(crate) const APP_ID: &str = "art.graphite.Graphite";

pub(crate) const APP_DIRECTORY_NAME: &str = "graphite";
Expand Down
6 changes: 6 additions & 0 deletions desktop/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl Window {
.with_min_surface_size(winit::dpi::LogicalSize::new(400, 300))
.with_surface_size(winit::dpi::LogicalSize::new(1200, 800))
.with_resizable(true)
.with_visible(false)
.with_theme(Some(winit::window::Theme::Dark));

attributes = native::NativeWindowImpl::configure(attributes, event_loop);
Expand All @@ -67,6 +68,11 @@ impl Window {
}
}

pub(crate) fn show(&self) {
self.winit_window.set_visible(true);
self.winit_window.focus_window();
}

pub(crate) fn request_redraw(&self) {
self.winit_window.request_redraw();
}
Expand Down
20 changes: 13 additions & 7 deletions desktop/src/window/win.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
use winit::dpi::PhysicalSize;
use windows::Win32::System::Com::{COINIT_APARTMENTTHREADED, CoInitializeEx};
use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
use windows::core::HSTRING;
use winit::event_loop::ActiveEventLoop;
use winit::icon::Icon;
use winit::platform::windows::{WinIcon, WindowAttributesWindows};
use winit::window::{Window, WindowAttributes};

use crate::consts::APP_ID;
use crate::event::AppEventScheduler;

pub(super) struct NativeWindowImpl {
native_handle: native_handle::NativeWindowHandle,
}

impl super::NativeWindow for NativeWindowImpl {
fn init() {
let app_id = HSTRING::from(APP_ID);
unsafe {
let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok();
SetCurrentProcessExplicitAppUserModelID(&app_id).ok();
}
}

fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
let icon = WinIcon::from_resource(1, Some(PhysicalSize::new(256, 256))).ok().map(|icon| Icon(std::sync::Arc::new(icon)));
let win_window = WindowAttributesWindows::default().with_taskbar_icon(icon);
let icon = WinIcon::from_resource(1, None).ok().map(|icon| Icon(std::sync::Arc::new(icon)));
attributes.with_window_icon(icon).with_platform_attributes(Box::new(win_window))
attributes
}

fn new(window: &dyn Window, _app_event_scheduler: AppEventScheduler) -> Self {
Expand Down
50 changes: 32 additions & 18 deletions desktop/src/window/win/native_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub(super) struct NativeWindowHandle {
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() {
let main = 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"),
};
Expand All @@ -57,55 +57,69 @@ impl NativeWindowHandle {
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 _),
Some(&main 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) };
let prev_window_message_handler = unsafe { SetWindowLongPtrW(main, 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,
let native_handle = NativeWindowHandle {
main,
helper,
prev_window_message_handler,
};
registry::insert(&inner);
registry::insert(&native_handle);

// Place the helper over the main window and show it without activation.
unsafe { position_helper(hwnd, helper) };
unsafe { position_helper(main, 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::<u32>() as u32) };
let _ = unsafe { DwmGetWindowAttribute(main, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &mut boarder_size as *mut _ as *mut _, size_of::<u32>() as u32) };
let margins = MARGINS {
cxLeftWidth: 0,
cxRightWidth: 0,
cyBottomHeight: 0,
cyTopHeight: boarder_size as i32,
};
let _ = unsafe { DwmExtendFrameIntoClientArea(hwnd, &margins) };
let _ = unsafe { DwmExtendFrameIntoClientArea(main, &margins) };

let hinst = unsafe { GetModuleHandleW(None) }.unwrap();

// Set taskbar icon
if let Ok(big) = unsafe { LoadImageW(hinst, PCWSTR(1usize as *const u16), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED) } {
unsafe { SetClassLongPtrW(main, GCLP_HICON, big.0 as isize) };
unsafe { SendMessageW(main, WM_SETICON, WPARAM(ICON_BIG as usize), LPARAM(big.0 as isize)) };
}

// Set window icon
if let Ok(small) = unsafe { LoadImageW(hinst, PCWSTR(1usize as *const u16), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED) } {
unsafe { SetClassLongPtrW(main, GCLP_HICONSM, small.0 as isize) };
unsafe { SendMessageW(main, WM_SETICON, WPARAM(ICON_SMALL as usize), LPARAM(small.0 as isize)) };
}

// Force window update
let _ = unsafe { SetWindowPos(hwnd, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) };
let _ = unsafe { SetWindowPos(main, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) };

inner
native_handle
}

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() {
if !self.helper.is_invalid() {
let _ = unsafe { DestroyWindow(self.helper) };
}
}
Expand Down Expand Up @@ -264,16 +278,17 @@ unsafe extern "system" fn helper_window_handle_message(hwnd: HWND, msg: u32, wpa
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
}

const RESIZE_BAND_THICKNESS: i32 = 8;

// 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 x = r.left - RESIZE_BAND_THICKNESS;
let y = r.top - RESIZE_BAND_THICKNESS;
let w = (r.right - r.left) + RESIZE_BAND_THICKNESS * 2;
let h = (r.bottom - r.top) + RESIZE_BAND_THICKNESS * 2;

let _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE) };
}
Expand All @@ -285,7 +300,6 @@ unsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> 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;
Expand Down