Open new windows with a default size and position (#9204)

Mikayla Maki , Nathan , and Ezekiel Warren created

This PR changes GPUI to open windows with a default size and location,
and to otherwise inherit from their spawning window.

Note: The linux build now crashes on startup.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Ezekiel Warren <zaucy@users.noreply.github.com>

Change summary

crates/collab/src/tests/test_server.rs           |   2 
crates/collab_ui/src/collab_titlebar_item.rs     |   3 
crates/collab_ui/src/collab_ui.rs                |   9 
crates/editor/src/editor_tests.rs                |   4 
crates/editor/src/inlay_hint_cache.rs            |   1 
crates/gpui/examples/hello_world.rs              |  25 +--
crates/gpui/src/app.rs                           |   5 
crates/gpui/src/app/test_context.rs              |  29 ++++
crates/gpui/src/geometry.rs                      |  68 ++++++++++
crates/gpui/src/interactive.rs                   |   1 
crates/gpui/src/platform.rs                      |  59 +++++---
crates/gpui/src/platform/linux/client.rs         |   5 
crates/gpui/src/platform/linux/platform.rs       |   8 
crates/gpui/src/platform/linux/wayland/client.rs |   9 +
crates/gpui/src/platform/linux/wayland/window.rs |  36 +---
crates/gpui/src/platform/linux/x11/client.rs     |  23 ++-
crates/gpui/src/platform/linux/x11/display.rs    |   8 
crates/gpui/src/platform/linux/x11/window.rs     |  50 +++----
crates/gpui/src/platform/mac/platform.rs         |   8 
crates/gpui/src/platform/mac/window.rs           | 114 ++++++-----------
crates/gpui/src/platform/test/platform.rs        |  10 +
crates/gpui/src/platform/test/window.rs          |  41 ++---
crates/gpui/src/platform/windows/platform.rs     |  13 +
crates/gpui/src/platform/windows/window.rs       |  41 ++---
crates/gpui/src/window.rs                        |  87 +++++++++++--
crates/storybook/src/storybook.rs                |  10 
crates/workspace/src/persistence.rs              |  39 +----
crates/workspace/src/persistence/model.rs        |   4 
crates/workspace/src/workspace.rs                |  63 ++++-----
crates/zed/src/zed.rs                            |  13 -
30 files changed, 442 insertions(+), 346 deletions(-)

Detailed changes

crates/collab/src/tests/test_server.rs 🔗

@@ -265,7 +265,7 @@ impl TestServer {
             workspace_store,
             languages: Arc::new(language_registry),
             fs: fs.clone(),
-            build_window_options: |_, _, _| Default::default(),
+            build_window_options: |_, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
         });
 

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -6,7 +6,6 @@ use gpui::{
     actions, canvas, div, point, px, Action, AnyElement, AppContext, Element, Hsla,
     InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
     StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView,
-    WindowBounds,
 };
 use project::{Project, RepositoryEntry};
 use recent_projects::RecentProjects;
@@ -65,7 +64,7 @@ impl Render for CollabTitlebarItem {
             .w_full()
             .h(titlebar_height(cx))
             .map(|this| {
-                if matches!(cx.window_bounds(), WindowBounds::Fullscreen) {
+                if cx.is_full_screen() {
                     this.pl_2()
                 } else {
                     // Use pixels here instead of a rem-based size because the macOS traffic

crates/collab_ui/src/collab_ui.rs 🔗

@@ -13,8 +13,8 @@ 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, WindowBounds,
-    WindowContext, WindowKind, WindowOptions,
+    actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowContext,
+    WindowKind, WindowOptions,
 };
 use panel_settings::MessageEditorSettings;
 pub use panel_settings::{
@@ -111,14 +111,15 @@ fn notification_window_options(
             ),
         size: window_size.into(),
     };
+
     WindowOptions {
-        bounds: WindowBounds::Fixed(bounds),
+        bounds: Some(bounds),
         titlebar: None,
-        center: false,
         focus: false,
         show: true,
         kind: WindowKind::PopUp,
         is_movable: false,
         display_id: Some(screen.id()),
+        fullscreen: false,
     }
 }

crates/editor/src/editor_tests.rs 🔗

@@ -9,7 +9,7 @@ use crate::{
 };
 
 use futures::StreamExt;
-use gpui::{div, TestAppContext, VisualTestContext, WindowBounds, WindowOptions};
+use gpui::{div, TestAppContext, VisualTestContext, WindowOptions};
 use indoc::indoc;
 use language::{
     language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
@@ -6873,7 +6873,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
     let follower = cx.update(|cx| {
         cx.open_window(
             WindowOptions {
-                bounds: WindowBounds::Fixed(Bounds::from_corners(
+                bounds: Some(Bounds::from_corners(
                     gpui::Point::new(0_f64.into(), 0_f64.into()),
                     gpui::Point::new(10_f64.into(), 80_f64.into()),
                 )),

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -2664,6 +2664,7 @@ pub mod tests {
         cx.executor().run_until_parked();
         let editor =
             cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+
         let editor_edited = Arc::new(AtomicBool::new(false));
         let fake_server = fake_servers.next().await.unwrap();
         let closure_editor_edited = Arc::clone(&editor_edited);

crates/gpui/examples/hello_world.rs 🔗

@@ -23,18 +23,17 @@ impl Render for HelloWorld {
 
 fn main() {
     App::new().run(|cx: &mut AppContext| {
-        let options = WindowOptions {
-            bounds: WindowBounds::Fixed(Bounds {
-                size: size(px(600.0), px(600.0)).into(),
-                origin: Default::default(),
-            }),
-            center: true,
-            ..Default::default()
-        };
-        cx.open_window(options, |cx| {
-            cx.new_view(|_cx| HelloWorld {
-                text: "World".into(),
-            })
-        });
+        let bounds = Bounds::centered(size(px(600.0), px(600.0)), cx);
+        cx.open_window(
+            WindowOptions {
+                bounds: Some(bounds),
+                ..Default::default()
+            },
+            |cx| {
+                cx.new_view(|_cx| HelloWorld {
+                    text: "World".into(),
+                })
+            },
+        );
     });
 }

crates/gpui/src/app.rs 🔗

@@ -520,6 +520,11 @@ impl AppContext {
         self.platform.displays()
     }
 
+    /// Returns the primary display that will be used for new windows.
+    pub fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        self.platform.primary_display()
+    }
+
     /// 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 🔗

@@ -171,13 +171,29 @@ impl TestAppContext {
         V: 'static + Render,
     {
         let mut cx = self.app.borrow_mut();
-        cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window))
+
+        // Some tests rely on the window size matching the bounds of the test display
+        let bounds = Bounds::maximized(&mut cx);
+        cx.open_window(
+            WindowOptions {
+                bounds: Some(bounds),
+                ..Default::default()
+            },
+            |cx| cx.new_view(build_window),
+        )
     }
 
     /// Adds a new window with no content.
     pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
         let mut cx = self.app.borrow_mut();
-        let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| Empty));
+        let bounds = Bounds::maximized(&mut cx);
+        let window = cx.open_window(
+            WindowOptions {
+                bounds: Some(bounds),
+                ..Default::default()
+            },
+            |cx| cx.new_view(|_| Empty),
+        );
         drop(cx);
         let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
         cx.run_until_parked();
@@ -193,7 +209,14 @@ impl TestAppContext {
         V: 'static + Render,
     {
         let mut cx = self.app.borrow_mut();
-        let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(build_window));
+        let bounds = Bounds::maximized(&mut cx);
+        let window = cx.open_window(
+            WindowOptions {
+                bounds: Some(bounds),
+                ..Default::default()
+            },
+            |cx| cx.new_view(build_window),
+        );
         drop(cx);
         let view = window.root_view(self).unwrap();
         let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();

crates/gpui/src/geometry.rs 🔗

@@ -9,9 +9,12 @@ use serde_derive::{Deserialize, Serialize};
 use std::{
     cmp::{self, PartialOrd},
     fmt,
+    hash::Hash,
     ops::{Add, Div, Mul, MulAssign, Sub},
 };
 
+use crate::AppContext;
+
 /// An axis along which a measurement can be made.
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 pub enum Axis {
@@ -84,7 +87,7 @@ pub struct Point<T: Default + Clone + Debug> {
 /// assert_eq!(p.x, 10);
 /// assert_eq!(p.y, 20);
 /// ```
-pub fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
+pub const fn point<T: Clone + Debug + Default>(x: T, y: T) -> Point<T> {
     Point { x, y }
 }
 
@@ -354,6 +357,15 @@ pub struct Size<T: Clone + Default + Debug> {
     pub height: T,
 }
 
+impl From<Size<GlobalPixels>> for Size<Pixels> {
+    fn from(size: Size<GlobalPixels>) -> Self {
+        Size {
+            width: Pixels(size.width.0),
+            height: Pixels(size.height.0),
+        }
+    }
+}
+
 /// Constructs a new `Size<T>` with the provided width and height.
 ///
 /// # Arguments
@@ -369,7 +381,7 @@ pub struct Size<T: Clone + Default + Debug> {
 /// assert_eq!(my_size.width, 10);
 /// assert_eq!(my_size.height, 20);
 /// ```
-pub fn size<T>(width: T, height: T) -> Size<T>
+pub const fn size<T>(width: T, height: T) -> Size<T>
 where
     T: Clone + Default + Debug,
 {
@@ -662,6 +674,35 @@ 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 {
+        let size = size.into();
+        cx.primary_display()
+            .map(|display| {
+                let center = display.bounds().center();
+                Bounds {
+                    origin: point(center.x - size.width / 2.0, center.y - size.height / 2.0),
+                    size,
+                }
+            })
+            .unwrap_or_else(|| Bounds {
+                origin: point(GlobalPixels(0.0), GlobalPixels(0.0)),
+                size,
+            })
+    }
+
+    /// Generate maximized bounds for the primary display
+    pub fn maximized(cx: &mut AppContext) -> Self {
+        cx.primary_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)),
+            })
+    }
+}
+
 impl<T> Bounds<T>
 where
     T: Clone + Debug + Sub<Output = T> + Default,
@@ -1165,6 +1206,29 @@ where
             size: self.size.map(f),
         }
     }
+
+    /// Applies a function to the origin  of the bounds, producing a new `Bounds` with the new origin
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use zed::{Bounds, Point, Size};
+    /// let bounds = Bounds {
+    ///     origin: Point { x: 10.0, y: 10.0 },
+    ///     size: Size { width: 10.0, height: 20.0 },
+    /// };
+    /// let new_bounds = bounds.map_origin(|value| value * 1.5);
+    ///
+    /// assert_eq!(new_bounds, Bounds {
+    ///     origin: Point { x: 15.0, y: 15.0 },
+    ///     size: Size { width: 10.0, height: 20.0 },
+    /// });
+    pub fn map_origin(self, f: impl Fn(Point<T>) -> Point<T>) -> Bounds<T> {
+        Bounds {
+            origin: f(self.origin),
+            size: self.size,
+        }
+    }
 }
 
 /// Checks if the bounds represent an empty area.

crates/gpui/src/interactive.rs 🔗

@@ -436,6 +436,7 @@ impl PlatformInput {
 
 #[cfg(test)]
 mod test {
+
     use crate::{
         self as gpui, div, Element, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
         Keystroke, ParentElement, Render, TestAppContext, VisualContext,

crates/gpui/src/platform.rs 🔗

@@ -88,12 +88,13 @@ pub(crate) trait Platform: 'static {
     fn unhide_other_apps(&self);
 
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
+    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
     fn active_window(&self) -> Option<AnyWindowHandle>;
     fn open_window(
         &self,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Box<dyn PlatformWindow>;
 
     /// Returns the appearance of the application's windows.
@@ -166,7 +167,7 @@ impl Debug for DisplayId {
 unsafe impl Send for DisplayId {}
 
 pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
-    fn bounds(&self) -> WindowBounds;
+    fn bounds(&self) -> Bounds<GlobalPixels>;
     fn content_size(&self) -> Size<Pixels>;
     fn scale_factor(&self) -> f32;
     fn titlebar_height(&self) -> Pixels;
@@ -191,6 +192,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
     fn minimize(&self);
     fn zoom(&self);
     fn toggle_full_screen(&self);
+    fn is_full_screen(&self) -> bool;
     fn on_request_frame(&self, callback: Box<dyn FnMut()>);
     fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
     fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
@@ -501,21 +503,21 @@ pub trait InputHandler: 'static {
 /// The variables that can be configured when creating a new window
 #[derive(Debug)]
 pub struct WindowOptions {
-    /// The initial bounds of the window
-    pub bounds: WindowBounds,
+    /// None -> inherit, Some(bounds) -> set bounds
+    pub bounds: Option<Bounds<GlobalPixels>>,
 
     /// The titlebar configuration of the window
     pub titlebar: Option<TitlebarOptions>,
 
-    /// Whether the window should be centered on the screen
-    pub center: bool,
-
     /// Whether the window should be focused when created
     pub focus: bool,
 
     /// Whether the window should be shown when created
     pub show: bool,
 
+    /// Whether the window should be fullscreen when created
+    pub fullscreen: bool,
+
     /// The kind of window to create
     pub kind: WindowKind,
 
@@ -526,21 +528,44 @@ pub struct WindowOptions {
     pub display_id: Option<DisplayId>,
 }
 
+/// The variables that can be configured when creating a new window
+#[derive(Debug)]
+pub(crate) struct WindowParams {
+    ///
+    pub bounds: Bounds<GlobalPixels>,
+
+    /// The titlebar configuration of the window
+    pub titlebar: Option<TitlebarOptions>,
+
+    /// The kind of window to create
+    pub kind: WindowKind,
+
+    /// Whether the window should be movable by the user
+    pub is_movable: bool,
+
+    pub focus: bool,
+
+    pub show: bool,
+
+    /// The display to create the window on
+    pub display_id: Option<DisplayId>,
+}
+
 impl Default for WindowOptions {
     fn default() -> Self {
         Self {
-            bounds: WindowBounds::default(),
+            bounds: None,
             titlebar: Some(TitlebarOptions {
                 title: Default::default(),
                 appears_transparent: Default::default(),
                 traffic_light_position: Default::default(),
             }),
-            center: false,
             focus: true,
             show: true,
             kind: WindowKind::Normal,
             is_movable: true,
             display_id: None,
+            fullscreen: false,
         }
     }
 }
@@ -569,19 +594,9 @@ pub enum WindowKind {
     PopUp,
 }
 
-/// Which bounds algorithm to use for the initial size a window
-#[derive(Copy, Clone, Debug, PartialEq, Default)]
-pub enum WindowBounds {
-    /// The window should be full screen, on macOS this corresponds to the full screen feature
-    Fullscreen,
-
-    /// Make the window as large as the current display's size.
-    #[default]
-    Maximized,
-
-    /// Set the window to the given size in pixels
-    Fixed(Bounds<GlobalPixels>),
-}
+/// Platform level interface
+/// bounds: Bounds<GlobalPixels>
+/// full_screen: bool
 
 /// The appearance of the window, as defined by the operating system.
 ///

crates/gpui/src/platform/linux/client.rs 🔗

@@ -4,15 +4,16 @@ use std::rc::Rc;
 use copypasta::ClipboardProvider;
 
 use crate::platform::PlatformWindow;
-use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowOptions};
+use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowParams};
 
 pub trait Client {
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
+    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
     fn open_window(
         &self,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Box<dyn PlatformWindow>;
     fn set_cursor_style(&self, style: CursorStyle);
     fn get_clipboard(&self) -> Rc<RefCell<dyn ClipboardProvider>>;

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

@@ -25,7 +25,7 @@ use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
     ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions,
     Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result,
-    SemanticVersion, Task, WindowOptions,
+    SemanticVersion, Task, WindowOptions, WindowParams,
 };
 
 use super::x11::X11Client;
@@ -156,6 +156,10 @@ impl Platform for LinuxPlatform {
     // todo(linux)
     fn unhide_other_apps(&self) {}
 
+    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        self.client.primary_display()
+    }
+
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
         self.client.displays()
     }
@@ -172,7 +176,7 @@ impl Platform for LinuxPlatform {
     fn open_window(
         &self,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Box<dyn PlatformWindow> {
         self.client.open_window(handle, options)
     }

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

@@ -39,11 +39,12 @@ use crate::platform::linux::client::Client;
 use crate::platform::linux::wayland::cursor::Cursor;
 use crate::platform::linux::wayland::window::{WaylandDecorationState, WaylandWindow};
 use crate::platform::{LinuxPlatformInner, PlatformWindow};
+use crate::WindowParams;
 use crate::{
     platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, CursorStyle, DisplayId,
     KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
     MouseDownEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
-    PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase, WindowOptions,
+    PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase,
 };
 
 /// Used to convert evdev scancode to xkb scancode
@@ -207,10 +208,14 @@ impl Client for WaylandClient {
         unimplemented!()
     }
 
+    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        None
+    }
+
     fn open_window(
         &self,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Box<dyn PlatformWindow> {
         let mut state = self.state.client_state_inner.borrow_mut();
 

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

@@ -22,8 +22,8 @@ use crate::platform::linux::wayland::display::WaylandDisplay;
 use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
 use crate::scene::Scene;
 use crate::{
-    px, size, Bounds, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point, PromptLevel, Size,
-    WindowAppearance, WindowBounds, WindowOptions,
+    px, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point,
+    PromptLevel, Size, WindowAppearance, WindowParams,
 };
 
 #[derive(Default)]
@@ -125,24 +125,9 @@ impl WaylandWindowState {
         wl_surf: Arc<wl_surface::WlSurface>,
         viewport: Option<wp_viewport::WpViewport>,
         toplevel: Arc<xdg_toplevel::XdgToplevel>,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Self {
-        if options.bounds == WindowBounds::Maximized {
-            toplevel.set_maximized();
-        } else if options.bounds == WindowBounds::Fullscreen {
-            toplevel.set_fullscreen(None);
-        }
-
-        let bounds: Bounds<u32> = match options.bounds {
-            WindowBounds::Fullscreen | WindowBounds::Maximized => Bounds {
-                origin: Point::default(),
-                size: Size {
-                    width: 500,
-                    height: 500,
-                }, // todo(implement)
-            },
-            WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as u32),
-        };
+        let bounds = options.bounds.map(|p| p.0 as u32);
 
         Self {
             surface: Arc::clone(&wl_surf),
@@ -290,8 +275,8 @@ impl HasDisplayHandle for WaylandWindow {
 
 impl PlatformWindow for WaylandWindow {
     // todo(linux)
-    fn bounds(&self) -> WindowBounds {
-        WindowBounds::Maximized
+    fn bounds(&self) -> Bounds<GlobalPixels> {
+        unimplemented!()
     }
 
     fn content_size(&self) -> Size<Pixels> {
@@ -331,9 +316,8 @@ impl PlatformWindow for WaylandWindow {
         crate::Modifiers::default()
     }
 
-    // todo(linux)
     fn as_any_mut(&mut self) -> &mut dyn Any {
-        unimplemented!()
+        self
     }
 
     fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
@@ -379,13 +363,17 @@ impl PlatformWindow for WaylandWindow {
     }
 
     fn toggle_full_screen(&self) {
-        if !self.0.inner.borrow_mut().fullscreen {
+        if !self.0.inner.borrow().fullscreen {
             self.0.toplevel.set_fullscreen(None);
         } else {
             self.0.toplevel.unset_fullscreen();
         }
     }
 
+    fn is_full_screen(&self) -> bool {
+        self.0.inner.borrow_mut().fullscreen
+    }
+
     fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
         self.0.callbacks.borrow_mut().request_frame = Some(callback);
     }

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

@@ -13,7 +13,7 @@ use crate::platform::linux::client::Client;
 use crate::platform::{LinuxPlatformInner, PlatformWindow};
 use crate::{
     AnyWindowHandle, Bounds, CursorStyle, DisplayId, PlatformDisplay, PlatformInput, Point,
-    ScrollDelta, Size, TouchPhase, WindowOptions,
+    ScrollDelta, Size, TouchPhase,
 };
 
 use super::{X11Display, X11Window, X11WindowState, XcbAtoms};
@@ -284,26 +284,35 @@ impl Client for X11Client {
         setup
             .roots()
             .enumerate()
-            .map(|(root_id, _)| {
-                Rc::new(X11Display::new(&self.xcb_connection, root_id as i32))
-                    as Rc<dyn PlatformDisplay>
+            .filter_map(|(root_id, _)| {
+                Some(
+                    Rc::new(X11Display::new(&self.xcb_connection, root_id as i32)?)
+                        as Rc<dyn PlatformDisplay>,
+                )
             })
             .collect()
     }
 
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
-        Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)))
+        Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)?))
+    }
+
+    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        Some(Rc::new(
+            X11Display::new(&self.xcb_connection, self.x_root_index)
+                .expect("There should always be a root index"),
+        ))
     }
 
     fn open_window(
         &self,
         _handle: AnyWindowHandle,
-        options: WindowOptions,
+        params: crate::WindowParams,
     ) -> Box<dyn PlatformWindow> {
         let x_window = self.xcb_connection.generate_id();
 
         let window_ptr = Rc::new(X11WindowState::new(
-            options,
+            params,
             &self.xcb_connection,
             self.x_root_index,
             x_window,

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

@@ -11,9 +11,9 @@ pub(crate) struct X11Display {
 }
 
 impl X11Display {
-    pub(crate) fn new(xc: &xcb::Connection, x_screen_index: i32) -> Self {
-        let screen = xc.get_setup().roots().nth(x_screen_index as usize).unwrap();
-        Self {
+    pub(crate) fn new(xc: &xcb::Connection, x_screen_index: i32) -> Option<Self> {
+        let screen = xc.get_setup().roots().nth(x_screen_index as usize)?;
+        Some(Self {
             x_screen_index,
             bounds: Bounds {
                 origin: Default::default(),
@@ -23,7 +23,7 @@ impl X11Display {
                 },
             },
             uuid: Uuid::from_bytes([0; 16]),
-        }
+        })
     }
 }
 

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

@@ -4,7 +4,7 @@
 use crate::{
     platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
     PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
-    Scene, Size, WindowAppearance, WindowBounds, WindowOptions,
+    Scene, Size, WindowAppearance, WindowOptions, WindowParams,
 };
 use blade_graphics as gpu;
 use parking_lot::Mutex;
@@ -138,13 +138,13 @@ impl rwh::HasDisplayHandle for X11Window {
 
 impl X11WindowState {
     pub fn new(
-        options: WindowOptions,
+        params: WindowParams,
         xcb_connection: &Rc<xcb::Connection>,
         x_main_screen_index: i32,
         x_window: x::Window,
         atoms: &XcbAtoms,
     ) -> Self {
-        let x_screen_index = options
+        let x_screen_index = params
             .display_id
             .map_or(x_main_screen_index, |did| did.0 as i32);
         let screen = xcb_connection
@@ -175,32 +175,21 @@ impl X11WindowState {
             ),
         ];
 
-        let bounds = match options.bounds {
-            WindowBounds::Fullscreen | WindowBounds::Maximized => Bounds {
-                origin: Point::default(),
-                size: Size {
-                    width: screen.width_in_pixels() as i32,
-                    height: screen.height_in_pixels() as i32,
-                },
-            },
-            WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32),
-        };
-
         xcb_connection.send_request(&x::CreateWindow {
             depth: x::COPY_FROM_PARENT as u8,
             wid: x_window,
             parent: screen.root(),
-            x: bounds.origin.x as i16,
-            y: bounds.origin.y as i16,
-            width: bounds.size.width as u16,
-            height: bounds.size.height as u16,
+            x: params.bounds.origin.x.0 as i16,
+            y: params.bounds.origin.y.0 as i16,
+            width: params.bounds.size.width.0 as u16,
+            height: params.bounds.size.height.0 as u16,
             border_width: 0,
             class: x::WindowClass::InputOutput,
             visual: screen.root_visual(),
             value_list: &xcb_values,
         });
 
-        if let Some(titlebar) = options.titlebar {
+        if let Some(titlebar) = params.titlebar {
             if let Some(title) = titlebar.title {
                 xcb_connection.send_request(&x::ChangeProperty {
                     mode: x::PropMode::Replace,
@@ -250,12 +239,12 @@ impl X11WindowState {
 
         Self {
             xcb_connection: xcb_connection.clone(),
-            display: Rc::new(X11Display::new(xcb_connection, x_screen_index)),
+            display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
             raw,
             x_window,
             callbacks: RefCell::new(Callbacks::default()),
             inner: RefCell::new(LinuxWindowInner {
-                bounds,
+                bounds: params.bounds.map(|v| v.0 as i32),
                 scale_factor: 1.0,
                 renderer: BladeRenderer::new(gpu, gpu_extent),
                 input_handler: None,
@@ -339,14 +328,12 @@ impl X11WindowState {
 }
 
 impl PlatformWindow for X11Window {
-    fn bounds(&self) -> WindowBounds {
-        WindowBounds::Fixed(
-            self.0
-                .inner
-                .borrow_mut()
-                .bounds
-                .map(|v| GlobalPixels(v as f32)),
-        )
+    fn bounds(&self) -> Bounds<GlobalPixels> {
+        self.0
+            .inner
+            .borrow_mut()
+            .bounds
+            .map(|v| GlobalPixels(v as f32))
     }
 
     fn content_size(&self) -> Size<Pixels> {
@@ -454,6 +441,11 @@ impl PlatformWindow for X11Window {
         unimplemented!()
     }
 
+    // todo(linux)
+    fn is_full_screen(&self) -> bool {
+        unimplemented!()
+    }
+
     fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
         self.0.callbacks.borrow_mut().request_frame = Some(callback);
     }

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

@@ -3,7 +3,7 @@ use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
     ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacTextSystem, MacWindow, Menu,
     MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
-    PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, WindowOptions,
+    PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, WindowParams,
 };
 use anyhow::{anyhow, bail};
 use block::ConcreteBlock;
@@ -477,6 +477,10 @@ impl Platform for MacPlatform {
         }
     }
 
+    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        Some(Rc::new(MacDisplay::primary()))
+    }
+
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
         MacDisplay::all()
             .map(|screen| Rc::new(screen) as Rc<_>)
@@ -494,7 +498,7 @@ impl Platform for MacPlatform {
     fn open_window(
         &self,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Box<dyn PlatformWindow> {
         // Clippy thinks that this evaluates to `()`, for some reason.
         #[allow(clippy::unit_arg, clippy::clone_on_copy)]

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

@@ -4,8 +4,7 @@ use crate::{
     Bounds, DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels,
     KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
     MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
-    PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind,
-    WindowOptions,
+    PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowKind, WindowParams,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -419,23 +418,7 @@ impl MacWindowState {
         }
     }
 
-    fn bounds(&self) -> WindowBounds {
-        unsafe {
-            if self.is_fullscreen() {
-                return WindowBounds::Fullscreen;
-            }
-
-            let frame = self.frame();
-            let screen_size = self.native_window.screen().visibleFrame().into();
-            if frame.size == screen_size {
-                WindowBounds::Maximized
-            } else {
-                WindowBounds::Fixed(frame)
-            }
-        }
-    }
-
-    fn frame(&self) -> Bounds<GlobalPixels> {
+    fn bounds(&self) -> Bounds<GlobalPixels> {
         let frame = unsafe { NSWindow::frame(self.native_window) };
         global_bounds_from_ns_rect(frame)
     }
@@ -483,7 +466,15 @@ pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
 impl MacWindow {
     pub fn open(
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        WindowParams {
+            bounds,
+            titlebar,
+            kind,
+            is_movable,
+            display_id,
+            focus,
+            show,
+        }: WindowParams,
         executor: ForegroundExecutor,
         renderer_context: renderer::Context,
     ) -> Self {
@@ -491,7 +482,7 @@ impl MacWindow {
             let pool = NSAutoreleasePool::new(nil);
 
             let mut style_mask;
-            if let Some(titlebar) = options.titlebar.as_ref() {
+            if let Some(titlebar) = titlebar.as_ref() {
                 style_mask = NSWindowStyleMask::NSClosableWindowMask
                     | NSWindowStyleMask::NSMiniaturizableWindowMask
                     | NSWindowStyleMask::NSResizableWindowMask
@@ -505,7 +496,7 @@ impl MacWindow {
                     | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
             }
 
-            let native_window: id = match options.kind {
+            let native_window: id = match kind {
                 WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
                 WindowKind::PopUp => {
                     style_mask |= NSWindowStyleMaskNonactivatingPanel;
@@ -513,8 +504,7 @@ impl MacWindow {
                 }
             };
 
-            let display = options
-                .display_id
+            let display = display_id
                 .and_then(MacDisplay::find_by_id)
                 .unwrap_or_else(MacDisplay::primary);
 
@@ -530,23 +520,13 @@ impl MacWindow {
                 }
             }
 
-            let window_rect = match options.bounds {
-                WindowBounds::Fullscreen => {
-                    // Set a temporary size as we will asynchronously resize the window
-                    NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.))
-                }
-                WindowBounds::Maximized => {
-                    let display_bounds = display.bounds();
+            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)
                 }
-                WindowBounds::Fixed(bounds) => {
-                    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 native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
@@ -568,17 +548,8 @@ impl MacWindow {
             assert!(!native_view.is_null());
 
             let window_size = {
-                let bounds = match options.bounds {
-                    WindowBounds::Fullscreen | WindowBounds::Maximized => {
-                        native_window.screen().visibleFrame()
-                    }
-                    WindowBounds::Fixed(bounds) => global_bounds_to_ns_rect(bounds),
-                };
                 let scale = get_scale_factor(native_window);
-                size(
-                    bounds.size.width as f32 * scale,
-                    bounds.size.height as f32 * scale,
-                )
+                size(bounds.size.width.0 * scale, bounds.size.height.0 * scale)
             };
 
             let window = Self(Arc::new(Mutex::new(MacWindowState {
@@ -594,7 +565,7 @@ impl MacWindow {
                     native_view as *mut _,
                     window_size,
                 ),
-                kind: options.kind,
+                kind,
                 request_frame_callback: None,
                 event_callback: None,
                 activate_callback: None,
@@ -608,8 +579,7 @@ impl MacWindow {
                 last_key_equivalent: None,
                 synthetic_drag_counter: 0,
                 last_fresh_keydown: None,
-                traffic_light_position: options
-                    .titlebar
+                traffic_light_position: titlebar
                     .as_ref()
                     .and_then(|titlebar| titlebar.traffic_light_position),
                 previous_modifiers_changed_event: None,
@@ -628,20 +598,16 @@ impl MacWindow {
                 Arc::into_raw(window.0.clone()) as *const c_void,
             );
 
-            if let Some(title) = options
-                .titlebar
+            if let Some(title) = titlebar
                 .as_ref()
                 .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
             {
                 native_window.setTitle_(NSString::alloc(nil).init_str(title));
             }
 
-            native_window.setMovable_(options.is_movable as BOOL);
+            native_window.setMovable_(is_movable as BOOL);
 
-            if options
-                .titlebar
-                .map_or(true, |titlebar| titlebar.appears_transparent)
-            {
+            if titlebar.map_or(true, |titlebar| titlebar.appears_transparent) {
                 native_window.setTitlebarAppearsTransparent_(YES);
                 native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
             }
@@ -663,11 +629,7 @@ impl MacWindow {
             native_window.setContentView_(native_view.autorelease());
             native_window.makeFirstResponder_(native_view);
 
-            if options.center {
-                native_window.center();
-            }
-
-            match options.kind {
+            match kind {
                 WindowKind::Normal => {
                     native_window.setLevel_(NSNormalWindowLevel);
                     native_window.setAcceptsMouseMovedEvents_(YES);
@@ -698,18 +660,13 @@ impl MacWindow {
                     );
                 }
             }
-            if options.focus {
+
+            if focus {
                 native_window.makeKeyAndOrderFront_(nil);
-            } else if options.show {
+            } else if show {
                 native_window.orderFront_(nil);
             }
 
-            if options.bounds == WindowBounds::Fullscreen {
-                // We need to toggle full screen asynchronously as doing so may
-                // call back into the platform handlers.
-                window.toggle_full_screen();
-            }
-
             window.0.lock().move_traffic_light();
 
             pool.drain();
@@ -754,7 +711,7 @@ impl Drop for MacWindow {
 }
 
 impl PlatformWindow for MacWindow {
-    fn bounds(&self) -> WindowBounds {
+    fn bounds(&self) -> Bounds<GlobalPixels> {
         self.0.as_ref().lock().bounds()
     }
 
@@ -995,6 +952,17 @@ impl PlatformWindow for MacWindow {
             .detach();
     }
 
+    fn is_full_screen(&self) -> bool {
+        let this = self.0.lock();
+        let window = this.native_window;
+
+        unsafe {
+            window
+                .styleMask()
+                .contains(NSWindowStyleMask::NSFullScreenWindowMask)
+        }
+    }
+
     fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
         self.0.as_ref().lock().request_frame_callback = Some(callback);
     }

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

@@ -1,7 +1,7 @@
 use crate::{
     AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
     Keymap, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow,
-    WindowAppearance, WindowOptions,
+    WindowAppearance, WindowParams,
 };
 use anyhow::{anyhow, Result};
 use collections::VecDeque;
@@ -161,6 +161,10 @@ impl Platform for TestPlatform {
         vec![self.active_display.clone()]
     }
 
+    fn primary_display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
+        Some(self.active_display.clone())
+    }
+
     fn display(&self, id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
         self.displays().iter().find(|d| d.id() == id).cloned()
     }
@@ -175,11 +179,11 @@ impl Platform for TestPlatform {
     fn open_window(
         &self,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        params: WindowParams,
     ) -> Box<dyn crate::PlatformWindow> {
         let window = TestWindow::new(
-            options,
             handle,
+            params,
             self.weak.clone(),
             self.active_display.clone(),
         );

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

@@ -1,7 +1,7 @@
 use crate::{
-    px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, Pixels, PlatformAtlas,
-    PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, Size,
-    TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
+    AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, GlobalPixels, Pixels,
+    PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
+    Size, TestPlatform, TileId, WindowAppearance, WindowParams,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
@@ -12,7 +12,7 @@ use std::{
 };
 
 pub(crate) struct TestWindowState {
-    pub(crate) bounds: WindowBounds,
+    pub(crate) bounds: Bounds<GlobalPixels>,
     pub(crate) handle: AnyWindowHandle,
     display: Rc<dyn PlatformDisplay>,
     pub(crate) title: Option<String>,
@@ -25,6 +25,7 @@ pub(crate) struct TestWindowState {
     resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
     moved_callback: Option<Box<dyn FnMut()>>,
     input_handler: Option<PlatformInputHandler>,
+    is_fullscreen: bool,
 }
 
 #[derive(Clone)]
@@ -48,13 +49,13 @@ impl HasDisplayHandle for TestWindow {
 
 impl TestWindow {
     pub fn new(
-        options: WindowOptions,
         handle: AnyWindowHandle,
+        params: WindowParams,
         platform: Weak<TestPlatform>,
         display: Rc<dyn PlatformDisplay>,
     ) -> Self {
         Self(Arc::new(Mutex::new(TestWindowState {
-            bounds: options.bounds,
+            bounds: params.bounds,
             display,
             platform,
             handle,
@@ -67,6 +68,7 @@ impl TestWindow {
             resize_callback: None,
             moved_callback: None,
             input_handler: None,
+            is_fullscreen: false,
         })))
     }
 
@@ -76,17 +78,7 @@ impl TestWindow {
         let Some(mut callback) = lock.resize_callback.take() else {
             return;
         };
-        match &mut lock.bounds {
-            WindowBounds::Fullscreen | WindowBounds::Maximized => {
-                lock.bounds = WindowBounds::Fixed(Bounds {
-                    origin: Point::default(),
-                    size: size.map(|pixels| f64::from(pixels).into()),
-                });
-            }
-            WindowBounds::Fixed(bounds) => {
-                bounds.size = size.map(|pixels| f64::from(pixels).into());
-            }
-        }
+        lock.bounds.size = size.map(|pixels| f64::from(pixels).into());
         drop(lock);
         callback(size, scale_factor);
         self.0.lock().resize_callback = Some(callback);
@@ -115,16 +107,12 @@ impl TestWindow {
 }
 
 impl PlatformWindow for TestWindow {
-    fn bounds(&self) -> WindowBounds {
+    fn bounds(&self) -> Bounds<GlobalPixels> {
         self.0.lock().bounds
     }
 
     fn content_size(&self) -> Size<Pixels> {
-        let bounds = match self.bounds() {
-            WindowBounds::Fixed(bounds) => bounds,
-            WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
-        };
-        bounds.size.map(|p| px(p.0))
+        self.bounds().size.into()
     }
 
     fn scale_factor(&self) -> f32 {
@@ -210,7 +198,12 @@ impl PlatformWindow for TestWindow {
     }
 
     fn toggle_full_screen(&self) {
-        unimplemented!()
+        let mut lock = self.0.lock();
+        lock.is_fullscreen = !lock.is_fullscreen;
+    }
+
+    fn is_full_screen(&self) -> bool {
+        self.0.lock().is_fullscreen
     }
 
     fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}

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

@@ -53,8 +53,8 @@ use windows::{
 use crate::{
     try_get_window_inner, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle,
     ForegroundExecutor, Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
-    PlatformTextSystem, PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher,
-    WindowsDisplay, WindowsTextSystem, WindowsWindow,
+    PlatformTextSystem, PlatformWindow, Task, WindowAppearance, WindowOptions, WindowParams,
+    WindowsDispatcher, WindowsDisplay, WindowsTextSystem, WindowsWindow,
 };
 
 pub(crate) struct WindowsPlatform {
@@ -327,15 +327,20 @@ impl Platform for WindowsPlatform {
         Some(Rc::new(WindowsDisplay::new()))
     }
 
+    // todo(windows)
+    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+        Some(Rc::new(WindowsDisplay::new()))
+    }
+
     // todo(windows)
     fn active_window(&self) -> Option<AnyWindowHandle> {
-        unimplemented!()
+        None
     }
 
     fn open_window(
         &self,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Box<dyn PlatformWindow> {
         Box::new(WindowsWindow::new(self.inner.clone(), handle, options))
     }

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

@@ -48,7 +48,7 @@ use windows::{
             WindowsAndMessaging::{
                 CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage,
                 RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW,
-                CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WHEEL_DELTA,
+                GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WHEEL_DELTA,
                 WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WM_CHAR, WM_CLOSE, WM_DESTROY, WM_KEYDOWN,
                 WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP,
                 WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOVE, WM_NCCREATE, WM_NCDESTROY,
@@ -65,7 +65,7 @@ use crate::{
     KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
     NavigationDirection, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
     PlatformInputHandler, PlatformWindow, Point, PromptLevel, Scene, ScrollDelta, Size, TouchPhase,
-    WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay, WindowsPlatformInner,
+    WindowAppearance, WindowParams, WindowsDisplay, WindowsPlatformInner,
 };
 
 #[derive(PartialEq)]
@@ -614,7 +614,7 @@ impl WindowsWindow {
     pub(crate) fn new(
         platform_inner: Rc<WindowsPlatformInner>,
         handle: AnyWindowHandle,
-        options: WindowOptions,
+        options: WindowParams,
     ) -> Self {
         let dwexstyle = WINDOW_EX_STYLE::default();
         let classname = register_wnd_class();
@@ -627,20 +627,10 @@ impl WindowsWindow {
                 .unwrap_or(""),
         );
         let dwstyle = WS_OVERLAPPEDWINDOW & !WS_VISIBLE;
-        let mut x = CW_USEDEFAULT;
-        let mut y = CW_USEDEFAULT;
-        let mut nwidth = CW_USEDEFAULT;
-        let mut nheight = CW_USEDEFAULT;
-        match options.bounds {
-            WindowBounds::Fullscreen => {}
-            WindowBounds::Maximized => {}
-            WindowBounds::Fixed(bounds) => {
-                x = bounds.origin.x.0 as i32;
-                y = bounds.origin.y.0 as i32;
-                nwidth = bounds.size.width.0 as i32;
-                nheight = bounds.size.height.0 as i32;
-            }
-        };
+        let x = options.bounds.origin.x.0 as i32;
+        let y = options.bounds.origin.y.0 as i32;
+        let nwidth = options.bounds.size.width.0 as i32;
+        let nheight = options.bounds.size.height.0 as i32;
         let hwndparent = HWND::default();
         let hmenu = HMENU::default();
         let hinstance = HINSTANCE::default();
@@ -684,11 +674,7 @@ impl WindowsWindow {
             .window_handle_values
             .borrow_mut()
             .insert(wnd.inner.hwnd.0);
-        match options.bounds {
-            WindowBounds::Fullscreen => wnd.toggle_full_screen(),
-            WindowBounds::Maximized => wnd.maximize(),
-            WindowBounds::Fixed(_) => {}
-        }
+
         unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
         wnd
     }
@@ -728,11 +714,11 @@ impl Drop for WindowsWindow {
 }
 
 impl PlatformWindow for WindowsWindow {
-    fn bounds(&self) -> WindowBounds {
-        WindowBounds::Fixed(Bounds {
+    fn bounds(&self) -> Bounds<GlobalPixels> {
+        Bounds {
             origin: self.inner.origin.get(),
             size: self.inner.size.get(),
-        })
+        }
     }
 
     // todo(windows)
@@ -887,6 +873,11 @@ impl PlatformWindow for WindowsWindow {
     // todo(windows)
     fn toggle_full_screen(&self) {}
 
+    // todo(windows)
+    fn is_full_screen(&self) -> bool {
+        false
+    }
+
     // todo(windows)
     fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
         self.inner.callbacks.borrow_mut().request_frame = Some(callback);

crates/gpui/src/window.rs 🔗

@@ -1,13 +1,13 @@
 use crate::{
-    px, 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,
-    Hsla, KeyBinding, KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model,
-    ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
-    PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels,
-    SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle,
-    TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
-    WindowOptions, WindowTextSystem,
+    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,
+    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};
 use collections::FxHashSet;
@@ -253,7 +253,6 @@ pub struct Window {
     mouse_hit_test: HitTest,
     modifiers: Modifiers,
     scale_factor: f32,
-    bounds: WindowBounds,
     bounds_observers: SubscriberSet<(), AnyObserver>,
     appearance: WindowAppearance,
     appearance_observers: SubscriberSet<(), AnyObserver>,
@@ -315,20 +314,69 @@ 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));
+
+    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()
+                .map(|display| {
+                    let center = display.bounds().center();
+                    let offset = DEFAULT_WINDOW_SIZE / 2.0;
+                    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,
+                    )
+                })
+        })
+}
+
+// Fixed, Maximized, Fullscreen, and 'Inherent / default'
+// Platform part, you don't, you only need Fixed, Maximized, Fullscreen
+
 impl Window {
     pub(crate) fn new(
         handle: AnyWindowHandle,
         options: WindowOptions,
         cx: &mut AppContext,
     ) -> Self {
-        let platform_window = cx.platform.open_window(handle, options);
+        let WindowOptions {
+            bounds,
+            titlebar,
+            focus,
+            show,
+            kind,
+            is_movable,
+            display_id,
+            fullscreen,
+        } = options;
+
+        let bounds = bounds.unwrap_or_else(|| default_bounds(cx));
+        let platform_window = cx.platform.open_window(
+            handle,
+            WindowParams {
+                bounds,
+                titlebar,
+                kind,
+                is_movable,
+                focus,
+                show,
+                display_id,
+            },
+        );
         let display_id = platform_window.display().id();
         let sprite_atlas = platform_window.sprite_atlas();
         let mouse_position = platform_window.mouse_position();
         let modifiers = platform_window.modifiers();
         let content_size = platform_window.content_size();
         let scale_factor = platform_window.scale_factor();
-        let bounds = platform_window.bounds();
         let appearance = platform_window.appearance();
         let text_system = Arc::new(WindowTextSystem::new(cx.text_system().clone()));
         let dirty = Rc::new(Cell::new(true));
@@ -337,6 +385,10 @@ impl Window {
         let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
         let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
 
+        if fullscreen {
+            platform_window.toggle_full_screen();
+        }
+
         platform_window.on_close(Box::new({
             let mut cx = cx.to_async();
             move || {
@@ -457,7 +509,6 @@ impl Window {
             mouse_hit_test: HitTest::default(),
             modifiers,
             scale_factor,
-            bounds,
             bounds_observers: SubscriberSet::new(),
             appearance,
             appearance_observers: SubscriberSet::new(),
@@ -740,7 +791,6 @@ impl<'a> WindowContext<'a> {
     fn window_bounds_changed(&mut self) {
         self.window.scale_factor = self.window.platform_window.scale_factor();
         self.window.viewport_size = self.window.platform_window.content_size();
-        self.window.bounds = self.window.platform_window.bounds();
         self.window.display_id = self.window.platform_window.display().id();
         self.refresh();
 
@@ -751,8 +801,13 @@ 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) -> WindowBounds {
-        self.window.bounds
+    pub fn window_bounds(&self) -> Bounds<GlobalPixels> {
+        self.window.platform_window.bounds()
+    }
+
+    /// Retusn whether or not the window is currently fullscreen
+    pub fn is_full_screen(&self) -> bool {
+        self.window.platform_window.is_full_screen()
     }
 
     fn appearance_changed(&mut self) {

crates/storybook/src/storybook.rs 🔗

@@ -7,8 +7,7 @@ mod story_selector;
 use clap::Parser;
 use dialoguer::FuzzySelect;
 use gpui::{
-    div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowBounds,
-    WindowOptions,
+    div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowOptions,
 };
 use log::LevelFilter;
 use settings::{default_settings, KeymapFile, Settings, SettingsStore};
@@ -85,12 +84,11 @@ fn main() {
         load_storybook_keymap(cx);
         cx.set_menus(app_menus());
 
+        let size = size(px(1500.), px(780.));
+        let bounds = Bounds::centered(size, cx);
         let _window = cx.open_window(
             WindowOptions {
-                bounds: WindowBounds::Fixed(Bounds {
-                    origin: Default::default(),
-                    size: size(px(1500.), px(780.)).into(),
-                }),
+                bounds: Some(bounds),
                 ..Default::default()
             },
             move |cx| {

crates/workspace/src/persistence.rs 🔗

@@ -4,7 +4,7 @@ use std::path::Path;
 
 use anyhow::{anyhow, bail, Context, Result};
 use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
-use gpui::{point, size, Axis, Bounds, WindowBounds};
+use gpui::{point, size, Axis, Bounds};
 
 use sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
@@ -59,7 +59,7 @@ impl sqlez::bindable::Column for SerializedAxis {
 }
 
 #[derive(Clone, Debug, PartialEq)]
-pub(crate) struct SerializedWindowsBounds(pub(crate) WindowBounds);
+pub(crate) struct SerializedWindowsBounds(pub(crate) Bounds<gpui::GlobalPixels>);
 
 impl StaticColumnCount for SerializedWindowsBounds {
     fn column_count() -> usize {
@@ -69,30 +69,15 @@ impl StaticColumnCount for SerializedWindowsBounds {
 
 impl Bind for SerializedWindowsBounds {
     fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
-        let (region, next_index) = match self.0 {
-            WindowBounds::Fullscreen => {
-                let next_index = statement.bind(&"Fullscreen", start_index)?;
-                (None, next_index)
-            }
-            WindowBounds::Maximized => {
-                let next_index = statement.bind(&"Maximized", start_index)?;
-                (None, next_index)
-            }
-            WindowBounds::Fixed(region) => {
-                let next_index = statement.bind(&"Fixed", start_index)?;
-                (Some(region), next_index)
-            }
-        };
+        let next_index = statement.bind(&"Fixed", start_index)?;
 
         statement.bind(
-            &region.map(|region| {
-                (
-                    SerializedGlobalPixels(region.origin.x),
-                    SerializedGlobalPixels(region.origin.y),
-                    SerializedGlobalPixels(region.size.width),
-                    SerializedGlobalPixels(region.size.height),
-                )
-            }),
+            &(
+                SerializedGlobalPixels(self.0.origin.x),
+                SerializedGlobalPixels(self.0.origin.y),
+                SerializedGlobalPixels(self.0.size.width),
+                SerializedGlobalPixels(self.0.size.height),
+            ),
             next_index,
         )
     }
@@ -102,18 +87,16 @@ impl Column for SerializedWindowsBounds {
     fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
         let (window_state, next_index) = String::column(statement, start_index)?;
         let bounds = match window_state.as_str() {
-            "Fullscreen" => SerializedWindowsBounds(WindowBounds::Fullscreen),
-            "Maximized" => SerializedWindowsBounds(WindowBounds::Maximized),
             "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;
-                SerializedWindowsBounds(WindowBounds::Fixed(Bounds {
+                SerializedWindowsBounds(Bounds {
                     origin: point(x.into(), y.into()),
                     size: size(width.into(), height.into()),
-                }))
+                })
             }
             _ => bail!("Window State did not have a valid string"),
         };

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

@@ -6,7 +6,7 @@ use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
+use gpui::{AsyncWindowContext, Bounds, GlobalPixels, 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<WindowBounds>,
+    pub(crate) bounds: Option<Bounds<GlobalPixels>>,
     pub(crate) display: Option<Uuid>,
     pub(crate) docks: DockStructure,
 }

crates/workspace/src/workspace.rs 🔗

@@ -32,7 +32,7 @@ use gpui::{
     FocusableView, Global, GlobalPixels, InteractiveElement, IntoElement, KeyContext, Keystroke,
     LayoutId, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point,
     PromptLevel, Render, SharedString, Size, Styled, Subscription, Task, View, ViewContext,
-    VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
+    VisualContext, WeakView, WindowContext, WindowHandle, WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
@@ -362,8 +362,7 @@ pub struct AppState {
     pub user_store: Model<UserStore>,
     pub workspace_store: Model<WorkspaceStore>,
     pub fs: Arc<dyn fs::Fs>,
-    pub build_window_options:
-        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
+    pub build_window_options: fn(Option<Uuid>, &mut AppContext) -> WindowOptions,
     pub node_runtime: Arc<dyn NodeRuntime>,
 }
 
@@ -424,7 +423,7 @@ impl AppState {
             user_store,
             workspace_store,
             node_runtime: FakeNodeRuntime::new(),
-            build_window_options: |_, _, _| Default::default(),
+            build_window_options: |_, _| Default::default(),
         })
     }
 }
@@ -690,18 +689,16 @@ impl Workspace {
             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 bounds = cx.window_bounds();
-                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
-                        let display_bounds = display.bounds();
-                        window_bounds.origin.x -= display_bounds.origin.x;
-                        window_bounds.origin.y -= display_bounds.origin.y;
-                    }
+                    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;
 
                     if let Some(display_uuid) = display.uuid().log_err() {
                         cx.background_executor()
                             .spawn(DB.set_window_bounds(
                                 workspace_id,
-                                SerializedWindowsBounds(bounds),
+                                SerializedWindowsBounds(window_bounds),
                                 display_uuid,
                             ))
                             .detach_and_log_err(cx);
@@ -847,19 +844,16 @@ impl Workspace {
 
                             // Stored bounds are relative to the containing display.
                             // So convert back to global coordinates if that screen still exists
-                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                                let screen = cx
-                                    .update(|cx| {
-                                        cx.displays().into_iter().find(|display| {
-                                            display.uuid().ok() == Some(serialized_display)
-                                        })
+                            let screen = cx
+                                .update(|cx| {
+                                    cx.displays().into_iter().find(|display| {
+                                        display.uuid().ok() == Some(serialized_display)
                                     })
-                                    .ok()??;
-                                let screen_bounds = screen.bounds();
-                                window_bounds.origin.x += screen_bounds.origin.x;
-                                window_bounds.origin.y += screen_bounds.origin.y;
-                                bounds = WindowBounds::Fixed(window_bounds);
-                            }
+                                })
+                                .ok()??;
+                            let screen_bounds = screen.bounds();
+                            bounds.origin.x += screen_bounds.origin.x;
+                            bounds.origin.y += screen_bounds.origin.y;
 
                             Some((bounds, serialized_display))
                         })
@@ -867,9 +861,8 @@ impl Workspace {
                 };
 
                 // Use the serialized workspace to construct the new window
-                let options =
-                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
-
+                let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?;
+                options.bounds = bounds;
                 cx.open_window(options, {
                     let app_state = app_state.clone();
                     let project_handle = project_handle.clone();
@@ -3610,7 +3603,7 @@ impl Workspace {
             client,
             user_store,
             fs: project.read(cx).fs().clone(),
-            build_window_options: |_, _, _| Default::default(),
+            build_window_options: |_, _| Default::default(),
             node_runtime: FakeNodeRuntime::new(),
         });
         let workspace = Self::new(0, project, app_state, cx);
@@ -3663,17 +3656,15 @@ impl Workspace {
     }
 }
 
-fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
+fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<Bounds<GlobalPixels>> {
     let display_origin = cx
         .update(|cx| Some(cx.displays().first()?.bounds().origin))
         .ok()??;
     ZED_WINDOW_POSITION
         .zip(*ZED_WINDOW_SIZE)
-        .map(|(position, size)| {
-            WindowBounds::Fixed(Bounds {
-                origin: display_origin + position,
-                size,
-            })
+        .map(|(position, size)| Bounds {
+            origin: display_origin + position,
+            size,
         })
 }
 
@@ -4553,7 +4544,8 @@ pub fn join_hosted_project(
 
             let window_bounds_override = window_bounds_env_override(&cx);
             cx.update(|cx| {
-                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
+                let mut options = (app_state.build_window_options)(None, cx);
+                options.bounds = window_bounds_override;
                 cx.open_window(options, |cx| {
                     cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
                 })
@@ -4611,7 +4603,8 @@ pub fn join_in_room_project(
 
             let window_bounds_override = window_bounds_env_override(&cx);
             cx.update(|cx| {
-                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
+                let mut options = (app_state.build_window_options)(None, cx);
+                options.bounds = window_bounds_override;
                 cx.open_window(options, |cx| {
                     cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
                 })

crates/zed/src/zed.rs 🔗

@@ -10,7 +10,7 @@ use collections::VecDeque;
 use editor::{scroll::Autoscroll, Editor, MultiBuffer};
 use gpui::{
     actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, PromptLevel,
-    TitlebarOptions, View, ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions,
+    TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions,
 };
 pub use only_instance::*;
 pub use open_listener::*;
@@ -79,12 +79,7 @@ pub fn init(cx: &mut AppContext) {
     cx.on_action(quit);
 }
 
-pub fn build_window_options(
-    bounds: Option<WindowBounds>,
-    display_uuid: Option<Uuid>,
-    cx: &mut AppContext,
-) -> WindowOptions {
-    let bounds = bounds.unwrap_or(WindowBounds::Maximized);
+pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut AppContext) -> WindowOptions {
     let display = display_uuid.and_then(|uuid| {
         cx.displays()
             .into_iter()
@@ -92,18 +87,18 @@ pub fn build_window_options(
     });
 
     WindowOptions {
-        bounds,
         titlebar: Some(TitlebarOptions {
             title: None,
             appears_transparent: true,
             traffic_light_position: Some(point(px(9.5), px(9.5))),
         }),
-        center: false,
+        bounds: None,
         focus: false,
         show: false,
         kind: WindowKind::Normal,
         is_movable: true,
         display_id: display.map(|display| display.id()),
+        fullscreen: false,
     }
 }