From 4f6fd44c6ce9d5020360257140aee351813664b4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 24 Dec 2023 10:12:09 +0100 Subject: [PATCH] macOS: Clean up coordinate system calculations (#3302) * Clean up macOS and iOS monitor code a bit * Clean up window size methods Use `setContentSize`, `setContentMinSize`, `setContentMaxSize` and `contentRectForFrameRect` to let the windowing system figure out the required scaling, instead of us doing it manually. * Use a flipped NSView coordinate system * Clean up window position methods --- clippy.toml | 1 + examples/fullscreen.rs | 6 +- examples/multithreaded.rs | 44 +++---- src/platform_impl/ios/monitor.rs | 25 ++-- src/platform_impl/macos/monitor.rs | 81 +++++++------ src/platform_impl/macos/util.rs | 23 +--- src/platform_impl/macos/view.rs | 39 +++---- src/platform_impl/macos/window.rs | 130 +++++++++------------ src/platform_impl/macos/window_delegate.rs | 41 ++++--- 9 files changed, 173 insertions(+), 217 deletions(-) diff --git a/clippy.toml b/clippy.toml index 0b6f05a2..e217efd2 100644 --- a/clippy.toml +++ b/clippy.toml @@ -11,4 +11,5 @@ disallowed-methods = [ { path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, { path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." }, + { path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" }, ] diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index fca98401..b82ca07f 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,7 +1,7 @@ #![allow(clippy::single_match)] use simple_logger::SimpleLogger; -use winit::dpi::PhysicalSize; +use winit::dpi::LogicalSize; use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event_loop::EventLoop; use winit::keyboard::{Key, NamedKey}; @@ -126,7 +126,7 @@ fn main() -> Result<(), impl std::error::Error> { "i" => { with_min_size = !with_min_size; let min_size = if with_min_size { - Some(PhysicalSize::new(100, 100)) + Some(LogicalSize::new(100, 100)) } else { None }; @@ -139,7 +139,7 @@ fn main() -> Result<(), impl std::error::Error> { "a" => { with_max_size = !with_max_size; let max_size = if with_max_size { - Some(PhysicalSize::new(200, 200)) + Some(LogicalSize::new(200, 200)) } else { None }; diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 2b02d8fd..b4660459 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -96,21 +96,13 @@ fn main() -> Result<(), impl std::error::Error> { )), (false, _) => None, }), - "l" if state => { - if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) - { - println!("error: {err}"); - } - } - "g" if state => { - if let Err(err) = - window.set_cursor_grab(CursorGrabMode::Confined) - { - println!("error: {err}"); - } - } - "g" | "l" if !state => { - if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) { + ch @ ("g" | "l") => { + let mode = match (ch, state) { + ("l", true) => CursorGrabMode::Locked, + ("g", true) => CursorGrabMode::Confined, + (_, _) => CursorGrabMode::None, + }; + if let Err(err) = window.set_cursor_grab(mode) { println!("error: {err}"); } } @@ -123,10 +115,6 @@ fn main() -> Result<(), impl std::error::Error> { println!("-> inner_size : {:?}", window.inner_size()); println!("-> fullscreen : {:?}", window.fullscreen()); } - "l" => window.set_min_inner_size(match state { - true => Some(WINDOW_SIZE), - false => None, - }), "m" => window.set_maximized(state), "p" => window.set_outer_position({ let mut position = window.outer_position().unwrap(); @@ -140,12 +128,26 @@ fn main() -> Result<(), impl std::error::Error> { "s" => { let _ = window.request_inner_size(match state { true => PhysicalSize::new( - WINDOW_SIZE.width + 100, - WINDOW_SIZE.height + 100, + WINDOW_SIZE.width + 50, + WINDOW_SIZE.height + 50, ), false => WINDOW_SIZE, }); } + "k" => window.set_min_inner_size(match state { + true => Some(PhysicalSize::new( + WINDOW_SIZE.width - 100, + WINDOW_SIZE.height - 100, + )), + false => None, + }), + "o" => window.set_max_inner_size(match state { + true => Some(PhysicalSize::new( + WINDOW_SIZE.width + 100, + WINDOW_SIZE.height + 100, + )), + false => None, + }), "w" => { if let Size::Physical(size) = WINDOW_SIZE.into() { window diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 45bace35..2621e8b2 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -135,24 +135,13 @@ impl Ord for MonitorHandle { impl fmt::Debug for MonitorHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Do this using the proper fmt API - #[derive(Debug)] - #[allow(dead_code)] - struct MonitorHandle { - name: Option, - size: PhysicalSize, - position: PhysicalPosition, - scale_factor: f64, - } - - let monitor_id_proxy = MonitorHandle { - name: self.name(), - size: self.size(), - position: self.position(), - scale_factor: self.scale_factor(), - }; - - monitor_id_proxy.fmt(f) + f.debug_struct("MonitorHandle") + .field("name", &self.name()) + .field("size", &self.size()) + .field("position", &self.position()) + .field("scale_factor", &self.scale_factor()) + .field("refresh_rate_millihertz", &self.refresh_rate_millihertz()) + .finish_non_exhaustive() } } diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index ec4c3303..cabbc240 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -11,17 +11,17 @@ use core_graphics::display::{ CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayCopyDisplayMode, }; use icrate::AppKit::NSScreen; -use icrate::Foundation::{ns_string, MainThreadMarker, NSNumber}; +use icrate::Foundation::{ns_string, MainThreadMarker, NSNumber, NSPoint, NSRect}; use objc2::{rc::Id, runtime::AnyObject}; use super::ffi; -use crate::dpi::{PhysicalPosition, PhysicalSize}; +use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; #[derive(Clone)] pub struct VideoMode { - pub(crate) size: (u32, u32), - pub(crate) bit_depth: u16, - pub(crate) refresh_rate_millihertz: u32, + size: PhysicalSize, + bit_depth: u16, + refresh_rate_millihertz: u32, pub(crate) monitor: MonitorHandle, pub(crate) native_mode: NativeDisplayMode, } @@ -80,7 +80,7 @@ impl Clone for NativeDisplayMode { impl VideoMode { pub fn size(&self) -> PhysicalSize { - self.size.into() + self.size } pub fn bit_depth(&self) -> u16 { @@ -154,26 +154,14 @@ pub fn primary_monitor() -> MonitorHandle { impl fmt::Debug for MonitorHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Do this using the proper fmt API - #[derive(Debug)] - #[allow(dead_code)] - struct MonitorHandle { - name: Option, - native_identifier: u32, - size: PhysicalSize, - position: PhysicalPosition, - scale_factor: f64, - } - - let monitor_id_proxy = MonitorHandle { - name: self.name(), - native_identifier: self.native_identifier(), - size: self.size(), - position: self.position(), - scale_factor: self.scale_factor(), - }; - - monitor_id_proxy.fmt(f) + f.debug_struct("MonitorHandle") + .field("name", &self.name()) + .field("native_identifier", &self.native_identifier()) + .field("size", &self.size()) + .field("position", &self.position()) + .field("scale_factor", &self.scale_factor()) + .field("refresh_rate_millihertz", &self.refresh_rate_millihertz()) + .finish_non_exhaustive() } } @@ -182,6 +170,8 @@ impl MonitorHandle { MonitorHandle(id) } + // TODO: Be smarter about this: + // pub fn name(&self) -> Option { let MonitorHandle(display_id) = *self; let screen_num = CGDisplay::new(display_id).model_number(); @@ -203,11 +193,12 @@ impl MonitorHandle { #[inline] pub fn position(&self) -> PhysicalPosition { + // This is already in screen coordinates. If we were using `NSScreen`, + // then a conversion would've been needed: + // flip_window_screen_coordinates(self.ns_screen(mtm)?.frame()) let bounds = unsafe { CGDisplayBounds(self.native_identifier()) }; - PhysicalPosition::from_logical::<_, f64>( - (bounds.origin.x as f64, bounds.origin.y as f64), - self.scale_factor(), - ) + let position = LogicalPosition::new(bounds.origin.x, bounds.origin.y); + position.to_physical(self.scale_factor()) } pub fn scale_factor(&self) -> f64 { @@ -268,13 +259,12 @@ impl MonitorHandle { }; modes.into_iter().map(move |mode| { - let cg_refresh_rate_millihertz = - ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; + let cg_refresh_rate_hertz = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64; // CGDisplayModeGetRefreshRate returns 0.0 for any display that // isn't a CRT - let refresh_rate_millihertz = if cg_refresh_rate_millihertz > 0 { - (cg_refresh_rate_millihertz * 1000) as u32 + let refresh_rate_millihertz = if cg_refresh_rate_hertz > 0 { + (cg_refresh_rate_hertz * 1000) as u32 } else { refresh_rate_millihertz }; @@ -293,7 +283,7 @@ impl MonitorHandle { }; VideoMode { - size: ( + size: PhysicalSize::new( ffi::CGDisplayModeGetPixelWidth(mode) as u32, ffi::CGDisplayModeGetPixelHeight(mode) as u32, ), @@ -339,3 +329,24 @@ pub(crate) fn get_display_id(screen: &NSScreen) -> u32 { obj.as_u32() }) } + +/// Core graphics screen coordinates are relative to the top-left corner of +/// the so-called "main" display, with y increasing downwards - which is +/// exactly what we want in Winit. +/// +/// However, `NSWindow` and `NSScreen` changes these coordinates to: +/// 1. Be relative to the bottom-left corner of the "main" screen. +/// 2. Be relative to the bottom-left corner of the window/screen itself. +/// 3. Have y increasing upwards. +/// +/// This conversion happens to be symmetric, so we only need this one function +/// to convert between the two coordinate systems. +pub(crate) fn flip_window_screen_coordinates(frame: NSRect) -> NSPoint { + // It is intentional that we use `CGMainDisplayID` (as opposed to + // `NSScreen::mainScreen`), because that's what the screen coordinates + // are relative to, no matter which display the window is currently on. + let main_screen_height = CGDisplay::main().bounds().size.height; + + let y = main_screen_height - frame.size.height - frame.origin.y; + NSPoint::new(frame.origin.x, y) +} diff --git a/src/platform_impl/macos/util.rs b/src/platform_impl/macos/util.rs index 2447a5dc..c90d737d 100644 --- a/src/platform_impl/macos/util.rs +++ b/src/platform_impl/macos/util.rs @@ -1,7 +1,4 @@ -use core_graphics::display::CGDisplay; -use icrate::Foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger}; - -use crate::dpi::LogicalPosition; +use icrate::Foundation::{NSNotFound, NSRange, NSUInteger}; // Replace with `!` once stable #[derive(Debug)] @@ -40,21 +37,3 @@ impl Drop for TraceGuard { trace!(target: self.module_path, "Completed `{}`", self.called_from_fn); } } - -// For consistency with other platforms, this will... -// 1. translate the bottom-left window corner into the top-left window corner -// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one -#[allow(clippy::unnecessary_cast)] -pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { - CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64 -} - -/// Converts from winit screen-coordinates to macOS screen-coordinates. -/// Winit: top-left is (0, 0) and y increasing downwards -/// macOS: bottom-left is (0, 0) and y increasing upwards -pub fn window_position(position: LogicalPosition) -> NSPoint { - NSPoint::new( - position.x as CGFloat, - CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat, - ) -} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 36baf160..7009e31f 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -123,8 +123,8 @@ fn get_left_modifier_code(key: &Key) -> KeyCode { #[derive(Debug, Default)] pub struct ViewState { cursor_state: RefCell, - ime_position: Cell>, - ime_size: Cell>, + ime_position: Cell, + ime_size: Cell, modifiers: Cell, phys_modifiers: RefCell>, tracking_rect: Cell>, @@ -162,6 +162,12 @@ declare_class!( } unsafe impl WinitView { + #[method(isFlipped)] + fn is_flipped(&self) -> bool { + // `winit` uses the upper-left corner as the origin. + true + } + #[method(viewDidMoveToWindow)] fn view_did_move_to_window(&self) { trace_scope!("viewDidMoveToWindow"); @@ -365,14 +371,9 @@ declare_class!( _actual_range: *mut NSRange, ) -> NSRect { trace_scope!("firstRectForCharacterRange:actualRange:"); - let window = self.window(); - let content_rect = window.contentRectForFrameRect(window.frame()); - let base_x = content_rect.origin.x as f64; - let base_y = (content_rect.origin.y + content_rect.size.height) as f64; - let x = base_x + self.ivars().ime_position.get().x; - let y = base_y - self.ivars().ime_position.get().y; - let LogicalSize { width, height } = self.ivars().ime_size.get(); - NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height)) + let rect = dbg!(NSRect::new(self.ivars().ime_position.get(), self.ivars().ime_size.get())); + // Return value is expected to be in screen coordinates, so we need a conversion here + unsafe { self.window().convertRectToScreen(self.convertRect_toView(rect, None)) } } #[method(insertText:replacementRange:)] @@ -876,11 +877,7 @@ impl WinitView { } } - pub(super) fn set_ime_cursor_area( - &self, - position: LogicalPosition, - size: LogicalSize, - ) { + pub(super) fn set_ime_cursor_area(&self, position: NSPoint, size: NSSize) { self.ivars().ime_position.set(position); self.ivars().ime_size.set(size); let input_context = self.inputContext().expect("input context"); @@ -1026,12 +1023,12 @@ impl WinitView { fn mouse_motion(&self, event: &NSEvent) { let window_point = unsafe { event.locationInWindow() }; let view_point = self.convertPoint_fromView(window_point, None); - let view_rect = self.frame(); + let frame = self.frame(); if view_point.x.is_sign_negative() || view_point.y.is_sign_negative() - || view_point.x > view_rect.size.width - || view_point.y > view_rect.size.height + || view_point.x > frame.size.width + || view_point.y > frame.size.height { let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() }; if mouse_buttons_down == 0 { @@ -1040,15 +1037,13 @@ impl WinitView { } } - let x = view_point.x as f64; - let y = view_rect.size.height as f64 - view_point.y as f64; - let logical_position = LogicalPosition::new(x, y); + let view_point = LogicalPosition::new(view_point.x, view_point.y); self.update_modifiers(event, false); self.queue_event(WindowEvent::CursorMoved { device_id: DEVICE_ID, - position: logical_position.to_physical(self.scale_factor()), + position: view_point.to_physical(self.scale_factor()), }); } } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 790a0ba1..aac58b91 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -18,7 +18,6 @@ use crate::{ event_loop::EventLoopWindowTarget, ffi, monitor::{self, MonitorHandle, VideoMode}, - util, view::WinitView, window_delegate::WinitWindowDelegate, Fullscreen, OsError, PlatformCustomCursor, @@ -52,7 +51,7 @@ use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType, De use super::cursor::cursor_from_icon; use super::ffi::kCGFloatingWindowLevel; use super::ffi::{kCGNormalWindowLevel, CGSMainConnectionID, CGSSetWindowBackgroundBlurRadius}; -use super::monitor::get_display_id; +use super::monitor::{flip_window_screen_coordinates, get_display_id}; pub(crate) struct Window { window: MainThreadBound>, @@ -295,24 +294,25 @@ impl WinitWindow { let scale_factor = NSScreen::mainScreen(mtm) .map(|screen| screen.backingScaleFactor() as f64) .unwrap_or(1.0); - let (width, height) = match attrs.inner_size { + let size = match attrs.inner_size { Some(size) => { - let logical = size.to_logical(scale_factor); - (logical.width, logical.height) + let size = size.to_logical(scale_factor); + NSSize::new(size.width, size.height) } - None => (800.0, 600.0), + None => NSSize::new(800.0, 600.0), }; - let (left, bottom) = match attrs.position { + let position = match attrs.position { Some(position) => { - let logical = util::window_position(position.to_logical(scale_factor)); - // macOS wants the position of the bottom left corner, - // but caller is setting the position of top left corner - (logical.x, logical.y - height) + let position = position.to_logical(scale_factor); + flip_window_screen_coordinates(NSRect::new( + NSPoint::new(position.x, position.y), + size, + )) } // This value is ignored by calling win.center() below - None => (0.0, 0.0), + None => NSPoint::new(0.0, 0.0), }; - NSRect::new(NSPoint::new(left, bottom), NSSize::new(width, height)) + NSRect::new(position, size) } }; @@ -609,52 +609,44 @@ impl WinitWindow { pub fn pre_present_notify(&self) {} pub fn outer_position(&self) -> Result, NotSupportedError> { - let frame_rect = self.frame(); - let position = LogicalPosition::new( - frame_rect.origin.x as f64, - util::bottom_left_to_top_left(frame_rect), - ); - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) + let position = flip_window_screen_coordinates(self.frame()); + Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())) } pub fn inner_position(&self) -> Result, NotSupportedError> { let content_rect = self.contentRectForFrameRect(self.frame()); - let position = LogicalPosition::new( - content_rect.origin.x as f64, - util::bottom_left_to_top_left(content_rect), - ); - let scale_factor = self.scale_factor(); - Ok(position.to_physical(scale_factor)) + let position = flip_window_screen_coordinates(content_rect); + Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor())) } pub fn set_outer_position(&self, position: Position) { - let scale_factor = self.scale_factor(); - let position = position.to_logical(scale_factor); - self.setFrameTopLeftPoint(util::window_position(position)); + let position = position.to_logical(self.scale_factor()); + let point = flip_window_screen_coordinates(NSRect::new( + NSPoint::new(position.x, position.y), + self.frame().size, + )); + unsafe { self.setFrameOrigin(point) }; } #[inline] pub fn inner_size(&self) -> PhysicalSize { - let frame = self.contentView().unwrap().frame(); - let logical: LogicalSize = (frame.size.width as f64, frame.size.height as f64).into(); - let scale_factor = self.scale_factor(); - logical.to_physical(scale_factor) + let content_rect = self.contentRectForFrameRect(self.frame()); + let logical = LogicalSize::new(content_rect.size.width, content_rect.size.height); + logical.to_physical(self.scale_factor()) } #[inline] pub fn outer_size(&self) -> PhysicalSize { let frame = self.frame(); - let logical: LogicalSize = (frame.size.width as f64, frame.size.height as f64).into(); - let scale_factor = self.scale_factor(); - logical.to_physical(scale_factor) + let logical = LogicalSize::new(frame.size.width, frame.size.height); + logical.to_physical(self.scale_factor()) } #[inline] pub fn request_inner_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); - let size: LogicalSize = size.to_logical(scale_factor); - self.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat)); + let size = size.to_logical(scale_factor); + self.setContentSize(NSSize::new(size.width, size.height)); None } @@ -665,26 +657,18 @@ impl WinitWindow { })); let min_size = dimensions.to_logical::(self.scale_factor()); - let mut current_rect = self.frame(); - let content_rect = self.contentRectForFrameRect(current_rect); - // Convert from client area size to window size - let min_size = NSSize::new( - min_size.width + (current_rect.size.width - content_rect.size.width), // this tends to be 0 - min_size.height + (current_rect.size.height - content_rect.size.height), - ); - self.setMinSize(min_size); + let min_size = NSSize::new(min_size.width, min_size.height); + unsafe { self.setContentMinSize(min_size) }; + // If necessary, resize the window to match constraint - if current_rect.size.width < min_size.width { - current_rect.size.width = min_size.width; - self.setFrame_display(current_rect, false) + let mut current_size = self.contentRectForFrameRect(self.frame()).size; + if current_size.width < min_size.width { + current_size.width = min_size.width; } - if current_rect.size.height < min_size.height { - // The origin point of a rectangle is at its bottom left in Cocoa. - // To ensure the window's top-left point remains the same: - current_rect.origin.y += current_rect.size.height - min_size.height; - current_rect.size.height = min_size.height; - self.setFrame_display(current_rect, false) + if current_size.height < min_size.height { + current_size.height = min_size.height; } + self.setContentSize(current_size); } pub fn set_max_inner_size(&self, dimensions: Option) { @@ -695,26 +679,18 @@ impl WinitWindow { let scale_factor = self.scale_factor(); let max_size = dimensions.to_logical::(scale_factor); - let mut current_rect = self.frame(); - let content_rect = self.contentRectForFrameRect(current_rect); - // Convert from client area size to window size - let max_size = NSSize::new( - max_size.width + (current_rect.size.width - content_rect.size.width), // this tends to be 0 - max_size.height + (current_rect.size.height - content_rect.size.height), - ); - self.setMaxSize(max_size); + let max_size = NSSize::new(max_size.width, max_size.height); + unsafe { self.setContentMaxSize(max_size) }; + // If necessary, resize the window to match constraint - if current_rect.size.width > max_size.width { - current_rect.size.width = max_size.width; - self.setFrame_display(current_rect, false) + let mut current_size = self.contentRectForFrameRect(self.frame()).size; + if max_size.width < current_size.width { + current_size.width = max_size.width; } - if current_rect.size.height > max_size.height { - // The origin point of a rectangle is at its bottom left in Cocoa. - // To ensure the window's top-left point remains the same: - current_rect.origin.y += current_rect.size.height - max_size.height; - current_rect.size.height = max_size.height; - self.setFrame_display(current_rect, false) + if max_size.height < current_size.height { + current_size.height = max_size.height; } + self.setContentSize(current_size); } pub fn resize_increments(&self) -> Option> { @@ -1062,11 +1038,7 @@ impl WinitWindow { let old_screen = self.screen().unwrap(); if old_screen != new_screen { - let mut screen_frame = new_screen.frame(); - // The coordinate system here has its origin at bottom-left - // and Y goes up - screen_frame.origin.y += screen_frame.size.height; - self.setFrameTopLeftPoint(screen_frame.origin); + unsafe { self.setFrameOrigin(new_screen.frame().origin) }; } } @@ -1291,7 +1263,11 @@ impl WinitWindow { pub fn set_ime_cursor_area(&self, spot: Position, size: Size) { let scale_factor = self.scale_factor(); let logical_spot = spot.to_logical(scale_factor); + let logical_spot = NSPoint::new(logical_spot.x, logical_spot.y); + let size = size.to_logical(scale_factor); + let size = NSSize::new(size.width, size.height); + self.view().set_ime_cursor_area(logical_spot, size); } diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 5f06f557..e4cad2c2 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -7,16 +7,18 @@ use icrate::AppKit::{ NSApplicationPresentationHideMenuBar, NSApplicationPresentationOptions, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSWindowDelegate, NSWindowOcclusionStateVisible, }; -use icrate::Foundation::{MainThreadMarker, NSArray, NSObject, NSObjectProtocol, NSSize, NSString}; +use icrate::Foundation::{ + MainThreadMarker, NSArray, NSObject, NSObjectProtocol, NSPoint, NSSize, NSString, +}; use objc2::rc::{autoreleasepool, Id}; use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::{ class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass, }; +use super::monitor::flip_window_screen_coordinates; use super::{ app_state::AppState, - util, window::{get_ns_theme, WinitWindow}, Fullscreen, }; @@ -35,7 +37,9 @@ pub(crate) struct State { initial_fullscreen: Cell, // During `windowDidResize`, we use this to only send Moved if the position changed. - previous_position: Cell>, + // + // This is expressed in native screen coordinates. + previous_position: Cell>, // Used to prevent redundant events. previous_scale_factor: Cell, @@ -450,34 +454,33 @@ impl WinitWindowDelegate { } fn queue_static_scale_factor_changed_event(&self) { - let scale_factor = self.ivars().window.scale_factor(); + let window = &self.ivars().window; + let scale_factor = window.scale_factor(); if scale_factor == self.ivars().previous_scale_factor.get() { return; }; self.ivars().previous_scale_factor.set(scale_factor); - let suggested_size = self.view_size(); + let content_size = window.contentRectForFrameRect(window.frame()).size; + let content_size = LogicalSize::new(content_size.width, content_size.height); AppState::queue_static_scale_factor_changed_event( - self.ivars().window.clone(), - suggested_size.to_physical(scale_factor), + window.clone(), + content_size.to_physical(scale_factor), scale_factor, ); } fn emit_move_event(&self) { - let rect = self.ivars().window.frame(); - let x = rect.origin.x as f64; - let y = util::bottom_left_to_top_left(rect); - if self.ivars().previous_position.get() != Some((x, y)) { - self.ivars().previous_position.set(Some((x, y))); - let scale_factor = self.ivars().window.scale_factor(); - let physical_pos = LogicalPosition::::from((x, y)).to_physical(scale_factor); - self.queue_event(WindowEvent::Moved(physical_pos)); + let window = &self.ivars().window; + let frame = window.frame(); + if self.ivars().previous_position.get() == Some(frame.origin) { + return; } - } + self.ivars().previous_position.set(Some(frame.origin)); - fn view_size(&self) -> LogicalSize { - let size = self.ivars().window.contentView().unwrap().frame().size; - LogicalSize::new(size.width as f64, size.height as f64) + let position = flip_window_screen_coordinates(frame); + let position = + LogicalPosition::new(position.x, position.y).to_physical(window.scale_factor()); + self.queue_event(WindowEvent::Moved(position)); } }