collab ui: Fix notification windows on external monitors (#9817)

Bennet Bo Fenner and Conrad Irwin created

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>

Change summary

crates/collab_ui/src/collab_ui.rs                 |  10 
crates/editor/src/editor_tests.rs                 |   4 
crates/gpui/examples/animation.rs                 |   2 
crates/gpui/examples/hello_world.rs               |   2 
crates/gpui/examples/window_positioning.rs        |  66 ++++++++++
crates/gpui/src/app.rs                            |  18 ++
crates/gpui/src/app/test_context.rs               |   6 
crates/gpui/src/geometry.rs                       |  90 +++++---------
crates/gpui/src/platform.rs                       |  23 +--
crates/gpui/src/platform/linux/wayland/display.rs |   8 
crates/gpui/src/platform/linux/wayland/window.rs  |   4 
crates/gpui/src/platform/linux/x11/display.rs     |  10 
crates/gpui/src/platform/linux/x11/window.rs      |  12 -
crates/gpui/src/platform/mac.rs                   |   6 
crates/gpui/src/platform/mac/display.rs           |  58 --------
crates/gpui/src/platform/mac/window.rs            |  82 +++++++++---
crates/gpui/src/platform/test/display.rs          |   8 
crates/gpui/src/platform/test/window.rs           |  13 +
crates/gpui/src/platform/windows/display.rs       |  30 ++--
crates/gpui/src/platform/windows/window.rs        | 105 ++++++----------
crates/gpui/src/window.rs                         |  39 +++---
crates/storybook/src/storybook.rs                 |   2 
crates/workspace/src/persistence.rs               |  27 ++--
crates/workspace/src/persistence/model.rs         |   4 
crates/workspace/src/workspace.rs                 |  54 ++------
25 files changed, 331 insertions(+), 352 deletions(-)

Detailed changes

crates/collab_ui/src/collab_ui.rs 🔗

@@ -13,7 +13,7 @@ use call::{report_call_event_for_room, ActiveCall};
 pub use collab_panel::CollabPanel;
 pub use collab_titlebar_item::CollabTitlebarItem;
 use gpui::{
-    actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowContext,
+    actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task, WindowContext,
     WindowKind, WindowOptions,
 };
 use panel_settings::MessageEditorSettings;
@@ -97,13 +97,13 @@ fn notification_window_options(
     screen: Rc<dyn PlatformDisplay>,
     window_size: Size<Pixels>,
 ) -> WindowOptions {
-    let notification_margin_width = GlobalPixels::from(16.);
-    let notification_margin_height = GlobalPixels::from(-0.) - GlobalPixels::from(48.);
+    let notification_margin_width = DevicePixels::from(16);
+    let notification_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
 
     let screen_bounds = screen.bounds();
-    let size: Size<GlobalPixels> = window_size.into();
+    let size: Size<DevicePixels> = window_size.into();
 
-    let bounds = gpui::Bounds::<GlobalPixels> {
+    let bounds = gpui::Bounds::<DevicePixels> {
         origin: screen_bounds.upper_right()
             - point(
                 size.width + notification_margin_width,

crates/editor/src/editor_tests.rs 🔗

@@ -7304,8 +7304,8 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
         cx.open_window(
             WindowOptions {
                 bounds: Some(Bounds::from_corners(
-                    gpui::Point::new(0_f64.into(), 0_f64.into()),
-                    gpui::Point::new(10_f64.into(), 80_f64.into()),
+                    gpui::Point::new(0.into(), 0.into()),
+                    gpui::Point::new(10.into(), 80.into()),
                 )),
                 ..Default::default()
             },

crates/gpui/examples/animation.rs 🔗

@@ -63,7 +63,7 @@ fn main() {
         .with_assets(Assets {})
         .run(|cx: &mut AppContext| {
             let options = WindowOptions {
-                bounds: Some(Bounds::centered(size(px(300.), px(300.)), cx)),
+                bounds: Some(Bounds::centered(None, size(px(300.), px(300.)), cx)),
                 ..Default::default()
             };
             cx.open_window(options, |cx| {

crates/gpui/examples/hello_world.rs 🔗

@@ -23,7 +23,7 @@ impl Render for HelloWorld {
 
 fn main() {
     App::new().run(|cx: &mut AppContext| {
-        let bounds = Bounds::centered(size(px(600.0), px(600.0)), cx);
+        let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx);
         cx.open_window(
             WindowOptions {
                 bounds: Some(bounds),

crates/gpui/examples/window_positioning.rs 🔗

@@ -0,0 +1,66 @@
+use gpui::*;
+
+struct WindowContent {
+    text: SharedString,
+}
+
+impl Render for WindowContent {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        div()
+            .flex()
+            .bg(rgb(0x1e2025))
+            .size_full()
+            .justify_center()
+            .items_center()
+            .text_xl()
+            .text_color(rgb(0xffffff))
+            .child(self.text.clone())
+    }
+}
+
+fn main() {
+    App::new().run(|cx: &mut AppContext| {
+        // Create several new windows, positioned in the top right corner of each screen
+
+        for screen in cx.displays() {
+            let options = {
+                let popup_margin_width = DevicePixels::from(16);
+                let popup_margin_height = DevicePixels::from(-0) - DevicePixels::from(48);
+
+                let window_size = Size {
+                    width: px(400.),
+                    height: px(72.),
+                };
+
+                let screen_bounds = screen.bounds();
+                let size: Size<DevicePixels> = window_size.into();
+
+                let bounds = gpui::Bounds::<DevicePixels> {
+                    origin: screen_bounds.upper_right()
+                        - point(size.width + popup_margin_width, popup_margin_height),
+                    size: window_size.into(),
+                };
+
+                WindowOptions {
+                    // Set the bounds of the window in screen coordinates
+                    bounds: Some(bounds),
+                    // Specify the display_id to ensure the window is created on the correct screen
+                    display_id: Some(screen.id()),
+
+                    titlebar: None,
+                    focus: false,
+                    show: true,
+                    kind: WindowKind::PopUp,
+                    is_movable: false,
+                    fullscreen: false,
+                }
+            };
+
+            cx.open_window(options, |cx| {
+                cx.new_view(|_| WindowContent {
+                    text: format!("{:?}", screen.id()).into(),
+                })
+            });
+        }
+    });
+}

crates/gpui/src/app.rs 🔗

@@ -30,11 +30,11 @@ use util::{
 use crate::{
     current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any,
     AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
-    DispatchPhase, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke,
-    LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder,
-    PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString, SubscriberSet,
-    Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
-    WindowContext, WindowHandle, WindowId,
+    DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap,
+    Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point,
+    PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, SharedString,
+    SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window,
+    WindowAppearance, WindowContext, WindowHandle, WindowId,
 };
 
 mod async_context;
@@ -525,6 +525,14 @@ impl AppContext {
         self.platform.primary_display()
     }
 
+    /// Returns the display with the given ID, if one exists.
+    pub fn find_display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
+        self.displays()
+            .iter()
+            .find(|display| display.id() == id)
+            .cloned()
+    }
+
     /// Returns the appearance of the application's windows.
     pub fn window_appearance(&self) -> WindowAppearance {
         self.platform.window_appearance()

crates/gpui/src/app/test_context.rs 🔗

@@ -173,7 +173,7 @@ impl TestAppContext {
         let mut cx = self.app.borrow_mut();
 
         // Some tests rely on the window size matching the bounds of the test display
-        let bounds = Bounds::maximized(&mut cx);
+        let bounds = Bounds::maximized(None, &mut cx);
         cx.open_window(
             WindowOptions {
                 bounds: Some(bounds),
@@ -186,7 +186,7 @@ impl TestAppContext {
     /// Adds a new window with no content.
     pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
         let mut cx = self.app.borrow_mut();
-        let bounds = Bounds::maximized(&mut cx);
+        let bounds = Bounds::maximized(None, &mut cx);
         let window = cx.open_window(
             WindowOptions {
                 bounds: Some(bounds),
@@ -209,7 +209,7 @@ impl TestAppContext {
         V: 'static + Render,
     {
         let mut cx = self.app.borrow_mut();
-        let bounds = Bounds::maximized(&mut cx);
+        let bounds = Bounds::maximized(None, &mut cx);
         let window = cx.open_window(
             WindowOptions {
                 bounds: Some(bounds),

crates/gpui/src/geometry.rs 🔗

@@ -13,7 +13,7 @@ use std::{
     ops::{Add, Div, Mul, MulAssign, Sub},
 };
 
-use crate::AppContext;
+use crate::{AppContext, DisplayId};
 
 /// An axis along which a measurement can be made.
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -363,11 +363,11 @@ pub struct Size<T: Clone + Default + Debug> {
     pub height: T,
 }
 
-impl From<Size<GlobalPixels>> for Size<Pixels> {
-    fn from(size: Size<GlobalPixels>) -> Self {
+impl From<Size<DevicePixels>> for Size<Pixels> {
+    fn from(size: Size<DevicePixels>) -> Self {
         Size {
-            width: Pixels(size.width.0),
-            height: Pixels(size.height.0),
+            width: Pixels(size.width.0 as f32),
+            height: Pixels(size.height.0 as f32),
         }
     }
 }
@@ -604,11 +604,11 @@ impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
     }
 }
 
-impl From<Size<Pixels>> for Size<GlobalPixels> {
+impl From<Size<Pixels>> for Size<DevicePixels> {
     fn from(size: Size<Pixels>) -> Self {
         Size {
-            width: GlobalPixels(size.width.0),
-            height: GlobalPixels(size.height.0),
+            width: DevicePixels(size.width.0 as i32),
+            height: DevicePixels(size.height.0 as i32),
         }
     }
 }
@@ -693,31 +693,43 @@ pub struct Bounds<T: Clone + Default + Debug> {
     pub size: Size<T>,
 }
 
-impl Bounds<GlobalPixels> {
-    /// Generate a centered bounds for the primary display
-    pub fn centered(size: impl Into<Size<GlobalPixels>>, cx: &mut AppContext) -> Self {
+impl Bounds<DevicePixels> {
+    /// Generate a centered bounds for the given display or primary display if none is provided
+    pub fn centered(
+        display_id: Option<DisplayId>,
+        size: impl Into<Size<DevicePixels>>,
+        cx: &mut AppContext,
+    ) -> Self {
+        let display = display_id
+            .and_then(|id| cx.find_display(id))
+            .or_else(|| cx.primary_display());
+
         let size = size.into();
-        cx.primary_display()
+        display
             .map(|display| {
                 let center = display.bounds().center();
                 Bounds {
-                    origin: point(center.x - size.width / 2.0, center.y - size.height / 2.0),
+                    origin: point(center.x - size.width / 2, center.y - size.height / 2),
                     size,
                 }
             })
             .unwrap_or_else(|| Bounds {
-                origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
+                origin: point(DevicePixels(0), DevicePixels(0)),
                 size,
             })
     }
 
-    /// Generate maximized bounds for the primary display
-    pub fn maximized(cx: &mut AppContext) -> Self {
-        cx.primary_display()
+    /// Generate maximized bounds for the given display or primary display if none is provided
+    pub fn maximized(display_id: Option<DisplayId>, cx: &mut AppContext) -> Self {
+        let display = display_id
+            .and_then(|id| cx.find_display(id))
+            .or_else(|| cx.primary_display());
+
+        display
             .map(|display| display.bounds())
             .unwrap_or_else(|| Bounds {
-                origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
-                size: size(GlobalPixels(1024.0), GlobalPixels(768.0)),
+                origin: point(DevicePixels(0), DevicePixels(0)),
+                size: size(DevicePixels(1024), DevicePixels(768)),
             })
     }
 }
@@ -2455,34 +2467,6 @@ impl From<ScaledPixels> for f64 {
     }
 }
 
-/// Represents pixels in a global coordinate space, which can span across multiple displays.
-///
-/// `GlobalPixels` is used when dealing with a coordinate system that is not limited to a single
-/// display's boundaries. This type is particularly useful in multi-monitor setups where
-/// positioning and measurements need to be consistent and relative to a "global" origin point
-/// rather than being relative to any individual display.
-#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
-#[repr(transparent)]
-pub struct GlobalPixels(pub(crate) f32);
-
-impl Debug for GlobalPixels {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{} px (global coordinate space)", self.0)
-    }
-}
-
-impl From<GlobalPixels> for f64 {
-    fn from(global_pixels: GlobalPixels) -> Self {
-        global_pixels.0 as f64
-    }
-}
-
-impl From<f64> for GlobalPixels {
-    fn from(global_pixels: f64) -> Self {
-        GlobalPixels(global_pixels as f32)
-    }
-}
-
 /// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [`WindowContext::set_rem_size`][set_rem_size].
 ///
 /// Rems are used for defining lengths that are scalable and consistent across different UI elements.
@@ -2834,12 +2818,6 @@ impl Half for Rems {
     }
 }
 
-impl Half for GlobalPixels {
-    fn half(&self) -> Self {
-        Self(self.0 / 2.)
-    }
-}
-
 /// Provides a trait for types that can negate their values.
 pub trait Negate {
     /// Returns the negation of the given value
@@ -2882,12 +2860,6 @@ impl Negate for Rems {
     }
 }
 
-impl Negate for GlobalPixels {
-    fn negate(self) -> Self {
-        Self(-self.0)
-    }
-}
-
 /// A trait for checking if a value is zero.
 ///
 /// This trait provides a method to determine if a value is considered to be zero.

crates/gpui/src/platform.rs 🔗

@@ -23,9 +23,9 @@ mod windows;
 
 use crate::{
     Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
-    DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels,
-    GlyphId, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext,
+    DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlyphId, Keymap,
+    LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams,
+    RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext,
 };
 use anyhow::Result;
 use async_task::Runnable;
@@ -152,7 +152,7 @@ pub trait PlatformDisplay: Send + Sync + Debug {
     fn uuid(&self) -> Result<Uuid>;
 
     /// Get the bounds for this display
-    fn bounds(&self) -> Bounds<GlobalPixels>;
+    fn bounds(&self) -> Bounds<DevicePixels>;
 }
 
 /// An opaque identifier for a hardware display
@@ -168,7 +168,7 @@ impl Debug for DisplayId {
 unsafe impl Send for DisplayId {}
 
 pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
-    fn bounds(&self) -> Bounds<GlobalPixels>;
+    fn bounds(&self) -> Bounds<DevicePixels>;
     fn is_maximized(&self) -> bool;
     fn is_minimized(&self) -> bool;
     fn content_size(&self) -> Size<Pixels>;
@@ -508,8 +508,9 @@ pub trait InputHandler: 'static {
 /// The variables that can be configured when creating a new window
 #[derive(Debug)]
 pub struct WindowOptions {
+    /// The bounds of the window in screen coordinates.
     /// None -> inherit, Some(bounds) -> set bounds
-    pub bounds: Option<Bounds<GlobalPixels>>,
+    pub bounds: Option<Bounds<DevicePixels>>,
 
     /// The titlebar configuration of the window
     pub titlebar: Option<TitlebarOptions>,
@@ -529,7 +530,8 @@ pub struct WindowOptions {
     /// Whether the window should be movable by the user
     pub is_movable: bool,
 
-    /// The display to create the window on
+    /// 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>,
 }
 
@@ -537,7 +539,7 @@ pub struct WindowOptions {
 #[derive(Debug)]
 pub(crate) struct WindowParams {
     ///
-    pub bounds: Bounds<GlobalPixels>,
+    pub bounds: Bounds<DevicePixels>,
 
     /// The titlebar configuration of the window
     pub titlebar: Option<TitlebarOptions>,
@@ -552,7 +554,6 @@ pub(crate) struct WindowParams {
 
     pub show: bool,
 
-    /// The display to create the window on
     pub display_id: Option<DisplayId>,
 }
 
@@ -599,10 +600,6 @@ pub enum WindowKind {
     PopUp,
 }
 
-/// Platform level interface
-/// bounds: Bounds<GlobalPixels>
-/// fullscreen: bool
-
 /// The appearance of the window, as defined by the operating system.
 ///
 /// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)

crates/gpui/src/platform/linux/wayland/display.rs 🔗

@@ -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
     }

crates/gpui/src/platform/linux/wayland/window.rs 🔗

@@ -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!()
     }
 

crates/gpui/src/platform/linux/x11/display.rs 🔗

@@ -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
     }
 }

crates/gpui/src/platform/linux/x11/window.rs 🔗

@@ -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)

crates/gpui/src/platform/mac.rs 🔗

@@ -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))
     }
 }

crates/gpui/src/platform/mac/display.rs 🔗

@@ -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),
                 ),
             }
         }

crates/gpui/src/platform/mac/window.rs 🔗

@@ -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()
     }
 

crates/gpui/src/platform/test/display.rs 🔗

@@ -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
     }
 }

crates/gpui/src/platform/test/window.rs 🔗

@@ -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
     }
 

crates/gpui/src/platform/windows/display.rs 🔗

@@ -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
     }
 }

crates/gpui/src/platform/windows/window.rs 🔗

@@ -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
         );
     }

crates/gpui/src/window.rs 🔗

@@ -1,13 +1,13 @@
 use crate::{
     point, px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena,
-    AsyncWindowContext, Bounds, Context, Corners, CursorStyle, DispatchActionListener,
-    DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
-    FileDropEvent, Flatten, Global, GlobalElementId, GlobalPixels, Hsla, KeyBinding, KeyDownEvent,
-    KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers,
-    ModifiersChangedEvent, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
-    PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels,
-    SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
-    TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowOptions,
+    AsyncWindowContext, Bounds, Context, Corners, CursorStyle, DevicePixels,
+    DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity,
+    EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId, Hsla, KeyBinding,
+    KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext,
+    Modifiers, ModifiersChangedEvent, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels,
+    PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render,
+    ScaledPixels, SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task,
+    TextStyle, TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowOptions,
     WindowParams, WindowTextSystem,
 };
 use anyhow::{anyhow, Context as _, Result};
@@ -358,26 +358,27 @@ pub(crate) struct ElementStateBox {
     pub(crate) type_name: &'static str,
 }
 
-fn default_bounds(cx: &mut AppContext) -> Bounds<GlobalPixels> {
-    const DEFAULT_WINDOW_SIZE: Size<GlobalPixels> = size(GlobalPixels(1024.0), GlobalPixels(700.0));
-    const DEFAULT_WINDOW_OFFSET: Point<GlobalPixels> = point(GlobalPixels(0.0), GlobalPixels(35.0));
+fn default_bounds(display_id: Option<DisplayId>, cx: &mut AppContext) -> Bounds<DevicePixels> {
+    const DEFAULT_WINDOW_SIZE: Size<DevicePixels> = size(DevicePixels(1024), DevicePixels(700));
+    const DEFAULT_WINDOW_OFFSET: Point<DevicePixels> = point(DevicePixels(0), DevicePixels(35));
 
     cx.active_window()
         .and_then(|w| w.update(cx, |_, cx| cx.window_bounds()).ok())
         .map(|bounds| bounds.map_origin(|origin| origin + DEFAULT_WINDOW_OFFSET))
         .unwrap_or_else(|| {
-            cx.primary_display()
+            let display = display_id
+                .map(|id| cx.find_display(id))
+                .unwrap_or_else(|| cx.primary_display());
+
+            display
                 .map(|display| {
                     let center = display.bounds().center();
-                    let offset = DEFAULT_WINDOW_SIZE / 2.0;
+                    let offset = DEFAULT_WINDOW_SIZE / 2;
                     let origin = point(center.x - offset.width, center.y - offset.height);
                     Bounds::new(origin, DEFAULT_WINDOW_SIZE)
                 })
                 .unwrap_or_else(|| {
-                    Bounds::new(
-                        point(GlobalPixels(0.0), GlobalPixels(0.0)),
-                        DEFAULT_WINDOW_SIZE,
-                    )
+                    Bounds::new(point(DevicePixels(0), DevicePixels(0)), DEFAULT_WINDOW_SIZE)
                 })
         })
 }
@@ -399,7 +400,7 @@ impl Window {
             fullscreen,
         } = options;
 
-        let bounds = bounds.unwrap_or_else(|| default_bounds(cx));
+        let bounds = bounds.unwrap_or_else(|| default_bounds(display_id, cx));
         let platform_window = cx.platform.open_window(
             handle,
             WindowParams {
@@ -867,7 +868,7 @@ impl<'a> WindowContext<'a> {
     }
 
     /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays.
-    pub fn window_bounds(&self) -> Bounds<GlobalPixels> {
+    pub fn window_bounds(&self) -> Bounds<DevicePixels> {
         self.window.platform_window.bounds()
     }
 

crates/storybook/src/storybook.rs 🔗

@@ -85,7 +85,7 @@ fn main() {
         cx.set_menus(app_menus());
 
         let size = size(px(1500.), px(780.));
-        let bounds = Bounds::centered(size, cx);
+        let bounds = Bounds::centered(None, size, cx);
         let _window = cx.open_window(
             WindowOptions {
                 bounds: Some(bounds),

crates/workspace/src/persistence.rs 🔗

@@ -59,7 +59,7 @@ impl sqlez::bindable::Column for SerializedAxis {
 }
 
 #[derive(Clone, Debug, PartialEq)]
-pub(crate) struct SerializedWindowsBounds(pub(crate) Bounds<gpui::GlobalPixels>);
+pub(crate) struct SerializedWindowsBounds(pub(crate) Bounds<gpui::DevicePixels>);
 
 impl StaticColumnCount for SerializedWindowsBounds {
     fn column_count() -> usize {
@@ -73,10 +73,10 @@ impl Bind for SerializedWindowsBounds {
 
         statement.bind(
             &(
-                SerializedGlobalPixels(self.0.origin.x),
-                SerializedGlobalPixels(self.0.origin.y),
-                SerializedGlobalPixels(self.0.size.width),
-                SerializedGlobalPixels(self.0.size.height),
+                SerializedDevicePixels(self.0.origin.x),
+                SerializedDevicePixels(self.0.origin.y),
+                SerializedDevicePixels(self.0.size.width),
+                SerializedDevicePixels(self.0.size.height),
             ),
             next_index,
         )
@@ -89,10 +89,10 @@ impl Column for SerializedWindowsBounds {
         let bounds = match window_state.as_str() {
             "Fixed" => {
                 let ((x, y, width, height), _) = Column::column(statement, next_index)?;
-                let x: f64 = x;
-                let y: f64 = y;
-                let width: f64 = width;
-                let height: f64 = height;
+                let x: i32 = x;
+                let y: i32 = y;
+                let width: i32 = width;
+                let height: i32 = height;
                 SerializedWindowsBounds(Bounds {
                     origin: point(x.into(), y.into()),
                     size: size(width.into(), height.into()),
@@ -106,17 +106,16 @@ impl Column for SerializedWindowsBounds {
 }
 
 #[derive(Clone, Debug, PartialEq)]
-struct SerializedGlobalPixels(gpui::GlobalPixels);
-impl sqlez::bindable::StaticColumnCount for SerializedGlobalPixels {}
+struct SerializedDevicePixels(gpui::DevicePixels);
+impl sqlez::bindable::StaticColumnCount for SerializedDevicePixels {}
 
-impl sqlez::bindable::Bind for SerializedGlobalPixels {
+impl sqlez::bindable::Bind for SerializedDevicePixels {
     fn bind(
         &self,
         statement: &sqlez::statement::Statement,
         start_index: i32,
     ) -> anyhow::Result<i32> {
-        let this: f64 = self.0.into();
-        let this: f32 = this as _;
+        let this: i32 = self.0.into();
         this.bind(statement, start_index)
     }
 }

crates/workspace/src/persistence/model.rs 🔗

@@ -6,7 +6,7 @@ use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use gpui::{AsyncWindowContext, Bounds, GlobalPixels, Model, Task, View, WeakView};
+use gpui::{AsyncWindowContext, Bounds, DevicePixels, Model, Task, View, WeakView};
 use project::Project;
 use std::{
     path::{Path, PathBuf},
@@ -69,7 +69,7 @@ pub(crate) struct SerializedWorkspace {
     pub(crate) id: WorkspaceId,
     pub(crate) location: WorkspaceLocation,
     pub(crate) center_group: SerializedPaneGroup,
-    pub(crate) bounds: Option<Bounds<GlobalPixels>>,
+    pub(crate) bounds: Option<Bounds<DevicePixels>>,
     pub(crate) fullscreen: bool,
     pub(crate) display: Option<Uuid>,
     pub(crate) docks: DockStructure,

crates/workspace/src/workspace.rs 🔗

@@ -27,8 +27,8 @@ use futures::{
 };
 use gpui::{
     actions, canvas, impl_actions, point, size, Action, AnyElement, AnyView, AnyWeakView,
-    AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DragMoveEvent, Entity as _, EntityId,
-    EventEmitter, FocusHandle, FocusableView, Global, GlobalPixels, KeyContext, Keystroke,
+    AppContext, AsyncAppContext, AsyncWindowContext, Bounds, DevicePixels, DragMoveEvent,
+    Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, KeyContext, Keystroke,
     LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render,
     Size, Subscription, Task, View, WeakView, WindowHandle, WindowOptions,
 };
@@ -89,11 +89,11 @@ use crate::persistence::{
 };
 
 lazy_static! {
-    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
+    static ref ZED_WINDOW_SIZE: Option<Size<DevicePixels>> = env::var("ZED_WINDOW_SIZE")
         .ok()
         .as_deref()
         .and_then(parse_pixel_size_env_var);
-    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
+    static ref ZED_WINDOW_POSITION: Option<Point<DevicePixels>> = env::var("ZED_WINDOW_POSITION")
         .ok()
         .as_deref()
         .and_then(parse_pixel_position_env_var);
@@ -745,11 +745,7 @@ impl Workspace {
             cx.observe_window_activation(Self::on_window_activation_changed),
             cx.observe_window_bounds(move |_, cx| {
                 if let Some(display) = cx.display() {
-                    // Transform fixed bounds to be stored in terms of the containing display
-                    let mut window_bounds = cx.window_bounds();
-                    let display_bounds = display.bounds();
-                    window_bounds.origin.x -= display_bounds.origin.x;
-                    window_bounds.origin.y -= display_bounds.origin.y;
+                    let window_bounds = cx.window_bounds();
                     let fullscreen = cx.is_fullscreen();
 
                     if let Some(display_uuid) = display.uuid().log_err() {
@@ -902,7 +898,7 @@ impl Workspace {
                 })?;
                 window
             } else {
-                let window_bounds_override = window_bounds_env_override(&cx);
+                let window_bounds_override = window_bounds_env_override();
 
                 let (bounds, display, fullscreen) = if let Some(bounds) = window_bounds_override {
                     (Some(bounds), None, false)
@@ -917,24 +913,7 @@ impl Workspace {
                             Some((display?, bounds?.0, fullscreen.unwrap_or(false)))
                         });
 
-                    if let Some((serialized_display, mut bounds, fullscreen)) = restorable_bounds {
-                        // Stored bounds are relative to the containing display.
-                        // So convert back to global coordinates if that screen still exists
-                        let screen_bounds = cx
-                            .update(|cx| {
-                                cx.displays()
-                                    .into_iter()
-                                    .find(|display| display.uuid().ok() == Some(serialized_display))
-                            })
-                            .ok()
-                            .flatten()
-                            .map(|screen| screen.bounds());
-
-                        if let Some(screen_bounds) = screen_bounds {
-                            bounds.origin.x += screen_bounds.origin.x;
-                            bounds.origin.y += screen_bounds.origin.y;
-                        }
-
+                    if let Some((serialized_display, bounds, fullscreen)) = restorable_bounds {
                         (Some(bounds), Some(serialized_display), fullscreen)
                     } else {
                         (None, None, false)
@@ -3756,14 +3735,11 @@ impl Workspace {
     }
 }
 
-fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<Bounds<GlobalPixels>> {
-    let display_origin = cx
-        .update(|cx| Some(cx.displays().first()?.bounds().origin))
-        .ok()??;
+fn window_bounds_env_override() -> Option<Bounds<DevicePixels>> {
     ZED_WINDOW_POSITION
         .zip(*ZED_WINDOW_SIZE)
         .map(|(position, size)| Bounds {
-            origin: display_origin + position,
+            origin: position,
             size,
         })
 }
@@ -4662,7 +4638,7 @@ pub fn join_hosted_project(
             )
             .await?;
 
-            let window_bounds_override = window_bounds_env_override(&cx);
+            let window_bounds_override = window_bounds_env_override();
             cx.update(|cx| {
                 let mut options = (app_state.build_window_options)(None, cx);
                 options.bounds = window_bounds_override;
@@ -4723,7 +4699,7 @@ pub fn join_in_room_project(
                 })?
                 .await?;
 
-            let window_bounds_override = window_bounds_env_override(&cx);
+            let window_bounds_override = window_bounds_env_override();
             cx.update(|cx| {
                 let mut options = (app_state.build_window_options)(None, cx);
                 options.bounds = window_bounds_override;
@@ -4817,18 +4793,18 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
     .detach_and_log_err(cx);
 }
 
-fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
+fn parse_pixel_position_env_var(value: &str) -> Option<Point<DevicePixels>> {
     let mut parts = value.split(',');
     let x: usize = parts.next()?.parse().ok()?;
     let y: usize = parts.next()?.parse().ok()?;
-    Some(point((x as f64).into(), (y as f64).into()))
+    Some(point((x as i32).into(), (y as i32).into()))
 }
 
-fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
+fn parse_pixel_size_env_var(value: &str) -> Option<Size<DevicePixels>> {
     let mut parts = value.split(',');
     let width: usize = parts.next()?.parse().ok()?;
     let height: usize = parts.next()?.parse().ok()?;
-    Some(size((width as f64).into(), (height as f64).into()))
+    Some(size((width as i32).into(), (height as i32).into()))
 }
 
 struct DisconnectedOverlay;