Detailed changes
@@ -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()),
}
@@ -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)),
@@ -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| {
@@ -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| {
@@ -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()
};
@@ -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,
}
};
@@ -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),
@@ -184,7 +184,7 @@ unsafe impl Send for DisplayId {}
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn bounds(&self) -> Bounds<DevicePixels>;
fn is_maximized(&self) -> bool;
- fn is_minimized(&self) -> bool;
+ fn window_bounds(&self) -> WindowBounds;
fn content_size(&self) -> Size<Pixels>;
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<Bounds<DevicePixels>>,
+ /// 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<WindowBounds>,
/// The titlebar configuration of the window
pub titlebar: Option<TitlebarOptions>,
@@ -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<DevicePixels>),
+ /// Indicates that the window should open in a maximized state.
+ /// The bounds provided here represent the restore size of the window.
+ Maximized(Bounds<DevicePixels>),
+ /// Indicates that the window should open in fullscreen mode.
+ /// The bounds provided here represent the restore size of the window.
+ Fullscreen(Bounds<DevicePixels>),
+}
+
+impl Default for WindowBounds {
+ fn default() -> Self {
+ WindowBounds::Windowed(Bounds::default())
+ }
+}
+
+impl WindowBounds {
+ /// Retrieve the inner bounds
+ pub fn get_bounds(&self) -> Bounds<DevicePixels> {
+ 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,
}
@@ -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<PlatformInputHandler>,
decoration_state: WaylandDecorationState,
fullscreen: bool,
+ restore_bounds: Bounds<DevicePixels>,
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<Pixels> {
@@ -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 {
@@ -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<Pixels> {
@@ -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<DevicePixels>,
}
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<Pixels> {
@@ -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<Mutex<MacWindowState>>,
drag_id: usize,
@@ -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
}
@@ -35,6 +35,7 @@ pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
pub struct WindowsWindowState {
pub origin: Point<DevicePixels>,
pub physical_size: Size<DevicePixels>,
+ pub fullscreen_restore_bounds: Bounds<DevicePixels>,
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::<WINDOWPLACEMENT>() 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<Runnable>,
@@ -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::<WINDOWPLACEMENT>() 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,
@@ -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<DisplayId>, cx: &mut AppContext) -> Bounds<
const DEFAULT_WINDOW_OFFSET: Point<DevicePixels> = point(DevicePixels(0), DevicePixels(35));
cx.active_window()
- .and_then(|w| w.update(cx, |_, cx| cx.window_bounds()).ok())
+ .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<RefCell<Vec<FrameCallback>>> = 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<DevicePixels> {
+ pub fn bounds(&self) -> Bounds<DevicePixels> {
self.window.platform_window.bounds()
}
@@ -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| {
@@ -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<gpui::DevicePixels>);
+#[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<i32> {
- 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<LocalPaths>,
Option<u64>,
- Option<SerializedWindowsBounds>,
+ Option<SerializedWindowBounds>,
Option<Uuid>,
Option<bool>,
- Option<bool>,
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<Uuid>, Option<SerializedWindowsBounds>, Option<bool>)> {
+ ) -> anyhow::Result<(Option<Uuid>, Option<SerializedWindowBounds>)> {
let mut prepared_query =
- self.select::<(Option<Uuid>, Option<SerializedWindowsBounds>, Option<bool>)>(sql!(
+ self.select::<(Option<Uuid>, Option<SerializedWindowBounds>)>(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,
}
}
@@ -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<Bounds<DevicePixels>>,
- pub(crate) fullscreen: bool,
+ pub(crate) window_bounds: Option<SerializedWindowBounds>,
pub(crate) centered_layout: bool,
pub(crate) display: Option<Uuid>,
pub(crate) docks: DockStructure,
@@ -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)
@@ -96,13 +96,12 @@ pub fn build_window_options(display_uuid: Option<Uuid>, 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()),
}