collab ui: Fix notification windows on external monitors (#9817)
Sharing a project displays a notification (window) on every screen. Previously there was an issue with the positioning of windows on all screens except the primary screen. As you can see here:  Now:  @mikayla-maki and I also decided to refactor the `WindowOptions` a bit. Previously you could specify bounds which controlled the positioning and size of the window in the global coordinate space, while also providing a display id (which screen to show the window on). This can lead to unusual behavior because you could theoretically specify a global bound which does not even belong to the display id which was provided. Therefore we changed the api to this: ```rust struct WindowOptions { /// The bounds of the window in screen coordinates /// None -> inherit, Some(bounds) -> set bounds. pub bounds: Option<Bounds<DevicePixels>>, /// The display to create the window on, if this is None, /// the window will be created on the main display pub display_id: Option<DisplayId>, } ``` This lets you specify a display id, which maps to the screen where the window should be created and bounds relative to the upper left of the screen. Release Notes: - Fixed positioning of popup windows (e.g. when sharing a project) when using multiple external displays. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
co-authored by
Conrad Irwin
parent
ffd698be14
commit
e272acd1bc
@@ -2,7 +2,7 @@ use std::fmt::Debug;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
|
||||
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Size};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WaylandDisplay {}
|
||||
@@ -19,12 +19,12 @@ impl PlatformDisplay for WaylandDisplay {
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
Bounds {
|
||||
origin: Default::default(),
|
||||
size: Size {
|
||||
width: GlobalPixels(1000f32),
|
||||
height: GlobalPixels(500f32),
|
||||
width: DevicePixels(1000),
|
||||
height: DevicePixels(500),
|
||||
},
|
||||
} // return some fake data so it doesn't panic
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::platform::linux::wayland::display::WaylandDisplay;
|
||||
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
|
||||
use crate::scene::Scene;
|
||||
use crate::{
|
||||
px, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point,
|
||||
px, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point,
|
||||
PromptLevel, Size, WindowAppearance, WindowParams,
|
||||
};
|
||||
|
||||
@@ -274,7 +274,7 @@ impl HasDisplayHandle for WaylandWindow {
|
||||
|
||||
impl PlatformWindow for WaylandWindow {
|
||||
// todo(linux)
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ use anyhow::Result;
|
||||
use uuid::Uuid;
|
||||
use x11rb::{connection::Connection as _, xcb_ffi::XCBConnection};
|
||||
|
||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
|
||||
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Size};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct X11Display {
|
||||
x_screen_index: usize,
|
||||
bounds: Bounds<GlobalPixels>,
|
||||
bounds: Bounds<DevicePixels>,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ impl X11Display {
|
||||
bounds: Bounds {
|
||||
origin: Default::default(),
|
||||
size: Size {
|
||||
width: GlobalPixels(screen.width_in_pixels as f32),
|
||||
height: GlobalPixels(screen.height_in_pixels as f32),
|
||||
width: DevicePixels(screen.width_in_pixels as i32),
|
||||
height: DevicePixels(screen.height_in_pixels as i32),
|
||||
},
|
||||
},
|
||||
uuid: Uuid::from_bytes([0; 16]),
|
||||
@@ -37,7 +37,7 @@ impl PlatformDisplay for X11Display {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::{
|
||||
platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
|
||||
platform::blade::BladeRenderer, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformAtlas,
|
||||
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
|
||||
Scene, Size, WindowAppearance, WindowOptions, WindowParams,
|
||||
};
|
||||
@@ -245,7 +245,7 @@ impl X11WindowState {
|
||||
x_window,
|
||||
callbacks: RefCell::new(Callbacks::default()),
|
||||
inner: RefCell::new(LinuxWindowInner {
|
||||
bounds: params.bounds.map(|v| v.0 as i32),
|
||||
bounds: params.bounds.map(|v| v.0),
|
||||
scale_factor: 1.0,
|
||||
renderer: BladeRenderer::new(gpu, gpu_extent),
|
||||
input_handler: None,
|
||||
@@ -325,12 +325,8 @@ impl X11WindowState {
|
||||
}
|
||||
|
||||
impl PlatformWindow for X11Window {
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
self.0
|
||||
.inner
|
||||
.borrow_mut()
|
||||
.bounds
|
||||
.map(|v| GlobalPixels(v as f32))
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.0.inner.borrow_mut().bounds.map(|v| v.into())
|
||||
}
|
||||
|
||||
// todo(linux)
|
||||
|
||||
@@ -22,7 +22,7 @@ mod text_system;
|
||||
mod window;
|
||||
mod window_appearance;
|
||||
|
||||
use crate::{px, size, GlobalPixels, Pixels, Size};
|
||||
use crate::{px, size, DevicePixels, Pixels, Size};
|
||||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
|
||||
@@ -122,9 +122,9 @@ impl From<NSRect> for Size<Pixels> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSRect> for Size<GlobalPixels> {
|
||||
impl From<NSRect> for Size<DevicePixels> {
|
||||
fn from(rect: NSRect) -> Self {
|
||||
let NSSize { width, height } = rect.size;
|
||||
size(width.into(), height.into())
|
||||
size(DevicePixels(width as i32), DevicePixels(height as i32))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
|
||||
use crate::{point, size, Bounds, DevicePixels, DisplayId, PlatformDisplay};
|
||||
use anyhow::Result;
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||
foundation::{NSDictionary, NSString},
|
||||
};
|
||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
|
||||
@@ -69,49 +69,6 @@ extern "C" {
|
||||
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from Cocoa's coordinate space to GPUI's coordinate space.
|
||||
///
|
||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||
pub(crate) fn global_bounds_from_ns_rect(rect: NSRect) -> Bounds<GlobalPixels> {
|
||||
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
|
||||
|
||||
Bounds {
|
||||
origin: point(
|
||||
GlobalPixels(rect.origin.x as f32),
|
||||
GlobalPixels(
|
||||
primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
|
||||
),
|
||||
),
|
||||
size: size(
|
||||
GlobalPixels(rect.size.width as f32),
|
||||
GlobalPixels(rect.size.height as f32),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from GPUI's coordinate system to Cocoa's native coordinate space.
|
||||
///
|
||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||
pub(crate) fn global_bounds_to_ns_rect(bounds: Bounds<GlobalPixels>) -> NSRect {
|
||||
let primary_screen_height = MacDisplay::primary().bounds().size.height;
|
||||
|
||||
NSRect::new(
|
||||
NSPoint::new(
|
||||
bounds.origin.x.into(),
|
||||
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
|
||||
),
|
||||
NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
|
||||
)
|
||||
}
|
||||
|
||||
impl PlatformDisplay for MacDisplay {
|
||||
fn id(&self) -> DisplayId {
|
||||
DisplayId(self.0)
|
||||
@@ -145,20 +102,17 @@ impl PlatformDisplay for MacDisplay {
|
||||
]))
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
unsafe {
|
||||
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
||||
// the top left of the primary display.
|
||||
let bounds = CGDisplayBounds(self.0);
|
||||
|
||||
Bounds {
|
||||
origin: point(
|
||||
GlobalPixels(bounds.origin.x as f32),
|
||||
GlobalPixels(bounds.origin.y as f32),
|
||||
),
|
||||
origin: point(DevicePixels(0), DevicePixels(0)),
|
||||
size: size(
|
||||
GlobalPixels(bounds.size.width as f32),
|
||||
GlobalPixels(bounds.size.height as f32),
|
||||
DevicePixels(bounds.size.width as i32),
|
||||
DevicePixels(bounds.size.height as i32),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use super::{global_bounds_from_ns_rect, ns_string, renderer, MacDisplay, NSRange};
|
||||
use super::{ns_string, renderer, MacDisplay, NSRange};
|
||||
use crate::{
|
||||
global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
|
||||
Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
|
||||
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||
PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowKind, WindowParams,
|
||||
platform::PlatformInputHandler, point, px, size, AnyWindowHandle, Bounds, DevicePixels,
|
||||
DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel,
|
||||
Size, Timer, WindowAppearance, WindowKind, WindowParams,
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
use cocoa::{
|
||||
@@ -441,9 +441,28 @@ impl MacWindowState {
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
let frame = unsafe { NSWindow::frame(self.native_window) };
|
||||
global_bounds_from_ns_rect(frame)
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
let mut window_frame = unsafe { NSWindow::frame(self.native_window) };
|
||||
let screen_frame = unsafe {
|
||||
let screen = NSWindow::screen(self.native_window);
|
||||
NSScreen::frame(screen)
|
||||
};
|
||||
|
||||
// Flip the y coordinate to be top-left origin
|
||||
window_frame.origin.y =
|
||||
screen_frame.size.height - window_frame.origin.y - window_frame.size.height;
|
||||
|
||||
let bounds = Bounds::new(
|
||||
point(
|
||||
((window_frame.origin.x - screen_frame.origin.x) as i32).into(),
|
||||
((window_frame.origin.y - screen_frame.origin.y) as i32).into(),
|
||||
),
|
||||
size(
|
||||
(window_frame.size.width as i32).into(),
|
||||
(window_frame.size.height as i32).into(),
|
||||
),
|
||||
);
|
||||
bounds
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
@@ -494,9 +513,9 @@ impl MacWindow {
|
||||
titlebar,
|
||||
kind,
|
||||
is_movable,
|
||||
display_id,
|
||||
focus,
|
||||
show,
|
||||
display_id,
|
||||
}: WindowParams,
|
||||
executor: ForegroundExecutor,
|
||||
renderer_context: renderer::Context,
|
||||
@@ -529,28 +548,37 @@ impl MacWindow {
|
||||
|
||||
let display = display_id
|
||||
.and_then(MacDisplay::find_by_id)
|
||||
.unwrap_or_else(MacDisplay::primary);
|
||||
.unwrap_or_else(|| MacDisplay::primary());
|
||||
|
||||
let mut target_screen = nil;
|
||||
let mut screen_frame = None;
|
||||
|
||||
let screens = NSScreen::screens(nil);
|
||||
let count: u64 = cocoa::foundation::NSArray::count(screens);
|
||||
for i in 0..count {
|
||||
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
|
||||
let frame = NSScreen::visibleFrame(screen);
|
||||
let display_id = display_id_for_screen(screen);
|
||||
if display_id == display.id().0 {
|
||||
if display_id == display.0 {
|
||||
screen_frame = Some(frame);
|
||||
target_screen = screen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let window_rect = {
|
||||
let display_bounds = display.bounds();
|
||||
if bounds.intersects(&display_bounds) {
|
||||
global_bounds_to_ns_rect(bounds)
|
||||
} else {
|
||||
global_bounds_to_ns_rect(display_bounds)
|
||||
}
|
||||
};
|
||||
let screen_frame = screen_frame.unwrap_or_else(|| {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
target_screen = screen;
|
||||
NSScreen::visibleFrame(screen)
|
||||
});
|
||||
|
||||
let window_rect = NSRect::new(
|
||||
NSPoint::new(
|
||||
screen_frame.origin.x + bounds.origin.x.0 as f64,
|
||||
screen_frame.origin.y
|
||||
+ (display.bounds().size.height - bounds.origin.y).0 as f64,
|
||||
),
|
||||
NSSize::new(bounds.size.width.0 as f64, bounds.size.height.0 as f64),
|
||||
);
|
||||
|
||||
let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
|
||||
window_rect,
|
||||
@@ -572,7 +600,10 @@ impl MacWindow {
|
||||
|
||||
let window_size = {
|
||||
let scale = get_scale_factor(native_window);
|
||||
size(bounds.size.width.0 * scale, bounds.size.height.0 * scale)
|
||||
size(
|
||||
bounds.size.width.0 as f32 * scale,
|
||||
bounds.size.height.0 as f32 * scale,
|
||||
)
|
||||
};
|
||||
|
||||
let window = Self(Arc::new(Mutex::new(MacWindowState {
|
||||
@@ -692,6 +723,11 @@ impl MacWindow {
|
||||
native_window.orderFront_(nil);
|
||||
}
|
||||
|
||||
// Set the initial position of the window to the specified origin.
|
||||
// Although we already specified the position using `initWithContentRect_styleMask_backing_defer_screen_`,
|
||||
// the window position might be incorrect if the main screen (the screen that contains the window that has focus)
|
||||
// is different from the primary screen.
|
||||
NSWindow::setFrameTopLeftPoint_(native_window, window_rect.origin);
|
||||
window.0.lock().move_traffic_light();
|
||||
|
||||
pool.drain();
|
||||
@@ -737,7 +773,7 @@ impl Drop for MacWindow {
|
||||
}
|
||||
|
||||
impl PlatformWindow for MacWindow {
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.0.as_ref().lock().bounds()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use anyhow::{Ok, Result};
|
||||
|
||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point};
|
||||
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TestDisplay {
|
||||
id: DisplayId,
|
||||
uuid: uuid::Uuid,
|
||||
bounds: Bounds<GlobalPixels>,
|
||||
bounds: Bounds<DevicePixels>,
|
||||
}
|
||||
|
||||
impl TestDisplay {
|
||||
@@ -16,7 +16,7 @@ impl TestDisplay {
|
||||
uuid: uuid::Uuid::new_v4(),
|
||||
bounds: Bounds::from_corners(
|
||||
Point::default(),
|
||||
Point::new(GlobalPixels(1920.), GlobalPixels(1080.)),
|
||||
Point::new(DevicePixels(1920), DevicePixels(1080)),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ impl PlatformDisplay for TestDisplay {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {
|
||||
fn bounds(&self) -> crate::Bounds<crate::DevicePixels> {
|
||||
self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult,
|
||||
GlobalPixels, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
||||
PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowParams,
|
||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels,
|
||||
DispatchEventResult, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||
PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance,
|
||||
WindowParams,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
@@ -12,7 +13,7 @@ use std::{
|
||||
};
|
||||
|
||||
pub(crate) struct TestWindowState {
|
||||
pub(crate) bounds: Bounds<GlobalPixels>,
|
||||
pub(crate) bounds: Bounds<DevicePixels>,
|
||||
pub(crate) handle: AnyWindowHandle,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
pub(crate) title: Option<String>,
|
||||
@@ -78,7 +79,7 @@ impl TestWindow {
|
||||
let Some(mut callback) = lock.resize_callback.take() else {
|
||||
return;
|
||||
};
|
||||
lock.bounds.size = size.map(|pixels| f64::from(pixels).into());
|
||||
lock.bounds.size = size.map(|pixels| (pixels.0 as i32).into());
|
||||
drop(lock);
|
||||
callback(size, scale_factor);
|
||||
self.0.lock().resize_callback = Some(callback);
|
||||
@@ -107,7 +108,7 @@ impl TestWindow {
|
||||
}
|
||||
|
||||
impl PlatformWindow for TestWindow {
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.0.lock().bounds
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ use windows::{
|
||||
Win32::{Foundation::*, Graphics::Gdi::*},
|
||||
};
|
||||
|
||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size};
|
||||
use crate::{Bounds, DevicePixels, DisplayId, PlatformDisplay, Point, Size};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WindowsDisplay {
|
||||
pub handle: HMONITOR,
|
||||
pub display_id: DisplayId,
|
||||
bounds: Bounds<GlobalPixels>,
|
||||
bounds: Bounds<DevicePixels>,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ impl WindowsDisplay {
|
||||
display_id,
|
||||
bounds: Bounds {
|
||||
origin: Point {
|
||||
x: GlobalPixels(size.left as f32),
|
||||
y: GlobalPixels(size.top as f32),
|
||||
x: DevicePixels(size.left as i32),
|
||||
y: DevicePixels(size.top as i32),
|
||||
},
|
||||
size: Size {
|
||||
width: GlobalPixels((size.right - size.left) as f32),
|
||||
height: GlobalPixels((size.bottom - size.top) as f32),
|
||||
width: DevicePixels((size.right - size.left) as i32),
|
||||
height: DevicePixels((size.bottom - size.top) as i32),
|
||||
},
|
||||
},
|
||||
uuid,
|
||||
@@ -59,12 +59,12 @@ impl WindowsDisplay {
|
||||
display_id: DisplayId(display_id as _),
|
||||
bounds: Bounds {
|
||||
origin: Point {
|
||||
x: GlobalPixels(size.left as f32),
|
||||
y: GlobalPixels(size.top as f32),
|
||||
x: DevicePixels(size.left as i32),
|
||||
y: DevicePixels(size.top as i32),
|
||||
},
|
||||
size: Size {
|
||||
width: GlobalPixels((size.right - size.left) as f32),
|
||||
height: GlobalPixels((size.bottom - size.top) as f32),
|
||||
width: DevicePixels((size.right - size.left) as i32),
|
||||
height: DevicePixels((size.bottom - size.top) as i32),
|
||||
},
|
||||
},
|
||||
uuid,
|
||||
@@ -81,12 +81,12 @@ impl WindowsDisplay {
|
||||
display_id,
|
||||
bounds: Bounds {
|
||||
origin: Point {
|
||||
x: GlobalPixels(size.left as f32),
|
||||
y: GlobalPixels(size.top as f32),
|
||||
x: DevicePixels(size.left as i32),
|
||||
y: DevicePixels(size.top as i32),
|
||||
},
|
||||
size: Size {
|
||||
width: GlobalPixels((size.right - size.left) as f32),
|
||||
height: GlobalPixels((size.bottom - size.top) as f32),
|
||||
width: DevicePixels((size.right - size.left) as i32),
|
||||
height: DevicePixels((size.bottom - size.top) as i32),
|
||||
},
|
||||
},
|
||||
uuid,
|
||||
@@ -148,7 +148,7 @@ impl PlatformDisplay for WindowsDisplay {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ use crate::*;
|
||||
|
||||
pub(crate) struct WindowsWindowInner {
|
||||
hwnd: HWND,
|
||||
origin: Cell<Point<GlobalPixels>>,
|
||||
physical_size: Cell<Size<GlobalPixels>>,
|
||||
origin: Cell<Point<DevicePixels>>,
|
||||
physical_size: Cell<Size<DevicePixels>>,
|
||||
scale_factor: Cell<f32>,
|
||||
input_handler: Cell<Option<PlatformInputHandler>>,
|
||||
renderer: RefCell<BladeRenderer>,
|
||||
@@ -68,12 +68,12 @@ impl WindowsWindowInner {
|
||||
) -> Self {
|
||||
let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
|
||||
let origin = Cell::new(Point {
|
||||
x: GlobalPixels(cs.x as f32),
|
||||
y: GlobalPixels(cs.y as f32),
|
||||
x: DevicePixels(cs.x as i32),
|
||||
y: DevicePixels(cs.y as i32),
|
||||
});
|
||||
let physical_size = Cell::new(Size {
|
||||
width: GlobalPixels(cs.cx as f32),
|
||||
height: GlobalPixels(cs.cy as f32),
|
||||
width: DevicePixels(cs.cx as i32),
|
||||
height: DevicePixels(cs.cy as i32),
|
||||
});
|
||||
let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
|
||||
let input_handler = Cell::new(None);
|
||||
@@ -299,15 +299,15 @@ impl WindowsWindowInner {
|
||||
}
|
||||
|
||||
fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let x = lparam.signed_loword() as i32;
|
||||
let y = lparam.signed_hiword() as i32;
|
||||
self.origin.set(Point {
|
||||
x: GlobalPixels(x),
|
||||
y: GlobalPixels(y),
|
||||
x: DevicePixels(x),
|
||||
y: DevicePixels(y),
|
||||
});
|
||||
let size = self.physical_size.get();
|
||||
let center_x = x + size.width.0 / 2.0;
|
||||
let center_y = y + size.height.0 / 2.0;
|
||||
let center_x = x + size.width.0 / 2;
|
||||
let center_y = y + size.height.0 / 2;
|
||||
let monitor_bounds = self.display.borrow().bounds();
|
||||
if center_x < monitor_bounds.left().0
|
||||
|| center_x > monitor_bounds.right().0
|
||||
@@ -329,12 +329,12 @@ impl WindowsWindowInner {
|
||||
}
|
||||
|
||||
fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
|
||||
let width = lparam.loword().max(1) as f32;
|
||||
let height = lparam.hiword().max(1) as f32;
|
||||
let width = lparam.loword().max(1) as i32;
|
||||
let height = lparam.hiword().max(1) as i32;
|
||||
let scale_factor = self.scale_factor.get();
|
||||
let new_physical_size = Size {
|
||||
width: GlobalPixels(width),
|
||||
height: GlobalPixels(height),
|
||||
width: DevicePixels(width),
|
||||
height: DevicePixels(height),
|
||||
};
|
||||
self.physical_size.set(new_physical_size);
|
||||
self.renderer.borrow_mut().update_drawable_size(Size {
|
||||
@@ -648,7 +648,7 @@ impl WindowsWindowInner {
|
||||
if let Some(callback) = callbacks.input.as_mut() {
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let physical_point = point(GlobalPixels(x), GlobalPixels(y));
|
||||
let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
|
||||
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
||||
let scale_factor = self.scale_factor.get();
|
||||
let event = MouseDownEvent {
|
||||
@@ -924,8 +924,8 @@ impl WindowsWindowInner {
|
||||
let height = size_rect.bottom - size_rect.top;
|
||||
|
||||
self.physical_size.set(Size {
|
||||
width: GlobalPixels(width as f32),
|
||||
height: GlobalPixels(height as f32),
|
||||
width: DevicePixels(width as i32),
|
||||
height: DevicePixels(height as i32),
|
||||
});
|
||||
|
||||
if self.hide_title_bar {
|
||||
@@ -1077,8 +1077,8 @@ impl WindowsWindowInner {
|
||||
};
|
||||
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
||||
let physical_point = point(
|
||||
GlobalPixels(cursor_point.x as f32),
|
||||
GlobalPixels(cursor_point.y as f32),
|
||||
DevicePixels(cursor_point.x as i32),
|
||||
DevicePixels(cursor_point.y as i32),
|
||||
);
|
||||
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
||||
let scale_factor = self.scale_factor.get();
|
||||
@@ -1305,7 +1305,7 @@ impl Drop for WindowsWindow {
|
||||
}
|
||||
|
||||
impl PlatformWindow for WindowsWindow {
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
fn bounds(&self) -> Bounds<DevicePixels> {
|
||||
Bounds {
|
||||
origin: self.inner.origin.get(),
|
||||
size: self.inner.physical_size.get(),
|
||||
@@ -1674,7 +1674,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler {
|
||||
struct ClickState {
|
||||
button: MouseButton,
|
||||
last_click: Instant,
|
||||
last_position: Point<GlobalPixels>,
|
||||
last_position: Point<DevicePixels>,
|
||||
current_count: usize,
|
||||
}
|
||||
|
||||
@@ -1689,7 +1689,7 @@ impl ClickState {
|
||||
}
|
||||
|
||||
/// update self and return the needed click count
|
||||
pub fn update(&mut self, button: MouseButton, new_position: Point<GlobalPixels>) -> usize {
|
||||
pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
|
||||
if self.button == button && self.is_double_click(new_position) {
|
||||
self.current_count += 1;
|
||||
} else {
|
||||
@@ -1703,7 +1703,7 @@ impl ClickState {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_double_click(&self, new_position: Point<GlobalPixels>) -> bool {
|
||||
fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
|
||||
let diff = self.last_position - new_position;
|
||||
|
||||
self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
|
||||
@@ -1839,10 +1839,10 @@ fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn logical_size(physical_size: Size<GlobalPixels>, scale_factor: f32) -> Size<Pixels> {
|
||||
fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
|
||||
Size {
|
||||
width: px(physical_size.width.0 / scale_factor),
|
||||
height: px(physical_size.height.0 / scale_factor),
|
||||
width: px(physical_size.width.0 as f32 / scale_factor),
|
||||
height: px(physical_size.height.0 as f32 / scale_factor),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1867,51 +1867,36 @@ const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
|
||||
// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
|
||||
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
|
||||
const DOUBLE_CLICK_SPATIAL_TOLERANCE: f32 = 4.0;
|
||||
const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ClickState;
|
||||
use crate::{point, GlobalPixels, MouseButton};
|
||||
use crate::{point, DevicePixels, MouseButton};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_double_click_interval() {
|
||||
let mut state = ClickState::new();
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Left,
|
||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||
),
|
||||
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Right,
|
||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||
),
|
||||
state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Left,
|
||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||
),
|
||||
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Left,
|
||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||
),
|
||||
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||
2
|
||||
);
|
||||
state.last_click -= Duration::from_millis(700);
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Left,
|
||||
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||
),
|
||||
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
|
||||
1
|
||||
);
|
||||
}
|
||||
@@ -1920,31 +1905,19 @@ mod tests {
|
||||
fn test_double_click_spatial_tolerance() {
|
||||
let mut state = ClickState::new();
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Left,
|
||||
point(GlobalPixels(-3.0), GlobalPixels(0.0))
|
||||
),
|
||||
state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Left,
|
||||
point(GlobalPixels(0.0), GlobalPixels(3.0))
|
||||
),
|
||||
state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Right,
|
||||
point(GlobalPixels(3.0), GlobalPixels(2.0))
|
||||
),
|
||||
state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
state.update(
|
||||
MouseButton::Right,
|
||||
point(GlobalPixels(10.0), GlobalPixels(0.0))
|
||||
),
|
||||
state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user