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:


![image](https://github.com/zed-industries/zed/assets/53836821/314cf367-8c70-4e8e-bc4a-dcbb99cb4f71)

Now:


![image](https://github.com/zed-industries/zed/assets/53836821/42af9ef3-8af9-453a-ad95-147b5f9d90ba)

@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:
Bennet Bo Fenner
2024-03-26 13:07:38 -07:00
committed by GitHub
co-authored by Conrad Irwin
parent ffd698be14
commit e272acd1bc
25 changed files with 331 additions and 352 deletions
+15 -15
View File
@@ -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
}
}
+39 -66
View File
@@ -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
);
}