From 63a5f46df4a694cf5ccfea43628a2ac8a4f053da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Wed, 8 May 2024 13:29:03 +0800 Subject: [PATCH] Remember window restore size (#10429) Now, regardless of how the Zed window is closed, Zed can remember the window's restore size. - [x] Windows implementation - [x] macOS implementation - [x] Linux implementation (partial) - [x] update SQL data base (mark column `fullscreen` as deprecated) The current implementation on Linux is basic, and I'm not sure if it's correct. The variable `fullscreen` in SQL can be removed, but I'm unsure how to do it. edit: mark `fullscreen` as deprecated ### Case 1 When the window is closed as maximized, reopening it will open in the maximized state, and returning from maximized state will restore the position and size it had when it was maximized. https://github.com/zed-industries/zed/assets/14981363/7207752e-878a-4d43-93a7-41ad1fdb3a06 ### Case 2 When the window is closed as fullscreen, reopening it will open in fullscreen mode, and toggling fullscreen will restore the position and size it had when it entered fullscreen (note that the fullscreen application was not recorded in the video, showing a black screen, but it had actually entered fullscreen mode). https://github.com/zed-industries/zed/assets/14981363/ea5aa70d-b296-462a-afb3-4c3372883ea3 ### What's more - As English is not my native language, some variable and struct names may need to be modified to match their actual meaning. - I am not familiar with the APIs related to macOS and Linux, so implementation for these two platforms has not been done for now. - Any suggestions and ideas are welcome. Release Notes: - N/A --- crates/collab_ui/src/collab_ui.rs | 5 +- crates/editor/src/editor_tests.rs | 6 +- crates/gpui/examples/animation.rs | 6 +- crates/gpui/examples/hello_world.rs | 2 +- crates/gpui/examples/image/image.rs | 4 +- crates/gpui/examples/window_positioning.rs | 3 +- crates/gpui/src/app/test_context.rs | 10 +- crates/gpui/src/platform.rs | 45 ++++-- .../gpui/src/platform/linux/wayland/window.rs | 30 +++- crates/gpui/src/platform/linux/x11/window.rs | 9 +- crates/gpui/src/platform/mac/window.rs | 57 +++---- crates/gpui/src/platform/test/window.rs | 8 +- crates/gpui/src/platform/windows/window.rs | 85 +++++++--- crates/gpui/src/window.rs | 37 +++-- crates/storybook/src/storybook.rs | 5 +- crates/workspace/src/persistence.rs | 148 +++++++++++------- crates/workspace/src/persistence/model.rs | 7 +- crates/workspace/src/workspace.rs | 68 ++++---- crates/zed/src/zed.rs | 3 +- 19 files changed, 319 insertions(+), 219 deletions(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index d116bb2e62ba3718320190e0e79a87073bc625a7..94f7c7ba32f4bba3fe0438f545d47048571c1ed4 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -14,7 +14,7 @@ pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; use gpui::{ actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task, - WindowBackgroundAppearance, WindowContext, WindowKind, WindowOptions, + WindowBackgroundAppearance, WindowBounds, WindowContext, WindowKind, WindowOptions, }; use panel_settings::MessageEditorSettings; pub use panel_settings::{ @@ -117,14 +117,13 @@ fn notification_window_options( let app_id = ReleaseChannel::global(cx).app_id(); WindowOptions { - bounds: Some(bounds), + window_bounds: Some(WindowBounds::Windowed(bounds)), titlebar: None, focus: false, show: true, kind: WindowKind::PopUp, is_movable: false, display_id: Some(screen.id()), - fullscreen: false, window_background: WindowBackgroundAppearance::default(), app_id: Some(app_id.to_owned()), } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index be2f8d68fa17c90ea86f166c0afb2befca673e36..06c79bf556cdd6ccf1261eb94a3c0c68d9162444 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -9,7 +9,7 @@ use crate::{ JoinLines, }; use futures::StreamExt; -use gpui::{div, TestAppContext, VisualTestContext, WindowOptions}; +use gpui::{div, TestAppContext, VisualTestContext, WindowBounds, WindowOptions}; use indoc::indoc; use language::{ language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent}, @@ -7493,10 +7493,10 @@ async fn test_following(cx: &mut gpui::TestAppContext) { let follower = cx.update(|cx| { cx.open_window( WindowOptions { - bounds: Some(Bounds::from_corners( + window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners( gpui::Point::new(0.into(), 0.into()), gpui::Point::new(10.into(), 80.into()), - )), + ))), ..Default::default() }, |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)), diff --git a/crates/gpui/examples/animation.rs b/crates/gpui/examples/animation.rs index b0e2c07df00d57e6530e98fc238b7e93a1f38aa8..f3d8f56eda974c49c627e042a25ede2fae019fb9 100644 --- a/crates/gpui/examples/animation.rs +++ b/crates/gpui/examples/animation.rs @@ -63,7 +63,11 @@ fn main() { .with_assets(Assets {}) .run(|cx: &mut AppContext| { let options = WindowOptions { - bounds: Some(Bounds::centered(None, size(px(300.), px(300.)), cx)), + window_bounds: Some(WindowBounds::Windowed(Bounds::centered( + None, + size(px(300.), px(300.)), + cx, + ))), ..Default::default() }; cx.open_window(options, |cx| { diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs index 018c6097cfae7799b2514e276d3f41453999822d..6a91f6ab41ce9bf8b15e21c24d0b4b2c8e05c9fa 100644 --- a/crates/gpui/examples/hello_world.rs +++ b/crates/gpui/examples/hello_world.rs @@ -26,7 +26,7 @@ fn main() { let bounds = Bounds::centered(None, size(px(600.0), px(600.0)), cx); cx.open_window( WindowOptions { - bounds: Some(bounds), + window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, |cx| { diff --git a/crates/gpui/examples/image/image.rs b/crates/gpui/examples/image/image.rs index 32bf205e83b8a674798c95851b2ff1f6f4a0f12e..e1e82d8d4a6aa7762d60708cdaa8209023b5b940 100644 --- a/crates/gpui/examples/image/image.rs +++ b/crates/gpui/examples/image/image.rs @@ -79,10 +79,10 @@ fn main() { ..Default::default() }), - bounds: Some(Bounds { + window_bounds: Some(WindowBounds::Windowed(Bounds { size: size(px(1100.), px(600.)).into(), origin: Point::new(DevicePixels::from(200), DevicePixels::from(200)), - }), + })), ..Default::default() }; diff --git a/crates/gpui/examples/window_positioning.rs b/crates/gpui/examples/window_positioning.rs index aa70236e3cb8e0ae133aa9fbfde44670283fedd2..da8742330b26fae6e3902bb1eac68d9863dfc47f 100644 --- a/crates/gpui/examples/window_positioning.rs +++ b/crates/gpui/examples/window_positioning.rs @@ -43,7 +43,7 @@ fn main() { WindowOptions { // Set the bounds of the window in screen coordinates - bounds: Some(bounds), + window_bounds: Some(WindowBounds::Windowed(bounds)), // Specify the display_id to ensure the window is created on the correct screen display_id: Some(screen.id()), @@ -53,7 +53,6 @@ fn main() { show: true, kind: WindowKind::PopUp, is_movable: false, - fullscreen: false, app_id: None, } }; diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 0c6fb888e1e4f657bfbe41eacfd69c4f5bfd9540..c66958c3c41a86cc9cba1279aa7a3e00948b3540 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -4,8 +4,8 @@ use crate::{ Element, Empty, Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, - TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowContext, - WindowHandle, WindowOptions, + TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowBounds, + WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{channel::oneshot, Stream, StreamExt}; @@ -188,7 +188,7 @@ impl TestAppContext { let bounds = Bounds::maximized(None, &mut cx); cx.open_window( WindowOptions { - bounds: Some(bounds), + window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, |cx| cx.new_view(build_window), @@ -201,7 +201,7 @@ impl TestAppContext { let bounds = Bounds::maximized(None, &mut cx); let window = cx.open_window( WindowOptions { - bounds: Some(bounds), + window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, |cx| cx.new_view(|_| Empty), @@ -224,7 +224,7 @@ impl TestAppContext { let bounds = Bounds::maximized(None, &mut cx); let window = cx.open_window( WindowOptions { - bounds: Some(bounds), + window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, |cx| cx.new_view(build_root_view), diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index cc4f23cff285ca25ece24253c6f289e8b710d921..1312b66bbe59dbed0c09b29701b424c5be8d64df 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -184,7 +184,7 @@ unsafe impl Send for DisplayId {} pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn bounds(&self) -> Bounds; fn is_maximized(&self) -> bool; - fn is_minimized(&self) -> bool; + fn window_bounds(&self) -> WindowBounds; fn content_size(&self) -> Size; fn scale_factor(&self) -> f32; fn appearance(&self) -> WindowAppearance; @@ -515,9 +515,10 @@ pub trait InputHandler: 'static { /// The variables that can be configured when creating a new window #[derive(Debug)] pub struct WindowOptions { - /// The bounds of the window in screen coordinates. - /// None -> inherit, Some(bounds) -> set bounds - pub bounds: Option>, + /// Specifies the state and bounds of the window in screen coordinates. + /// - `None`: Inherit the bounds. + /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size. + pub window_bounds: Option, /// The titlebar configuration of the window pub titlebar: Option, @@ -528,9 +529,6 @@ pub struct WindowOptions { /// 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, @@ -571,10 +569,40 @@ pub(crate) struct WindowParams { pub window_background: WindowBackgroundAppearance, } +/// Represents the status of how a window should be opened. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum WindowBounds { + /// Indicates that the window should open in a windowed state with the given bounds. + Windowed(Bounds), + /// Indicates that the window should open in a maximized state. + /// The bounds provided here represent the restore size of the window. + Maximized(Bounds), + /// Indicates that the window should open in fullscreen mode. + /// The bounds provided here represent the restore size of the window. + Fullscreen(Bounds), +} + +impl Default for WindowBounds { + fn default() -> Self { + WindowBounds::Windowed(Bounds::default()) + } +} + +impl WindowBounds { + /// Retrieve the inner bounds + pub fn get_bounds(&self) -> Bounds { + match self { + WindowBounds::Windowed(bounds) => *bounds, + WindowBounds::Maximized(bounds) => *bounds, + WindowBounds::Fullscreen(bounds) => *bounds, + } + } +} + impl Default for WindowOptions { fn default() -> Self { Self { - bounds: None, + window_bounds: None, titlebar: Some(TitlebarOptions { title: Default::default(), appears_transparent: Default::default(), @@ -585,7 +613,6 @@ impl Default for WindowOptions { kind: WindowKind::Normal, is_movable: true, display_id: None, - fullscreen: false, window_background: WindowBackgroundAppearance::default(), app_id: None, } diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 8924a322b544280e75dcfb82975e5faf58b4e37f..58e4641d645577480440b503d2c0c03fc198f670 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -28,7 +28,7 @@ use crate::scene::Scene; use crate::{ px, size, Bounds, DevicePixels, Globals, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point, PromptLevel, Size, WaylandClientState, WaylandClientStatePtr, WindowAppearance, - WindowBackgroundAppearance, WindowParams, + WindowBackgroundAppearance, WindowBounds, WindowParams, }; #[derive(Default)] @@ -79,6 +79,7 @@ pub struct WaylandWindowState { input_handler: Option, decoration_state: WaylandDecorationState, fullscreen: bool, + restore_bounds: Bounds, maximized: bool, client: WaylandClientStatePtr, callbacks: Callbacks, @@ -151,6 +152,7 @@ impl WaylandWindowState { input_handler: None, decoration_state: WaylandDecorationState::Client, fullscreen: false, + restore_bounds: Bounds::default(), maximized: false, callbacks: Callbacks::default(), client, @@ -332,10 +334,15 @@ impl WaylandWindowStatePtr { let height = NonZeroU32::new(height as u32); let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8)); let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8)); - self.resize(width, height); - self.set_fullscreen(fullscreen); let mut state = self.state.borrow_mut(); state.maximized = maximized; + state.fullscreen = fullscreen; + if fullscreen || maximized { + state.restore_bounds = state.bounds.map(|p| DevicePixels(p as i32)); + } + drop(state); + self.resize(width, height); + self.set_fullscreen(fullscreen); false } @@ -545,9 +552,17 @@ impl PlatformWindow for WaylandWindow { self.borrow().maximized } - fn is_minimized(&self) -> bool { - // This cannot be determined by the client - false + // todo(linux) + // check if it is right + fn window_bounds(&self) -> WindowBounds { + let state = self.borrow(); + if state.fullscreen { + WindowBounds::Fullscreen(state.restore_bounds) + } else if state.maximized { + WindowBounds::Maximized(state.restore_bounds) + } else { + WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p as i32))) + } } fn content_size(&self) -> Size { @@ -679,7 +694,8 @@ impl PlatformWindow for WaylandWindow { } fn toggle_fullscreen(&self) { - let state = self.borrow(); + let mut state = self.borrow_mut(); + state.restore_bounds = state.bounds.map(|p| DevicePixels(p as i32)); if !state.fullscreen { state.toplevel.set_fullscreen(None); } else { diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 1f23dcd9590034e9e34665c3abdab339a7fd410a..77c050ac58e4ba0801dd275819b985ce5d48eae5 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -5,8 +5,8 @@ use crate::{ platform::blade::{BladeRenderer, BladeSurfaceConfig}, size, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel, - Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowOptions, WindowParams, - X11Client, X11ClientState, X11ClientStatePtr, + Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions, + WindowParams, X11Client, X11ClientState, X11ClientStatePtr, }; use blade_graphics as gpu; use parking_lot::Mutex; @@ -526,8 +526,9 @@ impl PlatformWindow for X11Window { } // todo(linux) - fn is_minimized(&self) -> bool { - false + fn window_bounds(&self) -> WindowBounds { + let state = self.0.state.borrow(); + WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p))) } fn content_size(&self) -> Size { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 570eb9024aab51cc971cb30725c1ac028e087f2d..09f5f3241ab91aa049526a207bf8cd71fd7f305a 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -4,7 +4,8 @@ use crate::{ DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, - Size, Timer, WindowAppearance, WindowBackgroundAppearance, WindowKind, WindowParams, + Size, Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, + WindowParams, }; use block::ConcreteBlock; use cocoa::{ @@ -259,6 +260,10 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(windowDidChangeOcclusionState:), window_did_change_occlusion_state as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowWillEnterFullScreen:), + window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(windowDidMove:), window_did_move as extern "C" fn(&Object, Sel, id), @@ -302,14 +307,6 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(concludeDragOperation:), conclude_drag_operation as extern "C" fn(&Object, Sel, id), ); - decl.add_method( - sel!(windowDidMiniaturize:), - window_did_miniaturize as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(windowDidDeminiaturize:), - window_did_deminiaturize as extern "C" fn(&Object, Sel, id), - ); decl.register() } @@ -348,7 +345,7 @@ struct MacWindowState { external_files_dragged: bool, // Whether the next left-mouse click is also the focusing click. first_mouse: bool, - minimized: bool, + fullscreen_restore_bounds: Bounds, } impl MacWindowState { @@ -435,10 +432,6 @@ impl MacWindowState { } } - fn is_minimized(&self) -> bool { - self.minimized - } - fn is_fullscreen(&self) -> bool { unsafe { let style_mask = self.native_window.styleMask(); @@ -494,6 +487,14 @@ impl MacWindowState { px((frame.size.height - content_layout_rect.size.height) as f32) } } + + fn window_bounds(&self) -> WindowBounds { + if self.is_fullscreen() { + WindowBounds::Fullscreen(self.fullscreen_restore_bounds) + } else { + WindowBounds::Windowed(self.bounds()) + } + } } unsafe impl Send for MacWindowState {} @@ -639,7 +640,7 @@ impl MacWindow { previous_keydown_inserted_text: None, external_files_dragged: false, first_mouse: false, - minimized: false, + fullscreen_restore_bounds: Bounds::default(), }))); (*native_window).set_ivar( @@ -775,12 +776,12 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().bounds() } - fn is_maximized(&self) -> bool { - self.0.as_ref().lock().is_maximized() + fn window_bounds(&self) -> WindowBounds { + self.0.as_ref().lock().window_bounds() } - fn is_minimized(&self) -> bool { - self.0.as_ref().lock().is_minimized() + fn is_maximized(&self) -> bool { + self.0.as_ref().lock().is_maximized() } fn content_size(&self) -> Size { @@ -1466,6 +1467,12 @@ extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { window_state.as_ref().lock().move_traffic_light(); } +extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { + let window_state = unsafe { get_window_state(this) }; + let mut lock = window_state.as_ref().lock(); + lock.fullscreen_restore_bounds = lock.bounds(); +} + extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; let mut lock = window_state.as_ref().lock(); @@ -1863,18 +1870,6 @@ extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) { ); } -extern "C" fn window_did_miniaturize(this: &Object, _: Sel, _: id) { - let window_state = unsafe { get_window_state(this) }; - - window_state.lock().minimized = true; -} - -extern "C" fn window_did_deminiaturize(this: &Object, _: Sel, _: id) { - let window_state = unsafe { get_window_state(this) }; - - window_state.lock().minimized = false; -} - async fn synthetic_drag( window_state: Weak>, drag_id: usize, diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index bc9349e0170ca131d0bc89010fd8aac9c2dcf14e..1b9654c56127fe2eab4c3298deed7bc70fa9860a 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -2,7 +2,7 @@ use crate::{ AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels, DispatchEventResult, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, - WindowBackgroundAppearance, WindowParams, + WindowBackgroundAppearance, WindowBounds, WindowParams, }; use collections::HashMap; use parking_lot::Mutex; @@ -112,11 +112,11 @@ impl PlatformWindow for TestWindow { self.0.lock().bounds } - fn is_maximized(&self) -> bool { - false + fn window_bounds(&self) -> WindowBounds { + WindowBounds::Windowed(self.bounds()) } - fn is_minimized(&self) -> bool { + fn is_maximized(&self) -> bool { false } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index b16adf612679301eb08d4ce8c6cfd2872151cf55..753abe56d8c3c0499d75c6ae26fa101470013591 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -35,6 +35,7 @@ pub(crate) struct WindowsWindow(pub Rc); pub struct WindowsWindowState { pub origin: Point, pub physical_size: Size, + pub fullscreen_restore_bounds: Bounds, pub scale_factor: f32, pub callbacks: Callbacks, @@ -71,6 +72,10 @@ impl WindowsWindowState { ) -> Self { let origin = point(cs.x.into(), cs.y.into()); let physical_size = size(cs.cx.into(), cs.cy.into()); + let fullscreen_restore_bounds = Bounds { + origin, + size: physical_size, + }; let scale_factor = { let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32; monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32 @@ -84,6 +89,7 @@ impl WindowsWindowState { Self { origin, physical_size, + fullscreen_restore_bounds, scale_factor, callbacks, input_handler, @@ -113,6 +119,35 @@ impl WindowsWindowState { } } + fn window_bounds(&self) -> WindowBounds { + let placement = unsafe { + let mut placement = WINDOWPLACEMENT { + length: std::mem::size_of::() as u32, + ..Default::default() + }; + GetWindowPlacement(self.hwnd, &mut placement).log_err(); + placement + }; + let bounds = Bounds { + origin: point( + DevicePixels(placement.rcNormalPosition.left), + DevicePixels(placement.rcNormalPosition.top), + ), + size: size( + DevicePixels(placement.rcNormalPosition.right - placement.rcNormalPosition.left), + DevicePixels(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top), + ), + }; + + if self.is_fullscreen() { + WindowBounds::Fullscreen(self.fullscreen_restore_bounds) + } else if placement.showCmd == SW_SHOWMAXIMIZED.0 as u32 { + WindowBounds::Maximized(bounds) + } else { + WindowBounds::Windowed(bounds) + } + } + /// get the logical size of the app's drawable area. /// /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as @@ -176,10 +211,6 @@ impl WindowsWindowStatePtr { main_receiver: context.main_receiver.clone(), }) } - - fn is_minimized(&self) -> bool { - unsafe { IsIconic(self.hwnd) }.as_bool() - } } #[derive(Default)] @@ -209,7 +240,7 @@ struct WindowCreateContext { impl WindowsWindow { pub(crate) fn new( handle: AnyWindowHandle, - options: WindowParams, + params: WindowParams, icon: HICON, executor: ForegroundExecutor, main_receiver: flume::Receiver, @@ -217,13 +248,13 @@ impl WindowsWindow { current_cursor: HCURSOR, ) -> Self { let classname = register_wnd_class(icon); - let hide_title_bar = options + let hide_title_bar = params .titlebar .as_ref() .map(|titlebar| titlebar.appears_transparent) .unwrap_or(false); let windowname = HSTRING::from( - options + params .titlebar .as_ref() .and_then(|titlebar| titlebar.title.as_ref()) @@ -231,12 +262,6 @@ impl WindowsWindow { .unwrap_or(""), ); let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX; - let x = options.bounds.origin.x.0; - let y = options.bounds.origin.y.0; - let nwidth = options.bounds.size.width.0; - let nheight = options.bounds.size.height.0; - let hwndparent = HWND::default(); - let hmenu = HMENU::default(); let hinstance = get_module_handle(); let mut context = WindowCreateContext { inner: None, @@ -245,7 +270,7 @@ impl WindowsWindow { // todo(windows) move window to target monitor // options.display_id display: WindowsDisplay::primary_monitor().unwrap(), - transparent: options.window_background != WindowBackgroundAppearance::Opaque, + transparent: params.window_background != WindowBackgroundAppearance::Opaque, executor, main_receiver, mouse_wheel_settings, @@ -258,12 +283,12 @@ impl WindowsWindow { classname, &windowname, dwstyle, - x, - y, - nwidth, - nheight, - hwndparent, - hmenu, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + None, + None, hinstance, lpparam, ) @@ -272,6 +297,18 @@ impl WindowsWindow { register_drag_drop(state_ptr.clone()); let wnd = Self(state_ptr); + unsafe { + let mut placement = WINDOWPLACEMENT { + length: std::mem::size_of::() as u32, + ..Default::default() + }; + GetWindowPlacement(raw_hwnd, &mut placement).log_err(); + placement.rcNormalPosition.left = params.bounds.left().0; + placement.rcNormalPosition.right = params.bounds.right().0; + placement.rcNormalPosition.top = params.bounds.top().0; + placement.rcNormalPosition.bottom = params.bounds.bottom().0; + SetWindowPlacement(raw_hwnd, &placement).log_err(); + } unsafe { ShowWindow(raw_hwnd, SW_SHOW) }; wnd @@ -321,8 +358,8 @@ impl PlatformWindow for WindowsWindow { self.0.state.borrow().is_maximized() } - fn is_minimized(&self) -> bool { - self.0.is_minimized() + fn window_bounds(&self) -> WindowBounds { + self.0.state.borrow().window_bounds() } /// get the logical size of the app's drawable area. @@ -493,6 +530,10 @@ impl PlatformWindow for WindowsWindow { .executor .spawn(async move { let mut lock = state_ptr.state.borrow_mut(); + lock.fullscreen_restore_bounds = Bounds { + origin: lock.origin, + size: lock.physical_size, + }; let StyleAndBounds { style, x, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index d7e9863cfdba4ed8dc6e645808bacc3cbb6cae2f..f7063d17c3e99a8b912551b23223e6f5826025b8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -12,8 +12,8 @@ use crate::{ RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView, - WindowAppearance, WindowBackgroundAppearance, WindowOptions, WindowParams, WindowTextSystem, - SUBPIXEL_VARIANTS, + WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions, WindowParams, + WindowTextSystem, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::{FxHashMap, FxHashSet}; @@ -565,7 +565,7 @@ fn default_bounds(display_id: Option, cx: &mut AppContext) -> Bounds< const DEFAULT_WINDOW_OFFSET: Point = point(DevicePixels(0), DevicePixels(35)); cx.active_window() - .and_then(|w| w.update(cx, |_, cx| cx.window_bounds()).ok()) + .and_then(|w| w.update(cx, |_, cx| cx.bounds()).ok()) .map(|bounds| bounds.map_origin(|origin| origin + DEFAULT_WINDOW_OFFSET)) .unwrap_or_else(|| { let display = display_id @@ -592,19 +592,20 @@ impl Window { cx: &mut AppContext, ) -> Self { let WindowOptions { - bounds, + window_bounds, titlebar, focus, show, kind, is_movable, display_id, - fullscreen, window_background, app_id, } = options; - let bounds = bounds.unwrap_or_else(|| default_bounds(display_id, cx)); + let bounds = window_bounds + .map(|bounds| bounds.get_bounds()) + .unwrap_or_else(|| default_bounds(display_id, cx)); let mut platform_window = cx.platform.open_window( handle, WindowParams { @@ -632,8 +633,12 @@ impl Window { let next_frame_callbacks: Rc>> = Default::default(); let last_input_timestamp = Rc::new(Cell::new(Instant::now())); - if fullscreen { - platform_window.toggle_fullscreen(); + if let Some(ref window_open_state) = window_bounds { + match window_open_state { + WindowBounds::Fullscreen(_) => platform_window.toggle_fullscreen(), + WindowBounds::Maximized(_) => platform_window.zoom(), + WindowBounds::Windowed(_) => {} + } } platform_window.on_close(Box::new({ @@ -691,7 +696,7 @@ impl Window { let mut cx = cx.to_async(); move |_, _| { handle - .update(&mut cx, |_, cx| cx.window_bounds_changed()) + .update(&mut cx, |_, cx| cx.bounds_changed()) .log_err(); } })); @@ -699,7 +704,7 @@ impl Window { let mut cx = cx.to_async(); move || { handle - .update(&mut cx, |_, cx| cx.window_bounds_changed()) + .update(&mut cx, |_, cx| cx.bounds_changed()) .log_err(); } })); @@ -943,10 +948,10 @@ impl<'a> WindowContext<'a> { self.window.platform_window.is_maximized() } - /// Check if the platform window is minimized - /// On some platforms (namely Windows) the position is incorrect when minimized - pub fn is_minimized(&self) -> bool { - self.window.platform_window.is_minimized() + /// Return the `WindowBounds` to indicate that how a window should be opened + /// after it has been closed + pub fn window_bounds(&self) -> WindowBounds { + self.window.platform_window.window_bounds() } /// Dispatch the given action on the currently focused element. @@ -1075,7 +1080,7 @@ impl<'a> WindowContext<'a> { .spawn(|app| f(AsyncWindowContext::new(app, self.window.handle))) } - fn window_bounds_changed(&mut self) { + fn 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.display_id = self.window.platform_window.display().id(); @@ -1088,7 +1093,7 @@ impl<'a> WindowContext<'a> { } /// Returns the bounds of the current window in the global coordinate space, which could span across multiple displays. - pub fn window_bounds(&self) -> Bounds { + pub fn bounds(&self) -> Bounds { self.window.platform_window.bounds() } diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 70853267ca67e6704a955a2358eaed2757d586e9..cb3498c179d703bdc043e283a3c521111f552bae 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -7,7 +7,8 @@ mod story_selector; use clap::Parser; use dialoguer::FuzzySelect; use gpui::{ - div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowOptions, + div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowBounds, + WindowOptions, }; use log::LevelFilter; use project::Project; @@ -85,7 +86,7 @@ fn main() { let bounds = Bounds::centered(None, size, cx); let _window = cx.open_window( WindowOptions { - bounds: Some(bounds), + window_bounds: Some(WindowBounds::Windowed(bounds)), ..Default::default() }, move |cx| { diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 04639b5011b575069d0e8ebd9776b285fedf2381..e977166b006c3d386a6cfdb2de0bd98b13602f1e 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -5,7 +5,7 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use client::DevServerProjectId; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::{point, size, Axis, Bounds}; +use gpui::{point, size, Axis, Bounds, WindowBounds}; use sqlez::{ bindable::{Bind, Column, StaticColumnCount}, @@ -59,50 +59,99 @@ impl sqlez::bindable::Column for SerializedAxis { } } -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct SerializedWindowsBounds(pub(crate) Bounds); +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub(crate) struct SerializedWindowBounds(pub(crate) WindowBounds); -impl StaticColumnCount for SerializedWindowsBounds { +impl StaticColumnCount for SerializedWindowBounds { fn column_count() -> usize { 5 } } -impl Bind for SerializedWindowsBounds { +impl Bind for SerializedWindowBounds { fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = statement.bind(&"Fixed", start_index)?; - - statement.bind( - &( - SerializedDevicePixels(self.0.origin.x), - SerializedDevicePixels(self.0.origin.y), - SerializedDevicePixels(self.0.size.width), - SerializedDevicePixels(self.0.size.height), - ), - next_index, - ) + match self.0 { + WindowBounds::Windowed(bounds) => { + let next_index = statement.bind(&"Windowed", start_index)?; + statement.bind( + &( + SerializedDevicePixels(bounds.origin.x), + SerializedDevicePixels(bounds.origin.y), + SerializedDevicePixels(bounds.size.width), + SerializedDevicePixels(bounds.size.height), + ), + next_index, + ) + } + WindowBounds::Maximized(bounds) => { + let next_index = statement.bind(&"Maximized", start_index)?; + statement.bind( + &( + SerializedDevicePixels(bounds.origin.x), + SerializedDevicePixels(bounds.origin.y), + SerializedDevicePixels(bounds.size.width), + SerializedDevicePixels(bounds.size.height), + ), + next_index, + ) + } + WindowBounds::Fullscreen(bounds) => { + let next_index = statement.bind(&"FullScreen", start_index)?; + statement.bind( + &( + SerializedDevicePixels(bounds.origin.x), + SerializedDevicePixels(bounds.origin.y), + SerializedDevicePixels(bounds.size.width), + SerializedDevicePixels(bounds.size.height), + ), + next_index, + ) + } + } } } -impl Column for SerializedWindowsBounds { +impl Column for SerializedWindowBounds { 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() { - "Fixed" => { + let status = match window_state.as_str() { + "Windowed" | "Fixed" => { let ((x, y, width, height), _) = Column::column(statement, next_index)?; let x: i32 = x; let y: i32 = y; let width: i32 = width; let height: i32 = height; - SerializedWindowsBounds(Bounds { + SerializedWindowBounds(WindowBounds::Windowed(Bounds { origin: point(x.into(), y.into()), size: size(width.into(), height.into()), - }) + })) + } + "Maximized" => { + let ((x, y, width, height), _) = Column::column(statement, next_index)?; + let x: i32 = x; + let y: i32 = y; + let width: i32 = width; + let height: i32 = height; + SerializedWindowBounds(WindowBounds::Maximized(Bounds { + origin: point(x.into(), y.into()), + size: size(width.into(), height.into()), + })) + } + "FullScreen" => { + let ((x, y, width, height), _) = Column::column(statement, next_index)?; + let x: i32 = x; + let y: i32 = y; + let width: i32 = width; + let height: i32 = height; + SerializedWindowBounds(WindowBounds::Fullscreen(Bounds { + origin: point(x.into(), y.into()), + size: size(width.into(), height.into()), + })) } _ => bail!("Window State did not have a valid string"), }; - Ok((bounds, next_index + 4)) + Ok((status, next_index + 4)) } } @@ -279,6 +328,8 @@ define_connection! { ALTER TABLE pane_groups ADD COLUMN flexes TEXT; ), // Add fullscreen field to workspace + // Deprecated, `WindowBounds` holds the fullscreen state now. + // Preserving so users can downgrade Zed. sql!( ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; //bool ), @@ -328,19 +379,17 @@ impl WorkspaceDb { workspace_id, local_paths, dev_server_project_id, - bounds, + window_bounds, display, - fullscreen, centered_layout, docks, ): ( WorkspaceId, Option, Option, - Option, + Option, Option, Option, - Option, DockStructure, ) = self .select_row_bound(sql! { @@ -354,7 +403,6 @@ impl WorkspaceDb { window_width, window_height, display, - fullscreen, centered_layout, left_dock_visible, left_dock_active_panel, @@ -398,8 +446,7 @@ impl WorkspaceDb { .get_center_pane_group(workspace_id) .context("Getting center group") .log_err()?, - bounds: bounds.map(|bounds| bounds.0), - fullscreen: fullscreen.unwrap_or(false), + window_bounds, centered_layout: centered_layout.unwrap_or(false), display, docks, @@ -549,13 +596,12 @@ impl WorkspaceDb { pub(crate) fn last_window( &self, - ) -> anyhow::Result<(Option, Option, Option)> { + ) -> anyhow::Result<(Option, Option)> { let mut prepared_query = - self.select::<(Option, Option, Option)>(sql!( + self.select::<(Option, Option)>(sql!( SELECT display, - window_state, window_x, window_y, window_width, window_height, - fullscreen + window_state, window_x, window_y, window_width, window_height FROM workspaces WHERE local_paths IS NOT NULL @@ -563,10 +609,7 @@ impl WorkspaceDb { LIMIT 1 ))?; let result = prepared_query()?; - Ok(result - .into_iter() - .next() - .unwrap_or_else(|| (None, None, None))) + Ok(result.into_iter().next().unwrap_or_else(|| (None, None))) } query! { @@ -829,7 +872,7 @@ impl WorkspaceDb { } query! { - pub(crate) async fn set_window_bounds(workspace_id: WorkspaceId, bounds: SerializedWindowsBounds, display: Uuid) -> Result<()> { + pub(crate) async fn set_window_open_status(workspace_id: WorkspaceId, bounds: SerializedWindowBounds, display: Uuid) -> Result<()> { UPDATE workspaces SET window_state = ?2, window_x = ?3, @@ -841,14 +884,6 @@ impl WorkspaceDb { } } - query! { - pub(crate) async fn set_fullscreen(workspace_id: WorkspaceId, fullscreen: bool) -> Result<()> { - UPDATE workspaces - SET fullscreen = ?2 - WHERE workspace_id = ?1 - } - } - query! { pub(crate) async fn set_centered_layout(workspace_id: WorkspaceId, centered_layout: bool) -> Result<()> { UPDATE workspaces @@ -938,10 +973,9 @@ mod tests { id: WorkspaceId(1), location: LocalPaths::new(["/tmp", "/tmp2"]).into(), center_group: Default::default(), - bounds: Default::default(), + window_bounds: Default::default(), display: Default::default(), docks: Default::default(), - fullscreen: false, centered_layout: false, }; @@ -949,10 +983,9 @@ mod tests { id: WorkspaceId(2), location: LocalPaths::new(["/tmp"]).into(), center_group: Default::default(), - bounds: Default::default(), + window_bounds: Default::default(), display: Default::default(), docks: Default::default(), - fullscreen: false, centered_layout: false, }; @@ -1049,10 +1082,9 @@ mod tests { id: WorkspaceId(5), location: LocalPaths::new(["/tmp", "/tmp2"]).into(), center_group, - bounds: Default::default(), + window_bounds: Default::default(), display: Default::default(), docks: Default::default(), - fullscreen: false, centered_layout: false, }; @@ -1079,10 +1111,9 @@ mod tests { id: WorkspaceId(1), location: LocalPaths::new(["/tmp", "/tmp2"]).into(), center_group: Default::default(), - bounds: Default::default(), + window_bounds: Default::default(), display: Default::default(), docks: Default::default(), - fullscreen: false, centered_layout: false, }; @@ -1090,10 +1121,9 @@ mod tests { id: WorkspaceId(2), location: LocalPaths::new(["/tmp"]).into(), center_group: Default::default(), - bounds: Default::default(), + window_bounds: Default::default(), display: Default::default(), docks: Default::default(), - fullscreen: false, centered_layout: false, }; @@ -1128,10 +1158,9 @@ mod tests { id: WorkspaceId(3), location: LocalPaths::new(&["/tmp", "/tmp2"]).into(), center_group: Default::default(), - bounds: Default::default(), + window_bounds: Default::default(), display: Default::default(), docks: Default::default(), - fullscreen: false, centered_layout: false, }; @@ -1163,10 +1192,9 @@ mod tests { id: WorkspaceId(4), location: LocalPaths::new(workspace_id).into(), center_group: center_group.clone(), - bounds: Default::default(), + window_bounds: Default::default(), display: Default::default(), docks: Default::default(), - fullscreen: false, centered_layout: false, } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index aef9e04e5d2e9f955b3c372cfeff2e5c1abc4075..08fe507afc0ca82a07e816b133528e5255dc353d 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -1,4 +1,4 @@ -use super::SerializedAxis; +use super::{SerializedAxis, SerializedWindowBounds}; use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId}; use anyhow::{Context, Result}; use async_recursion::async_recursion; @@ -7,7 +7,7 @@ use db::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui::{AsyncWindowContext, Bounds, DevicePixels, Model, Task, View, WeakView}; +use gpui::{AsyncWindowContext, Model, Task, View, WeakView}; use project::Project; use serde::{Deserialize, Serialize}; use std::{ @@ -110,8 +110,7 @@ pub(crate) struct SerializedWorkspace { pub(crate) id: WorkspaceId, pub(crate) location: SerializedWorkspaceLocation, pub(crate) center_group: SerializedPaneGroup, - pub(crate) bounds: Option>, - pub(crate) fullscreen: bool, + pub(crate) window_bounds: Option, pub(crate) centered_layout: bool, pub(crate) display: Option, pub(crate) docks: DockStructure, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 098ac3d28bc75d6ff59437b2bba998cefb9fb0db..20f88ab0efacc632b02e4519418884a0a8d8bfd5 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -32,7 +32,7 @@ use gpui::{ ElementId, Entity as _, EntityId, EventEmitter, FocusHandle, FocusableView, Global, GlobalElementId, KeyContext, Keystroke, LayoutId, ManagedView, Model, ModelContext, PathPromptOptions, Point, PromptLevel, Render, Size, Subscription, Task, View, WeakView, - WindowHandle, WindowOptions, + WindowBounds, WindowHandle, WindowOptions, }; use item::{ FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings, @@ -46,7 +46,7 @@ use node_runtime::NodeRuntime; use notifications::{simple_message_notification::MessageNotification, NotificationHandle}; pub use pane::*; pub use pane_group::*; -use persistence::{model::SerializedWorkspace, SerializedWindowsBounds, DB}; +use persistence::{model::SerializedWorkspace, SerializedWindowBounds, DB}; pub use persistence::{ model::{ItemId, LocalPaths, SerializedDevServerProject, SerializedWorkspaceLocation}, WorkspaceDb, DB as WORKSPACE_DB, @@ -785,29 +785,15 @@ impl Workspace { .await; this.update(&mut cx, |this, cx| { if let Some(display) = cx.display() { - let window_bounds = cx.window_bounds(); - let fullscreen = cx.is_fullscreen(); - if let Some(display_uuid) = display.uuid().log_err() { - // Only update the window bounds when not full screen, - // so we can remember the last non-fullscreen bounds - // across restarts - if fullscreen { - cx.background_executor() - .spawn(DB.set_fullscreen(workspace_id, true)) - .detach_and_log_err(cx); - } else if !cx.is_minimized() { - cx.background_executor() - .spawn(DB.set_fullscreen(workspace_id, false)) - .detach_and_log_err(cx); - cx.background_executor() - .spawn(DB.set_window_bounds( - workspace_id, - SerializedWindowsBounds(window_bounds), - display_uuid, - )) - .detach_and_log_err(cx); - } + let window_bounds = cx.window_bounds(); + cx.background_executor() + .spawn(DB.set_window_open_status( + workspace_id, + SerializedWindowBounds(window_bounds), + display_uuid, + )) + .detach_and_log_err(cx); } } this.bounds_save_task_queued.take(); @@ -947,30 +933,27 @@ impl Workspace { } else { let window_bounds_override = window_bounds_env_override(); - let (bounds, display, fullscreen) = if let Some(bounds) = window_bounds_override { - (Some(bounds), None, false) + let (window_bounds, display) = if let Some(bounds) = window_bounds_override { + (Some(WindowBounds::Windowed(bounds)), None) } else { let restorable_bounds = serialized_workspace .as_ref() - .and_then(|workspace| { - Some((workspace.display?, workspace.bounds?, workspace.fullscreen)) - }) + .and_then(|workspace| Some((workspace.display?, workspace.window_bounds?))) .or_else(|| { - let (display, bounds, fullscreen) = DB.last_window().log_err()?; - Some((display?, bounds?.0, fullscreen.unwrap_or(false))) + let (display, window_bounds) = DB.last_window().log_err()?; + Some((display?, window_bounds?)) }); - if let Some((serialized_display, bounds, fullscreen)) = restorable_bounds { - (Some(bounds), Some(serialized_display), fullscreen) + if let Some((serialized_display, serialized_status)) = restorable_bounds { + (Some(serialized_status.0), Some(serialized_display)) } else { - (None, None, false) + (None, None) } }; // Use the serialized workspace to construct the new window let mut options = cx.update(|cx| (app_state.build_window_options)(display, cx))?; - options.bounds = bounds; - options.fullscreen = fullscreen; + options.window_bounds = window_bounds; let centered_layout = serialized_workspace .as_ref() .map(|w| w.centered_layout) @@ -3667,14 +3650,14 @@ impl Workspace { if let Some(location) = location { let center_group = build_serialized_pane_group(&self.center.root, cx); let docks = build_serialized_docks(self, cx); + let window_bounds = Some(SerializedWindowBounds(cx.window_bounds())); let serialized_workspace = SerializedWorkspace { id: self.database_id, location, center_group, - bounds: Default::default(), + window_bounds, display: Default::default(), docks, - fullscreen: cx.is_fullscreen(), centered_layout: self.centered_layout, }; return cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace)); @@ -4867,7 +4850,8 @@ pub fn join_hosted_project( let window_bounds_override = window_bounds_env_override(); cx.update(|cx| { let mut options = (app_state.build_window_options)(None, cx); - options.bounds = window_bounds_override; + options.window_bounds = + window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds)); cx.open_window(options, |cx| { cx.new_view(|cx| { Workspace::new(Default::default(), project, app_state.clone(), cx) @@ -4931,7 +4915,8 @@ pub fn join_dev_server_project( let window_bounds_override = window_bounds_env_override(); cx.update(|cx| { let mut options = (app_state.build_window_options)(None, cx); - options.bounds = window_bounds_override; + options.window_bounds = + window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds)); cx.open_window(options, |cx| { cx.new_view(|cx| { Workspace::new(Default::default(), project, app_state.clone(), cx) @@ -4993,7 +4978,8 @@ pub fn join_in_room_project( let window_bounds_override = window_bounds_env_override(); cx.update(|cx| { let mut options = (app_state.build_window_options)(None, cx); - options.bounds = window_bounds_override; + options.window_bounds = + window_bounds_override.map(|bounds| WindowBounds::Windowed(bounds)); cx.open_window(options, |cx| { cx.new_view(|cx| { Workspace::new(Default::default(), project, app_state.clone(), cx) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 05eae13153818e46354ffe4ba207d73a9ec85df6..63bf427b5acbc190c187fa0934ae098fe1c73f3b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -96,13 +96,12 @@ pub fn build_window_options(display_uuid: Option, cx: &mut AppContext) -> appears_transparent: true, traffic_light_position: Some(point(px(9.0), px(9.0))), }), - bounds: None, + window_bounds: None, focus: false, show: false, kind: WindowKind::Normal, is_movable: true, display_id: display.map(|display| display.id()), - fullscreen: false, window_background: cx.theme().window_background_appearance(), app_id: Some(app_id.to_owned()), }