diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 469c4176ab..1ffb2d281d 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/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(), }); diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 47a12c2cb1..e11d449924 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/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 diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 8bccc00e3f..6692e8bf38 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/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, } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index c1db51fef8..9a096c347c 100644 --- a/crates/editor/src/editor_tests.rs +++ b/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()), )), diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index c9b066f56d..55de313bcc 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/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); diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs index 9361cbf437..202dd2f691 100644 --- a/crates/gpui/examples/hello_world.rs +++ b/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(), + }) + }, + ); }); } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 5c7db5d92d..1c30f16199 100644 --- a/crates/gpui/src/app.rs +++ b/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> { + self.platform.primary_display() + } + /// Returns the appearance of the application's windows. pub fn window_appearance(&self) -> WindowAppearance { self.platform.window_appearance() diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 62f82f9cbd..7a4255c69b 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/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(); diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index c216e0684d..9833ae9f72 100644 --- a/crates/gpui/src/geometry.rs +++ b/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 { /// assert_eq!(p.x, 10); /// assert_eq!(p.y, 20); /// ``` -pub fn point(x: T, y: T) -> Point { +pub const fn point(x: T, y: T) -> Point { Point { x, y } } @@ -354,6 +357,15 @@ pub struct Size { pub height: T, } +impl From> for Size { + fn from(size: Size) -> Self { + Size { + width: Pixels(size.width.0), + height: Pixels(size.height.0), + } + } +} + /// Constructs a new `Size` with the provided width and height. /// /// # Arguments @@ -369,7 +381,7 @@ pub struct Size { /// assert_eq!(my_size.width, 10); /// assert_eq!(my_size.height, 20); /// ``` -pub fn size(width: T, height: T) -> Size +pub const fn size(width: T, height: T) -> Size where T: Clone + Default + Debug, { @@ -662,6 +674,35 @@ pub struct Bounds { pub size: Size, } +impl Bounds { + /// Generate a centered bounds for the primary display + pub fn centered(size: impl Into>, 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 Bounds where T: Clone + Debug + Sub + 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) -> Point) -> Bounds { + Bounds { + origin: f(self.origin), + size: self.size, + } + } } /// Checks if the bounds represent an empty area. diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index cfeaf6653e..85b8f871be 100644 --- a/crates/gpui/src/interactive.rs +++ b/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, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 176d391d82..90b036a78b 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -88,12 +88,13 @@ pub(crate) trait Platform: 'static { fn unhide_other_apps(&self); fn displays(&self) -> Vec>; + fn primary_display(&self) -> Option>; fn display(&self, id: DisplayId) -> Option>; fn active_window(&self) -> Option; fn open_window( &self, handle: AnyWindowHandle, - options: WindowOptions, + options: WindowParams, ) -> Box; /// 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; fn content_size(&self) -> Size; 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); fn on_input(&self, callback: Box bool>); fn on_active_status_change(&self, callback: Box); @@ -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>, /// The titlebar configuration of the window pub titlebar: Option, - /// 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, } +/// The variables that can be configured when creating a new window +#[derive(Debug)] +pub(crate) struct WindowParams { + /// + pub bounds: Bounds, + + /// The titlebar configuration of the window + pub titlebar: Option, + + /// 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, +} + 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), -} +/// Platform level interface +/// bounds: Bounds +/// full_screen: bool /// The appearance of the window, as defined by the operating system. /// diff --git a/crates/gpui/src/platform/linux/client.rs b/crates/gpui/src/platform/linux/client.rs index b5d154b7a7..acc5a2d955 100644 --- a/crates/gpui/src/platform/linux/client.rs +++ b/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>; + fn primary_display(&self) -> Option>; fn display(&self, id: DisplayId) -> Option>; fn open_window( &self, handle: AnyWindowHandle, - options: WindowOptions, + options: WindowParams, ) -> Box; fn set_cursor_style(&self, style: CursorStyle); fn get_clipboard(&self) -> Rc>; diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index aa82f5870c..622504efaf 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/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> { + self.client.primary_display() + } + fn displays(&self) -> Vec> { self.client.displays() } @@ -172,7 +176,7 @@ impl Platform for LinuxPlatform { fn open_window( &self, handle: AnyWindowHandle, - options: WindowOptions, + options: WindowParams, ) -> Box { self.client.open_window(handle, options) } diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index b2988bc5f8..23b2c5dc47 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/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> { + None + } + fn open_window( &self, handle: AnyWindowHandle, - options: WindowOptions, + options: WindowParams, ) -> Box { let mut state = self.state.client_state_inner.borrow_mut(); diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index d295326671..8959c08eb9 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/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, viewport: Option, toplevel: Arc, - 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 = 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 { + unimplemented!() } fn content_size(&self) -> Size { @@ -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) { self.0.callbacks.borrow_mut().request_frame = Some(callback); } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index ddae8cbb31..89b35d8ed2 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/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 + .filter_map(|(root_id, _)| { + Some( + Rc::new(X11Display::new(&self.xcb_connection, root_id as i32)?) + as Rc, + ) }) .collect() } fn display(&self, id: DisplayId) -> Option> { - 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> { + 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 { 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, diff --git a/crates/gpui/src/platform/linux/x11/display.rs b/crates/gpui/src/platform/linux/x11/display.rs index 85ae754192..7ec29b71f4 100644 --- a/crates/gpui/src/platform/linux/x11/display.rs +++ b/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 { + 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]), - } + }) } } diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index e53833be6e..6b9e958b20 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/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, 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 { + self.0 + .inner + .borrow_mut() + .bounds + .map(|v| GlobalPixels(v as f32)) } fn content_size(&self) -> Size { @@ -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) { self.0.callbacks.borrow_mut().request_frame = Some(callback); } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index d293f4cf40..427c95df71 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/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> { + Some(Rc::new(MacDisplay::primary())) + } + fn displays(&self) -> Vec> { 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 { // Clippy thinks that this evaluates to `()`, for some reason. #[allow(clippy::unit_arg, clippy::clone_on_copy)] diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index cbe8dfae14..196c861a93 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/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 { + fn bounds(&self) -> Bounds { let frame = unsafe { NSWindow::frame(self.native_window) }; global_bounds_from_ns_rect(frame) } @@ -483,7 +466,15 @@ pub(crate) struct MacWindow(Arc>); 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,16 +660,11 @@ impl MacWindow { ); } } - if options.focus { - native_window.makeKeyAndOrderFront_(nil); - } else if options.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(); + if focus { + native_window.makeKeyAndOrderFront_(nil); + } else if show { + native_window.orderFront_(nil); } window.0.lock().move_traffic_light(); @@ -754,7 +711,7 @@ impl Drop for MacWindow { } impl PlatformWindow for MacWindow { - fn bounds(&self) -> WindowBounds { + fn bounds(&self) -> Bounds { 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) { self.0.as_ref().lock().request_frame_callback = Some(callback); } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 8726d472b7..ec870fb44d 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/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> { + Some(self.active_display.clone()) + } + fn display(&self, id: DisplayId) -> Option> { 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 { let window = TestWindow::new( - options, handle, + params, self.weak.clone(), self.active_display.clone(), ); diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 4dac341d69..08fb108c88 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/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, pub(crate) handle: AnyWindowHandle, display: Rc, pub(crate) title: Option, @@ -25,6 +25,7 @@ pub(crate) struct TestWindowState { resize_callback: Option, f32)>>, moved_callback: Option>, input_handler: Option, + 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, display: Rc, ) -> 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 { self.0.lock().bounds } fn content_size(&self) -> Size { - 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) {} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 4e08aff282..34ba80d89b 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/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> { + Some(Rc::new(WindowsDisplay::new())) + } + // todo(windows) fn active_window(&self) -> Option { - unimplemented!() + None } fn open_window( &self, handle: AnyWindowHandle, - options: WindowOptions, + options: WindowParams, ) -> Box { Box::new(WindowsWindow::new(self.inner.clone(), handle, options)) } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index a4994f001c..b159a29ca2 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/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, 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 { + 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) { self.inner.callbacks.borrow_mut().request_frame = Some(callback); diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8a2a6f8f0d..60cab84125 100644 --- a/crates/gpui/src/window.rs +++ b/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 { + const DEFAULT_WINDOW_SIZE: Size = size(GlobalPixels(1024.0), GlobalPixels(700.0)); + const DEFAULT_WINDOW_OFFSET: Point = 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>> = 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 { + 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) { diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 2bd60cc680..3e9d89bfe8 100644 --- a/crates/storybook/src/storybook.rs +++ b/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| { diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 9f99233c27..2e6375b999 100644 --- a/crates/workspace/src/persistence.rs +++ b/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); 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 { - 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( - ®ion.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"), }; diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 6c917b495d..c03b1dc116 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/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, + pub(crate) bounds: Option>, pub(crate) display: Option, pub(crate) docks: DockStructure, } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b319f46440..87cfdd9c13 100644 --- a/crates/workspace/src/workspace.rs +++ b/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, pub workspace_store: Model, pub fs: Arc, - pub build_window_options: - fn(Option, Option, &mut AppContext) -> WindowOptions, + pub build_window_options: fn(Option, &mut AppContext) -> WindowOptions, pub node_runtime: Arc, } @@ -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 { +fn window_bounds_env_override(cx: &AsyncAppContext) -> Option> { 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)) }) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 67b323a313..a9deb6818c 100644 --- a/crates/zed/src/zed.rs +++ b/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, - display_uuid: Option, - cx: &mut AppContext, -) -> WindowOptions { - let bounds = bounds.unwrap_or(WindowBounds::Maximized); +pub fn build_window_options(display_uuid: Option, 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, } }