diff --git a/Cargo.toml b/Cargo.toml index 05a09fa2..46f09018 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [ "NSPasteboard", "NSResponder", "NSRunningApplication", + "NSScreen", "NSTrackingArea", "NSView", "NSWindow", diff --git a/src/macos/window.rs b/src/macos/window.rs index 4989e7f3..7c85c763 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -22,7 +22,7 @@ use raw_window_handle::{ }; use crate::{ - Event, EventStatus, MouseCursor, Size, WindowHandler, WindowInfo, WindowOpenOptions, + Event, EventStatus, MouseCursor, Point, Size, WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, }; @@ -364,6 +364,23 @@ impl<'a> Window<'a> { } } + pub fn set_position(&mut self, position: Point) { + // NOTE: macOS uses a coordinate system where (0,0) is at the bottom-left of the screen. + // We need to convert from top-left coordinates to bottom-left coordinates. + if let Some(ns_window) = self.inner.ns_window.get() { + if let Some(screen) = ns_window.screen() { + let screen_frame = screen.frame(); + let window_frame = ns_window.frame(); + + let y_bottom_left = + screen_frame.size.height - position.y.round() - window_frame.size.height; + let origin = NSPoint::new(position.x.round(), y_bottom_left); + + ns_window.setFrameOrigin(origin); + } + } + } + pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) { todo!() } diff --git a/src/win/window.rs b/src/win/window.rs index 8310bc38..cebc835e 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -13,15 +13,16 @@ use windows_sys::Win32::{ }, WindowsAndMessaging::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, - GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, SetCursor, SetTimer, - SetWindowLongPtrW, SetWindowPos, TranslateMessage, GWLP_USERDATA, HTCLIENT, MSG, - SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOZORDER, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, - WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, - WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, - WM_NCDESTROY, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, - WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, - WM_XBUTTONUP, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, - WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, + GetMessageW, GetSystemMetrics, GetWindowLongPtrW, LoadCursorW, PostMessageW, SetCursor, + SetTimer, SetWindowLongPtrW, SetWindowPos, TranslateMessage, GWLP_USERDATA, HTCLIENT, + MSG, SM_CXSCREEN, SM_CYSCREEN, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, + WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, WM_INPUTLANGCHANGE, + WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, + WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, WM_RBUTTONDOWN, + WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, + WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WS_CAPTION, WS_CHILD, + WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, + WS_VISIBLE, }, }, }; @@ -45,8 +46,8 @@ const BV_WINDOW_MUST_CLOSE: u32 = WM_USER + 1; use crate::win::hook::{self, KeyboardHookHandle}; use crate::{ - Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, + Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, Point, ScrollDelta, Size, + WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, }; use super::cursor::cursor_to_lpcwstr; @@ -562,6 +563,21 @@ impl WindowState { ) }; } + WindowTask::SetPosition(position) => { + let window_info = self.window_info(); + let physical_pos = position.to_physical(&window_info); + unsafe { + SetWindowPos( + self.hwnd, + self.hwnd, + physical_pos.x, + physical_pos.y, + 0, + 0, + SWP_NOZORDER | SWP_NOSIZE, + ) + }; + } } } } @@ -573,6 +589,9 @@ pub(super) enum WindowTask { /// Resize the window to the given size. The size is in logical pixels. DPI scaling is applied /// automatically. Resize(Size), + /// Set the position of the window. The position is in logical pixels. DPI scaling is applied + /// automatically. + SetPosition(Point), } pub struct Window<'a> { @@ -668,13 +687,24 @@ impl Window<'_> { AdjustWindowRectEx(&mut rect, flags, FALSE, 0); } + let (x, y) = if !parented { + let screen_width = GetSystemMetrics(SM_CXSCREEN); + let screen_height = GetSystemMetrics(SM_CYSCREEN); + ( + (screen_width - (rect.right - rect.left)) / 2, + (screen_height - (rect.bottom - rect.top)) / 2, + ) + } else { + (0, 0) + }; + let hwnd = CreateWindowExW( 0, window_class.as_atom_ptr(), title.as_ptr(), flags, - 0, - 0, + x, + y, rect.right - rect.left, rect.bottom - rect.top, parent as *mut _, @@ -771,20 +801,29 @@ impl Window<'_> { SetTimer(hwnd, WIN_FRAME_TIMER, 15, None); if let Some(mut new_rect) = new_rect { - // Convert this desired"client rectangle" size to the actual "window rectangle" - // size (Because of course you have to do that). - AdjustWindowRectEx(&mut new_rect, flags, 0, 0); + let (x, y) = if !parented { + let screen_width = GetSystemMetrics(SM_CXSCREEN); + let screen_height = GetSystemMetrics(SM_CYSCREEN); + AdjustWindowRectEx(&mut new_rect, flags, FALSE, 0); + ( + (screen_width - (new_rect.right - new_rect.left)) / 2, + (screen_height - (new_rect.bottom - new_rect.top)) / 2, + ) + } else { + AdjustWindowRectEx(&mut new_rect, flags, FALSE, 0); + (new_rect.left, new_rect.top) + }; // Windows makes us resize the window manually. This will trigger another `WM_SIZE` event, // which we can then send the user the new scale factor. SetWindowPos( hwnd, hwnd, - new_rect.left, - new_rect.top, + x, + y, new_rect.right - new_rect.left, new_rect.bottom - new_rect.top, - SWP_NOZORDER | SWP_NOMOVE, + SWP_NOZORDER, ); } @@ -816,6 +855,13 @@ impl Window<'_> { self.state.deferred_tasks.borrow_mut().push_back(task); } + pub fn set_position(&mut self, position: Point) { + // To avoid reentrant event handler calls we'll defer the actual positioning until after the + // event has been handled + let task = WindowTask::SetPosition(position); + self.state.deferred_tasks.borrow_mut().push_back(task); + } + pub fn set_mouse_cursor(&mut self, mouse_cursor: MouseCursor) { self.state.cursor_icon.set(mouse_cursor); unsafe { diff --git a/src/window.rs b/src/window.rs index 25b53d1e..edfde8a5 100644 --- a/src/window.rs +++ b/src/window.rs @@ -6,7 +6,7 @@ use raw_window_handle::{ use crate::event::{Event, EventStatus}; use crate::window_open_options::WindowOpenOptions; -use crate::{MouseCursor, Size}; +use crate::{MouseCursor, Point, Size}; #[cfg(target_os = "macos")] use crate::macos as platform; @@ -98,6 +98,12 @@ impl<'a> Window<'a> { self.window.resize(size); } + /// Set the position of the window. The position is always in logical pixels. DPI scaling will + /// automatically be accounted for. + pub fn set_position(&mut self, position: Point) { + self.window.set_position(position); + } + pub fn set_mouse_cursor(&mut self, cursor: MouseCursor) { self.window.set_mouse_cursor(cursor); } diff --git a/src/x11/window.rs b/src/x11/window.rs index 6414486d..88bbc10f 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -21,7 +21,7 @@ use x11rb::wrapper::ConnectionExt as _; use super::XcbConnection; use crate::{ - Event, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, + Event, MouseCursor, Point, Size, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, }; @@ -195,6 +195,19 @@ impl<'a> Window<'a> { let window_info = WindowInfo::from_logical_size(options.size, scaling); + let width = (options.size.width * scaling).round() as u32; + let height = (options.size.height * scaling).round() as u32; + + let (x, y) = if parent.is_none() { + let screen_width = screen.width_in_pixels; + let screen_height = screen.height_in_pixels; + let x = (screen_width as i32 - width as i32) / 2; + let y = (screen_height as i32 - height as i32) / 2; + (x.max(0) as i16, y.max(0) as i16) + } else { + (0, 0) + }; + #[cfg(feature = "opengl")] let visual_info = WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; @@ -207,11 +220,11 @@ impl<'a> Window<'a> { visual_info.visual_depth, window_id, parent_id, - 0, // x coordinate of the new window - 0, // y coordinate of the new window - window_info.physical_size().width as u16, // window width - window_info.physical_size().height as u16, // window height - 0, // window border + x, + y, + width as u16, + height as u16, + 0, // window border WindowClass::INPUT_OUTPUT, visual_info.visual_id, &CreateWindowAux::new() @@ -344,6 +357,17 @@ impl<'a> Window<'a> { // and notify the window handler about it } + pub fn set_position(&mut self, position: Point) { + let window_info = self.inner.window_info; + let physical_pos = position.to_physical(&window_info); + + let _ = self.inner.xcb_connection.conn.configure_window( + self.inner.window_id, + &ConfigureWindowAux::new().x(physical_pos.x).y(physical_pos.y), + ); + let _ = self.inner.xcb_connection.conn.flush(); + } + #[cfg(feature = "opengl")] pub fn gl_context(&self) -> Option<&crate::gl::GlContext> { self.inner.gl_context.as_ref()