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
This commit is contained in:
parent
6ddcff25e3
commit
63a5f46df4
19 changed files with 319 additions and 219 deletions
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue