From 15799f7af63cad57d7f23f5b0d37e608406e1857 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 20 Jan 2023 12:15:21 -0800 Subject: [PATCH 1/5] wip --- crates/collab/src/tests.rs | 2 +- crates/collab_ui/src/collab_ui.rs | 2 +- crates/editor/src/persistence.rs | 2 +- crates/gpui/src/app.rs | 109 ++++++++- crates/gpui/src/platform.rs | 4 +- crates/gpui/src/platform/mac/status_item.rs | 2 + crates/gpui/src/platform/mac/window.rs | 23 ++ crates/gpui/src/platform/test.rs | 8 +- crates/gpui/src/presenter.rs | 3 +- crates/settings/src/settings.rs | 3 +- crates/sqlez/src/bindable.rs | 236 ++++++++++++-------- crates/sqlez/src/statement.rs | 11 +- crates/workspace/src/dock.rs | 2 + crates/workspace/src/persistence.rs | 121 ++++++++-- crates/workspace/src/persistence/model.rs | 33 ++- crates/workspace/src/workspace.rs | 49 ++-- crates/zed/src/zed.rs | 15 +- 17 files changed, 479 insertions(+), 146 deletions(-) diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index f257d9549358cdffcb932b535e11fb51e2ddf422..fb018a901759afeab4e0b200ac12c1b4173cba7b 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -196,7 +196,7 @@ impl TestServer { languages: Arc::new(LanguageRegistry::new(Task::ready(()))), themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), - build_window_options: Default::default, + build_window_options: |_| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), dock_default_item_factory: |_, _| unimplemented!(), }); diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index b19bc92455e136ef9fb130d35aeca1d20b8a0898..2d6b9489d809ca975ed80ceabda3afad27f57a33 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -54,7 +54,7 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }) .await?; - let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { + let (_, workspace) = cx.add_window((app_state.build_window_options)(None), |cx| { let mut workspace = Workspace::new( Default::default(), 0, diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 2d8d1a74fd152b11092eaa74e017d10f1816bc59..6e37735c1371ff466e345c95d0fa92d6d3c892a6 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -6,7 +6,7 @@ use db::{define_connection, query}; use workspace::{ItemId, WorkspaceDb, WorkspaceId}; define_connection!( - // Current table shape using pseudo-rust syntax: + // Current schema shape using pseudo-rust syntax: // editors( // item_id: usize, // workspace_id: usize, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 2e7378fc163e4703ac9bd9775c0a60644e552dfe..79695004d29aa6ebc496b708f12cc9fcf9bbec94 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -43,6 +43,7 @@ use crate::{ util::post_inc, Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, KeyUpEvent, ModifiersChangedEvent, MouseButton, MouseRegionId, PathPromptOptions, TextLayoutCache, + WindowBounds, }; pub trait Entity: 'static { @@ -594,6 +595,7 @@ type ReleaseObservationCallback = Box; type WindowActivationCallback = Box bool>; type WindowFullscreenCallback = Box bool>; +type WindowBoundsCallback = Box bool>; type KeystrokeCallback = Box< dyn FnMut(&Keystroke, &MatchResult, Option<&Box>, &mut MutableAppContext) -> bool, >; @@ -624,6 +626,7 @@ pub struct MutableAppContext { action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>, window_activation_observations: CallbackCollection, window_fullscreen_observations: CallbackCollection, + window_bounds_observations: CallbackCollection, keystroke_observations: CallbackCollection, #[allow(clippy::type_complexity)] @@ -681,6 +684,7 @@ impl MutableAppContext { global_observations: Default::default(), window_activation_observations: Default::default(), window_fullscreen_observations: Default::default(), + window_bounds_observations: Default::default(), keystroke_observations: Default::default(), action_dispatch_observations: Default::default(), presenters_and_platform_windows: Default::default(), @@ -1240,6 +1244,23 @@ impl MutableAppContext { ) } + fn observe_window_bounds(&mut self, window_id: usize, callback: F) -> Subscription + where + F: 'static + FnMut(WindowBounds, &mut MutableAppContext) -> bool, + { + let subscription_id = post_inc(&mut self.next_subscription_id); + self.pending_effects + .push_back(Effect::WindowBoundsObservation { + window_id, + subscription_id, + callback: Box::new(callback), + }); + Subscription::WindowBoundsObservation( + self.window_bounds_observations + .subscribe(window_id, subscription_id), + ) + } + pub fn observe_keystrokes(&mut self, window_id: usize, callback: F) -> Subscription where F: 'static @@ -1774,6 +1795,13 @@ impl MutableAppContext { })); } + { + let mut app = self.upgrade(); + window.on_moved(Box::new(move || { + app.update(|cx| cx.window_was_moved(window_id)) + })); + } + { let mut app = self.upgrade(); window.on_fullscreen(Box::new(move |is_fullscreen| { @@ -2071,6 +2099,11 @@ impl MutableAppContext { .invalidation .get_or_insert(WindowInvalidation::default()); } + self.handle_window_moved(window_id); + } + + Effect::MoveWindow { window_id } => { + self.handle_window_moved(window_id); } Effect::WindowActivationObservation { @@ -2103,6 +2136,16 @@ impl MutableAppContext { is_fullscreen, } => self.handle_fullscreen_effect(window_id, is_fullscreen), + Effect::WindowBoundsObservation { + window_id, + subscription_id, + callback, + } => self.window_bounds_observations.add_callback( + window_id, + subscription_id, + callback, + ), + Effect::RefreshWindows => { refreshing = true; } @@ -2197,6 +2240,11 @@ impl MutableAppContext { .push_back(Effect::ResizeWindow { window_id }); } + fn window_was_moved(&mut self, window_id: usize) { + self.pending_effects + .push_back(Effect::MoveWindow { window_id }); + } + fn window_was_fullscreen_changed(&mut self, window_id: usize, is_fullscreen: bool) { self.pending_effects.push_back(Effect::FullscreenWindow { window_id, @@ -2510,6 +2558,20 @@ impl MutableAppContext { } } + fn handle_window_moved(&mut self, window_id: usize) { + let bounds = if self.window_is_fullscreen(window_id) { + WindowBounds::Fullscreen + } else { + WindowBounds::Fixed(self.window_bounds(window_id)) + }; + self.window_bounds_observations + .clone() + .emit(window_id, self, move |callback, this| { + callback(bounds, this); + true + }); + } + pub fn focus(&mut self, window_id: usize, view_id: Option) { self.pending_effects .push_back(Effect::Focus { window_id, view_id }); @@ -2887,9 +2949,8 @@ pub enum Effect { ResizeWindow { window_id: usize, }, - FullscreenWindow { + MoveWindow { window_id: usize, - is_fullscreen: bool, }, ActivateWindow { window_id: usize, @@ -2900,11 +2961,20 @@ pub enum Effect { subscription_id: usize, callback: WindowActivationCallback, }, + FullscreenWindow { + window_id: usize, + is_fullscreen: bool, + }, WindowFullscreenObservation { window_id: usize, subscription_id: usize, callback: WindowFullscreenCallback, }, + WindowBoundsObservation { + window_id: usize, + subscription_id: usize, + callback: WindowBoundsCallback, + }, Keystroke { window_id: usize, keystroke: Keystroke, @@ -3015,6 +3085,10 @@ impl Debug for Effect { .debug_struct("Effect::RefreshWindow") .field("window_id", window_id) .finish(), + Effect::MoveWindow { window_id } => f + .debug_struct("Effect::MoveWindow") + .field("window_id", window_id) + .finish(), Effect::WindowActivationObservation { window_id, subscription_id, @@ -3049,6 +3123,16 @@ impl Debug for Effect { .field("window_id", window_id) .field("subscription_id", subscription_id) .finish(), + + Effect::WindowBoundsObservation { + window_id, + subscription_id, + callback: _, + } => f + .debug_struct("Effect::WindowBoundsObservation") + .field("window_id", window_id) + .field("subscription_id", subscription_id) + .finish(), Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(), Effect::WindowShouldCloseSubscription { window_id, .. } => f .debug_struct("Effect::WindowShouldCloseSubscription") @@ -3928,6 +4012,24 @@ impl<'a, T: View> ViewContext<'a, T> { ) } + pub fn observe_window_bounds(&mut self, mut callback: F) -> Subscription + where + F: 'static + FnMut(&mut T, WindowBounds, &mut ViewContext), + { + let observer = self.weak_handle(); + self.app + .observe_window_bounds(self.window_id(), move |bounds, cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, bounds, cx); + }); + true + } else { + false + } + }) + } + pub fn emit(&mut self, payload: T::Event) { self.app.pending_effects.push_back(Effect::Event { entity_id: self.view_id, @@ -5092,6 +5194,7 @@ pub enum Subscription { FocusObservation(callback_collection::Subscription), WindowActivationObservation(callback_collection::Subscription), WindowFullscreenObservation(callback_collection::Subscription), + WindowBoundsObservation(callback_collection::Subscription), KeystrokeObservation(callback_collection::Subscription), ReleaseObservation(callback_collection::Subscription), ActionObservation(callback_collection::Subscription<(), ActionObservationCallback>), @@ -5107,6 +5210,7 @@ impl Subscription { Subscription::FocusObservation(subscription) => subscription.id(), Subscription::WindowActivationObservation(subscription) => subscription.id(), Subscription::WindowFullscreenObservation(subscription) => subscription.id(), + Subscription::WindowBoundsObservation(subscription) => subscription.id(), Subscription::KeystrokeObservation(subscription) => subscription.id(), Subscription::ReleaseObservation(subscription) => subscription.id(), Subscription::ActionObservation(subscription) => subscription.id(), @@ -5123,6 +5227,7 @@ impl Subscription { Subscription::KeystrokeObservation(subscription) => subscription.detach(), Subscription::WindowActivationObservation(subscription) => subscription.detach(), Subscription::WindowFullscreenObservation(subscription) => subscription.detach(), + Subscription::WindowBoundsObservation(subscription) => subscription.detach(), Subscription::ReleaseObservation(subscription) => subscription.detach(), Subscription::ActionObservation(subscription) => subscription.detach(), } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 05ba61a9adc5efa15efe321816cc80dca8e5a65a..1db1fe62b0926ab918a24aa95dac17584d92c382 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -126,6 +126,7 @@ pub trait Window { fn on_active_status_change(&mut self, callback: Box); fn on_resize(&mut self, callback: Box); fn on_fullscreen(&mut self, callback: Box); + fn on_moved(&mut self, callback: Box); fn on_should_close(&mut self, callback: Box bool>); fn on_close(&mut self, callback: Box); fn set_input_handler(&mut self, input_handler: Box); @@ -186,8 +187,9 @@ pub enum WindowKind { PopUp, } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub enum WindowBounds { + Fullscreen, Maximized, Fixed(RectF), } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index 2da7caab7ebc23cbefafc35bf22b130702570171..8ac9dbea71de4ccd724d5a718c42a4e11876aad5 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -183,6 +183,8 @@ impl platform::Window for StatusItem { fn on_resize(&mut self, _: Box) {} + fn on_moved(&mut self, _: Box) {} + fn on_fullscreen(&mut self, _: Box) {} fn on_should_close(&mut self, _: Box bool>) {} diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bef6f65e428183de8d3314dfb230ca5d21deb6a7..c4cd7d0e27d1b8688dbd4231e47553c4ad252ebd 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -295,6 +295,10 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C sel!(windowWillExitFullScreen:), window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowDidMove:), + window_did_move as extern "C" fn(&Object, Sel, id), + ); decl.add_method( sel!(windowDidBecomeKey:), window_did_change_key_status as extern "C" fn(&Object, Sel, id), @@ -333,6 +337,7 @@ struct WindowState { activate_callback: Option>, resize_callback: Option>, fullscreen_callback: Option>, + moved_callback: Option>, should_close_callback: Option bool>>, close_callback: Option>, appearance_changed_callback: Option>, @@ -405,6 +410,9 @@ impl Window { let screen = native_window.screen(); match options.bounds { + WindowBounds::Fullscreen => { + native_window.toggleFullScreen_(nil); + } WindowBounds::Maximized => { native_window.setFrame_display_(screen.visibleFrame(), YES); } @@ -441,6 +449,7 @@ impl Window { close_callback: None, activate_callback: None, fullscreen_callback: None, + moved_callback: None, appearance_changed_callback: None, input_handler: None, pending_key_down: None, @@ -588,6 +597,10 @@ impl platform::Window for Window { self.0.as_ref().borrow_mut().resize_callback = Some(callback); } + fn on_moved(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().moved_callback = Some(callback); + } + fn on_fullscreen(&mut self, callback: Box) { self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); } @@ -1137,6 +1150,16 @@ fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) { } } +extern "C" fn window_did_move(this: &Object, _: Sel, _: id) { + let window_state = unsafe { get_window_state(this) }; + let mut window_state_borrow = window_state.as_ref().borrow_mut(); + if let Some(mut callback) = window_state_borrow.moved_callback.take() { + drop(window_state_borrow); + callback(); + window_state.borrow_mut().moved_callback = Some(callback); + } +} + extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; let window_state_borrow = window_state.borrow(); diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index d33e3e2fca5fe2ea6ec65cb85b96790ab0936235..f3e1ce405577811e7d278359e99586427c285f47 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -40,6 +40,7 @@ pub struct Window { current_scene: Option, event_handlers: Vec bool>>, pub(crate) resize_handlers: Vec>, + pub(crate) moved_handlers: Vec>, close_handlers: Vec>, fullscreen_handlers: Vec>, pub(crate) active_status_change_handlers: Vec>, @@ -143,7 +144,7 @@ impl super::Platform for Platform { _executor: Rc, ) -> Box { Box::new(Window::new(match options.bounds { - WindowBounds::Maximized => vec2f(1024., 768.), + WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.), WindowBounds::Fixed(rect) => rect.size(), })) } @@ -225,6 +226,7 @@ impl Window { size, event_handlers: Default::default(), resize_handlers: Default::default(), + moved_handlers: Default::default(), close_handlers: Default::default(), should_close_handler: Default::default(), active_status_change_handlers: Default::default(), @@ -273,6 +275,10 @@ impl super::Window for Window { self.resize_handlers.push(callback); } + fn on_moved(&mut self, callback: Box) { + self.moved_handlers.push(callback); + } + fn on_close(&mut self, callback: Box) { self.close_handlers.push(callback); } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 499e0df93eea1b61733c956d6f8880d1bbe265d3..9d2cd1336d88237d6746fe0adcf29eca4cf56b77 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -23,7 +23,7 @@ use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use smallvec::SmallVec; use sqlez::{ - bindable::{Bind, Column}, + bindable::{Bind, Column, StaticRowComponent}, statement::Statement, }; use std::{ @@ -932,6 +932,7 @@ impl ToJson for Axis { } } +impl StaticRowComponent for Axis {} impl Bind for Axis { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 8b2c12a59bf8b673965838ed62319318a0574c6f..92236bad616f9e54499b23d0c16d290640035717 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -15,7 +15,7 @@ use schemars::{ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use sqlez::{ - bindable::{Bind, Column}, + bindable::{Bind, Column, StaticRowComponent}, statement::Statement, }; use std::{collections::HashMap, fmt::Write as _, num::NonZeroU32, str, sync::Arc}; @@ -253,6 +253,7 @@ pub enum DockAnchor { Expanded, } +impl StaticRowComponent for DockAnchor {} impl Bind for DockAnchor { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 62212d8f18c66a9c59a4941b0fbb9268a4e5dc90..51d4f5d4a365fd669508f66e9d436ce16aab40c4 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -9,14 +9,37 @@ use anyhow::{Context, Result}; use crate::statement::{SqlType, Statement}; -pub trait Bind { +pub trait StaticRowComponent { + fn static_column_count() -> usize { + 1 + } +} + +pub trait RowComponent { + fn column_count(&self) -> usize; +} + +impl RowComponent for T { + fn column_count(&self) -> usize { + T::static_column_count() + } +} + +impl StaticRowComponent for &T { + fn static_column_count() -> usize { + T::static_column_count() + } +} + +pub trait Bind: RowComponent { fn bind(&self, statement: &Statement, start_index: i32) -> Result; } -pub trait Column: Sized { +pub trait Column: Sized + RowComponent { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)>; } +impl StaticRowComponent for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -33,6 +56,7 @@ impl Column for bool { } } +impl StaticRowComponent for &[u8] {} impl Bind for &[u8] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -42,6 +66,7 @@ impl Bind for &[u8] { } } +impl StaticRowComponent for &[u8; C] {} impl Bind for &[u8; C] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -51,6 +76,7 @@ impl Bind for &[u8; C] { } } +impl StaticRowComponent for Vec {} impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -70,6 +96,7 @@ impl Column for Vec { } } +impl StaticRowComponent for f64 {} impl Bind for f64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -89,6 +116,7 @@ impl Column for f64 { } } +impl StaticRowComponent for f32 {} impl Bind for f32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -109,6 +137,7 @@ impl Column for f32 { } } +impl StaticRowComponent for i32 {} impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -126,6 +155,7 @@ impl Column for i32 { } } +impl StaticRowComponent for i64 {} impl Bind for i64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -142,6 +172,7 @@ impl Column for i64 { } } +impl StaticRowComponent for u32 {} impl Bind for u32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -157,6 +188,7 @@ impl Column for u32 { } } +impl StaticRowComponent for usize {} impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -172,6 +204,7 @@ impl Column for usize { } } +impl StaticRowComponent for &str {} impl Bind for &str { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -179,6 +212,7 @@ impl Bind for &str { } } +impl StaticRowComponent for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self.as_ref())?; @@ -186,6 +220,7 @@ impl Bind for Arc { } } +impl StaticRowComponent for String {} impl Bind for String { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -207,28 +242,41 @@ impl Column for String { } } -impl Bind for Option { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { +impl StaticRowComponent for Option { + fn static_column_count() -> usize { + T::static_column_count() + } +} +impl Bind for Option { + fn bind(&self, statement: &Statement, mut start_index: i32) -> Result { if let Some(this) = self { this.bind(statement, start_index) } else { - statement.bind_null(start_index)?; - Ok(start_index + 1) + for _ in 0..T::static_column_count() { + statement.bind_null(start_index)?; + start_index += 1; + } + Ok(start_index) } } } -impl Column for Option { +impl Column for Option { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { if let SqlType::Null = statement.column_type(start_index)? { - Ok((None, start_index + 1)) + Ok((None, start_index + T::static_column_count() as i32)) } else { T::column(statement, start_index).map(|(result, next_index)| (Some(result), next_index)) } } } -impl Bind for [T; COUNT] { +impl StaticRowComponent for [T; COUNT] { + fn static_column_count() -> usize { + T::static_column_count() * COUNT + } +} +impl Bind for [T; COUNT] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let mut current_index = start_index; for binding in self { @@ -239,7 +287,7 @@ impl Bind for [T; COUNT] { } } -impl Column for [T; COUNT] { +impl Column for [T; COUNT] { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let mut array = [Default::default(); COUNT]; let mut current_index = start_index; @@ -250,40 +298,21 @@ impl Column for [T; COUNT] { } } -impl Bind for Vec { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let mut current_index = start_index; - for binding in self.iter() { - current_index = binding.bind(statement, current_index)? - } - - Ok(current_index) - } -} - -impl Bind for &[T] { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let mut current_index = start_index; - for binding in *self { - current_index = binding.bind(statement, current_index)? - } - - Ok(current_index) - } -} - +impl StaticRowComponent for &Path {} impl Bind for &Path { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_os_str().as_bytes().bind(statement, start_index) } } +impl StaticRowComponent for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_ref().bind(statement, start_index) } } +impl StaticRowComponent for PathBuf {} impl Bind for PathBuf { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (self.as_ref() as &Path).bind(statement, start_index) @@ -301,6 +330,11 @@ impl Column for PathBuf { } } +impl StaticRowComponent for () { + fn static_column_count() -> usize { + 0 + } +} /// Unit impls do nothing. This simplifies query macros impl Bind for () { fn bind(&self, _statement: &Statement, start_index: i32) -> Result { @@ -314,74 +348,80 @@ impl Column for () { } } -impl Bind for (T1, T2) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - self.1.bind(statement, next_index) - } -} - -impl Column for (T1, T2) { - fn column<'a>(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - Ok(((first, second), next_index)) - } -} - -impl Bind for (T1, T2, T3) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - let next_index = self.1.bind(statement, next_index)?; - self.2.bind(statement, next_index) - } -} - -impl Column for (T1, T2, T3) { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - let (third, next_index) = T3::column(statement, next_index)?; - Ok(((first, second, third), next_index)) - } -} - -impl Bind for (T1, T2, T3, T4) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - let next_index = self.1.bind(statement, next_index)?; - let next_index = self.2.bind(statement, next_index)?; - self.3.bind(statement, next_index) - } -} +macro_rules! impl_tuple_row_traits { + ( $($local:ident: $type:ident),+ ) => { + impl<$($type: RowComponent),+> RowComponent for ($($type,)+) { + fn column_count(&self) -> usize { + let ($($local,)+) = self; + let mut count = 0; + $(count += $local.column_count();)+ + count + } + } -impl Column for (T1, T2, T3, T4) { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - let (third, next_index) = T3::column(statement, next_index)?; - let (fourth, next_index) = T4::column(statement, next_index)?; - Ok(((first, second, third, fourth), next_index)) - } -} + impl<$($type: Bind),+> Bind for ($($type,)+) { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let mut next_index = start_index; + let ($($local,)+) = self; + $(next_index = $local.bind(statement, next_index)?;)+ + Ok(next_index) + } + } -impl Bind for (T1, T2, T3, T4, T5) { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { - let next_index = self.0.bind(statement, start_index)?; - let next_index = self.1.bind(statement, next_index)?; - let next_index = self.2.bind(statement, next_index)?; - let next_index = self.3.bind(statement, next_index)?; - self.4.bind(statement, next_index) + impl<$($type: Column),+> Column for ($($type,)+) { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let mut next_index = start_index; + Ok(( + ( + $({ + let value; + (value, next_index) = $type::column(statement, next_index)?; + value + },)+ + ), + next_index, + )) + } + } } } -impl Column for (T1, T2, T3, T4, T5) { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let (first, next_index) = T1::column(statement, start_index)?; - let (second, next_index) = T2::column(statement, next_index)?; - let (third, next_index) = T3::column(statement, next_index)?; - let (fourth, next_index) = T4::column(statement, next_index)?; - let (fifth, next_index) = T5::column(statement, next_index)?; - Ok(((first, second, third, fourth, fifth), next_index)) - } -} +impl_tuple_row_traits!(t1: T1, t2: T2); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6); +impl_tuple_row_traits!(t1: T1, t2: T2, t3: T3, t4: T4, t5: T5, t6: T6, t7: T7); +impl_tuple_row_traits!( + t1: T1, + t2: T2, + t3: T3, + t4: T4, + t5: T5, + t6: T6, + t7: T7, + t8: T8 +); +impl_tuple_row_traits!( + t1: T1, + t2: T2, + t3: T3, + t4: T4, + t5: T5, + t6: T6, + t7: T7, + t8: T8, + t9: T9 +); +impl_tuple_row_traits!( + t1: T1, + t2: T2, + t3: T3, + t4: T4, + t5: T5, + t6: T6, + t7: T7, + t8: T8, + t9: T9, + t10: T10 +); diff --git a/crates/sqlez/src/statement.rs b/crates/sqlez/src/statement.rs index f3ec6d48541b8f2fde85a1cffdcaba99501315c2..da4f2388d9f08baa9186dcf4239d87eda42d0503 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -238,11 +238,14 @@ impl<'a> Statement<'a> { pub fn bind(&self, value: T, index: i32) -> Result { debug_assert!(index > 0); - value.bind(self, index) + let after = value.bind(self, index)?; + debug_assert_eq!((after - index) as usize, value.column_count()); + Ok(after) } pub fn column(&mut self) -> Result { - let (result, _) = T::column(self, 0)?; + let (result, after) = T::column(self, 0)?; + debug_assert_eq!(T::column_count(&result), after as usize); Ok(result) } @@ -260,7 +263,9 @@ impl<'a> Statement<'a> { } pub fn with_bindings(&mut self, bindings: impl Bind) -> Result<&mut Self> { - self.bind(bindings, 1)?; + let column_count = bindings.column_count(); + let after = self.bind(bindings, 1)?; + debug_assert_eq!((after - 1) as usize, column_count); Ok(self) } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 747541f87d6faaed9a3e3883a9a4d0e5f5072403..7a31d02433193d9f798334486c0027f2c55e5400 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -534,6 +534,8 @@ mod tests { }], }, left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 03a866f2f6cf362a2592e4bd8ced681aae5fd17c..1e8e72a3d228c684523a93e34bf2daf0d7d771a0 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -6,7 +6,7 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::Axis; +use gpui::{Axis, geometry::{rect::RectF, vector::Vector2F}}; use util::{unzip_option, ResultExt}; @@ -19,6 +19,51 @@ use model::{ }; define_connection! { + // Current schema shape using pseudo-rust syntax: + // + // workspaces( + // workspace_id: usize, // Primary key for workspaces + // workspace_location: Bincode>, + // dock_visible: bool, + // dock_anchor: DockAnchor, // 'Bottom' / 'Right' / 'Expanded' + // dock_pane: Option, // PaneId + // left_sidebar_open: boolean, + // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS + // fullscreen bool, // Boolean + // window_x: f32, + // window_y: f32, + // window_width: f32, + // window_height: f32, + // ) + // + // pane_groups( + // group_id: usize, // Primary key for pane_groups + // workspace_id: usize, // References workspaces table + // parent_group_id: Option, // None indicates that this is the root node + // position: Optiopn, // None indicates that this is the root node + // axis: Option, // 'Vertical', 'Horizontal' + // ) + // + // panes( + // pane_id: usize, // Primary key for panes + // workspace_id: usize, // References workspaces table + // active: bool, + // ) + // + // center_panes( + // pane_id: usize, // Primary key for center_panes + // parent_group_id: Option, // References pane_groups. If none, this is the root + // position: Option, // None indicates this is the root + // ) + // + // CREATE TABLE items( + // item_id: usize, // This is the item's view id, so this is not unique + // workspace_id: usize, // References workspaces table + // pane_id: usize, // References panes table + // kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global + // position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column + // active: bool, // Indicates if this item is the active one in the pane + // ) pub static ref DB: WorkspaceDb<()> = &[sql!( CREATE TABLE workspaces( @@ -39,8 +84,8 @@ define_connection! { position INTEGER, // NULL indicates that this is a root node axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, + ON DELETE CASCADE + ON UPDATE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; @@ -49,8 +94,8 @@ define_connection! { workspace_id INTEGER NOT NULL, active INTEGER NOT NULL, // Boolean FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE + ON DELETE CASCADE + ON UPDATE CASCADE ) STRICT; CREATE TABLE center_panes( @@ -58,7 +103,7 @@ define_connection! { parent_group_id INTEGER, // NULL means that this is a root pane position INTEGER, // NULL means that this is a root pane FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, + ON DELETE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; @@ -70,12 +115,19 @@ define_connection! { position INTEGER NOT NULL, active INTEGER NOT NULL, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, + ON DELETE CASCADE + ON UPDATE CASCADE, FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, + ON DELETE CASCADE, PRIMARY KEY(item_id, workspace_id) ) STRICT; + ), + sql!( + ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean + ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever )]; } @@ -91,14 +143,26 @@ impl WorkspaceDb { // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position): ( + let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_x, window_y, window_width, window_height): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, - ) = - self.select_row_bound(sql!{ - SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor + bool, + f32, f32, f32, f32 + ) = self + .select_row_bound(sql! { + SELECT + workspace_id, + workspace_location, + left_sidebar_open, + dock_visible, + dock_anchor, + fullscreen, + window_x, + window_y, + window_width, + window_height FROM workspaces WHERE workspace_location = ? }) @@ -120,6 +184,11 @@ impl WorkspaceDb { .log_err()?, dock_position, left_sidebar_open, + fullscreen, + bounds: Some(RectF::new( + Vector2F::new(window_x, window_y), + Vector2F::new(window_width, window_height) + )) }) } @@ -410,6 +479,18 @@ impl WorkspaceDb { WHERE workspace_id = ? } } + + query! { + pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, x: f32, y: f32, width: f32, height: f32) -> Result<()> { + UPDATE workspaces + SET fullscreen = ?2, + window_x = ?3, + window_y = ?4, + window_width = ?5, + window_height = ?6 + WHERE workspace_id = ?1 + } + } } #[cfg(test)] @@ -499,6 +580,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), }; let mut workspace_2 = SerializedWorkspace { @@ -508,6 +591,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -614,6 +699,8 @@ mod tests { center_group, dock_pane, left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace.clone()).await; @@ -642,6 +729,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), }; let mut workspace_2 = SerializedWorkspace { @@ -651,6 +740,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -687,6 +778,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + fullscreen: false, + bounds: Default::default(), }; db.save_workspace(workspace_3.clone()).await; @@ -722,6 +815,8 @@ mod tests { center_group: center_group.clone(), dock_pane, left_sidebar_open: true, + fullscreen: false, + bounds: Default::default(), } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index b264114fb68820203f90cd8b139d2d85a4836864..4bc8881bbae6e1bf1067ab5163ca54872c03b6cc 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -6,10 +6,12 @@ use std::{ use anyhow::{Context, Result}; use async_recursion::async_recursion; -use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle}; +use gpui::{ + geometry::rect::RectF, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds, +}; use db::sqlez::{ - bindable::{Bind, Column}, + bindable::{Bind, Column, StaticRowComponent}, statement::Statement, }; use project::Project; @@ -40,6 +42,7 @@ impl, T: IntoIterator> From for WorkspaceLocation { } } +impl StaticRowComponent for WorkspaceLocation {} impl Bind for &WorkspaceLocation { fn bind(&self, statement: &Statement, start_index: i32) -> Result { bincode::serialize(&self.0) @@ -58,7 +61,7 @@ impl Column for WorkspaceLocation { } } -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct SerializedWorkspace { pub id: WorkspaceId, pub location: WorkspaceLocation, @@ -66,6 +69,20 @@ pub struct SerializedWorkspace { pub center_group: SerializedPaneGroup, pub dock_pane: SerializedPane, pub left_sidebar_open: bool, + pub fullscreen: bool, + pub bounds: Option, +} + +impl SerializedWorkspace { + pub fn bounds(&self) -> WindowBounds { + if self.fullscreen { + WindowBounds::Fullscreen + } else if let Some(bounds) = self.bounds { + WindowBounds::Fixed(bounds) + } else { + WindowBounds::Maximized + } + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -237,6 +254,11 @@ impl Default for SerializedItem { } } +impl StaticRowComponent for SerializedItem { + fn static_column_count() -> usize { + 3 + } +} impl Bind for &SerializedItem { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(self.kind.clone(), start_index)?; @@ -261,6 +283,11 @@ impl Column for SerializedItem { } } +impl StaticRowComponent for DockPosition { + fn static_column_count() -> usize { + 2 + } +} impl Bind for DockPosition { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let next_index = statement.bind(self.is_visible(), start_index)?; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index b0abe09070fe25a7ed27266b783410efb48b8bd4..addbb9b017f681b7ad51b77fa9fb682f366088f6 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -38,7 +38,7 @@ use gpui::{ platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, + Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -339,7 +339,7 @@ pub struct AppState { pub client: Arc, pub user_store: ModelHandle, pub fs: Arc, - pub build_window_options: fn() -> WindowOptions<'static>, + pub build_window_options: fn(Option) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), pub dock_default_item_factory: DockDefaultItemFactory, } @@ -366,7 +366,7 @@ impl AppState { languages, user_store, initialize_workspace: |_, _, _| {}, - build_window_options: Default::default, + build_window_options: |_| Default::default(), dock_default_item_factory: |_, _| unimplemented!(), }) } @@ -682,17 +682,36 @@ impl Workspace { }; // Use the serialized workspace to construct the new window - let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { - let mut workspace = Workspace::new( - serialized_workspace, - workspace_id, - project_handle, - app_state.dock_default_item_factory, - cx, - ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - workspace - }); + let (_, workspace) = cx.add_window( + (app_state.build_window_options)( + serialized_workspace.as_ref().map(|sw| sw.bounds()), + ), + |cx| { + let mut workspace = Workspace::new( + serialized_workspace, + workspace_id, + project_handle, + app_state.dock_default_item_factory, + cx, + ); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + cx.observe_window_bounds(move |_, bounds, cx| { + let fullscreen = cx.window_is_fullscreen(cx.window_id()); + cx.background() + .spawn(DB.set_bounds( + workspace_id, + fullscreen, + bounds.min_x(), + bounds.min_y(), + bounds.width(), + bounds.height(), + )) + .detach_and_log_err(cx); + }) + .detach(); + workspace + }, + ); notify_if_database_failed(&workspace, &mut cx); @@ -2327,6 +2346,8 @@ impl Workspace { dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), + fullscreen: false, + bounds: Default::default(), }; cx.background() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c9f8b2d408441aee28a63b1b6242e62aafb61dcc..3529e1c6dc61257ce2ef9390ed805e98bbe6b4c0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -369,12 +369,15 @@ pub fn initialize_workspace( }); } -pub fn build_window_options() -> WindowOptions<'static> { - let bounds = if let Some((position, size)) = ZED_WINDOW_POSITION.zip(*ZED_WINDOW_SIZE) { - WindowBounds::Fixed(RectF::new(position, size)) - } else { - WindowBounds::Maximized - }; +pub fn build_window_options(bounds: Option) -> WindowOptions<'static> { + let bounds = bounds + .or_else(|| { + ZED_WINDOW_POSITION + .zip(*ZED_WINDOW_SIZE) + .map(|(position, size)| WindowBounds::Fixed(RectF::new(position, size))) + }) + .unwrap_or(WindowBounds::Maximized); + WindowOptions { bounds, titlebar: Some(TitlebarOptions { From a581d0c5b82087c3e6bfe93c0f4f8204d4196f00 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Sun, 22 Jan 2023 20:33:21 -0800 Subject: [PATCH 2/5] wip --- crates/gpui/src/app.rs | 13 +- crates/gpui/src/presenter.rs | 4 +- crates/settings/src/settings.rs | 4 +- crates/sqlez/src/bindable.rs | 92 ++--- crates/sqlez/src/statement.rs | 12 +- crates/workspace/src/persistence.rs | 450 +++++++++++----------- crates/workspace/src/persistence/model.rs | 12 +- crates/workspace/src/workspace.rs | 25 +- 8 files changed, 301 insertions(+), 311 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 79695004d29aa6ebc496b708f12cc9fcf9bbec94..df62580c4d0a66bffc3899aa3c4a0f571c0fda53 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2377,11 +2377,20 @@ impl MutableAppContext { let window = this.cx.windows.get_mut(&window_id)?; window.is_fullscreen = is_fullscreen; - let mut observations = this.window_fullscreen_observations.clone(); - observations.emit(window_id, this, |callback, this| { + let mut fullscreen_observations = this.window_fullscreen_observations.clone(); + fullscreen_observations.emit(window_id, this, |callback, this| { callback(is_fullscreen, this) }); + let bounds = if this.window_is_fullscreen(window_id) { + WindowBounds::Fullscreen + } else { + WindowBounds::Fixed(this.window_bounds(window_id)) + }; + + let mut bounds_observations = this.window_bounds_observations.clone(); + bounds_observations.emit(window_id, this, |callback, this| callback(bounds, this)); + Some(()) }); } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 9d2cd1336d88237d6746fe0adcf29eca4cf56b77..66c991f0a87f94916f21208a2f9b013da4d2b074 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -23,7 +23,7 @@ use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use smallvec::SmallVec; use sqlez::{ - bindable::{Bind, Column, StaticRowComponent}, + bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use std::{ @@ -932,7 +932,7 @@ impl ToJson for Axis { } } -impl StaticRowComponent for Axis {} +impl StaticColumnCount for Axis {} impl Bind for Axis { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 92236bad616f9e54499b23d0c16d290640035717..6e25222d96eb83079db7c1805dc7d32b8aa198e3 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -15,7 +15,7 @@ use schemars::{ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use sqlez::{ - bindable::{Bind, Column, StaticRowComponent}, + bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use std::{collections::HashMap, fmt::Write as _, num::NonZeroU32, str, sync::Arc}; @@ -253,7 +253,7 @@ pub enum DockAnchor { Expanded, } -impl StaticRowComponent for DockAnchor {} +impl StaticColumnCount for DockAnchor {} impl Bind for DockAnchor { fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result { match self { diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 51d4f5d4a365fd669508f66e9d436ce16aab40c4..9d5d0c8b2f9a62a43dddca9b8c9460bb0edb5f9f 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -9,37 +9,21 @@ use anyhow::{Context, Result}; use crate::statement::{SqlType, Statement}; -pub trait StaticRowComponent { - fn static_column_count() -> usize { +pub trait StaticColumnCount { + fn column_count() -> usize { 1 } } -pub trait RowComponent { - fn column_count(&self) -> usize; -} - -impl RowComponent for T { - fn column_count(&self) -> usize { - T::static_column_count() - } -} - -impl StaticRowComponent for &T { - fn static_column_count() -> usize { - T::static_column_count() - } -} - -pub trait Bind: RowComponent { +pub trait Bind { fn bind(&self, statement: &Statement, start_index: i32) -> Result; } -pub trait Column: Sized + RowComponent { +pub trait Column: Sized { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)>; } -impl StaticRowComponent for bool {} +impl StaticColumnCount for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -56,7 +40,7 @@ impl Column for bool { } } -impl StaticRowComponent for &[u8] {} +impl StaticColumnCount for &[u8] {} impl Bind for &[u8] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -66,7 +50,7 @@ impl Bind for &[u8] { } } -impl StaticRowComponent for &[u8; C] {} +impl StaticColumnCount for &[u8; C] {} impl Bind for &[u8; C] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -76,7 +60,7 @@ impl Bind for &[u8; C] { } } -impl StaticRowComponent for Vec {} +impl StaticColumnCount for Vec {} impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -96,7 +80,7 @@ impl Column for Vec { } } -impl StaticRowComponent for f64 {} +impl StaticColumnCount for f64 {} impl Bind for f64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -116,7 +100,7 @@ impl Column for f64 { } } -impl StaticRowComponent for f32 {} +impl StaticColumnCount for f32 {} impl Bind for f32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -137,7 +121,7 @@ impl Column for f32 { } } -impl StaticRowComponent for i32 {} +impl StaticColumnCount for i32 {} impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -155,7 +139,7 @@ impl Column for i32 { } } -impl StaticRowComponent for i64 {} +impl StaticColumnCount for i64 {} impl Bind for i64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -172,7 +156,7 @@ impl Column for i64 { } } -impl StaticRowComponent for u32 {} +impl StaticColumnCount for u32 {} impl Bind for u32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -188,7 +172,7 @@ impl Column for u32 { } } -impl StaticRowComponent for usize {} +impl StaticColumnCount for usize {} impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -204,7 +188,7 @@ impl Column for usize { } } -impl StaticRowComponent for &str {} +impl StaticColumnCount for &str {} impl Bind for &str { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -212,7 +196,7 @@ impl Bind for &str { } } -impl StaticRowComponent for Arc {} +impl StaticColumnCount for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self.as_ref())?; @@ -220,7 +204,7 @@ impl Bind for Arc { } } -impl StaticRowComponent for String {} +impl StaticColumnCount for String {} impl Bind for String { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -242,17 +226,18 @@ impl Column for String { } } -impl StaticRowComponent for Option { - fn static_column_count() -> usize { - T::static_column_count() +impl StaticColumnCount for Option { + fn column_count() -> usize { + T::column_count() } } -impl Bind for Option { +impl Bind for Option { fn bind(&self, statement: &Statement, mut start_index: i32) -> Result { if let Some(this) = self { this.bind(statement, start_index) } else { - for _ in 0..T::static_column_count() { + for i in 0..T::column_count() { + dbg!(i); statement.bind_null(start_index)?; start_index += 1; } @@ -261,22 +246,22 @@ impl Bind for Option { } } -impl Column for Option { +impl Column for Option { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { if let SqlType::Null = statement.column_type(start_index)? { - Ok((None, start_index + T::static_column_count() as i32)) + Ok((None, start_index + T::column_count() as i32)) } else { T::column(statement, start_index).map(|(result, next_index)| (Some(result), next_index)) } } } -impl StaticRowComponent for [T; COUNT] { - fn static_column_count() -> usize { - T::static_column_count() * COUNT +impl StaticColumnCount for [T; COUNT] { + fn column_count() -> usize { + T::column_count() * COUNT } } -impl Bind for [T; COUNT] { +impl Bind for [T; COUNT] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let mut current_index = start_index; for binding in self { @@ -287,7 +272,7 @@ impl Bind for [T; COUNT] { } } -impl Column for [T; COUNT] { +impl Column for [T; COUNT] { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { let mut array = [Default::default(); COUNT]; let mut current_index = start_index; @@ -298,21 +283,21 @@ impl Column } } -impl StaticRowComponent for &Path {} +impl StaticColumnCount for &Path {} impl Bind for &Path { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_os_str().as_bytes().bind(statement, start_index) } } -impl StaticRowComponent for Arc {} +impl StaticColumnCount for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_ref().bind(statement, start_index) } } -impl StaticRowComponent for PathBuf {} +impl StaticColumnCount for PathBuf {} impl Bind for PathBuf { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (self.as_ref() as &Path).bind(statement, start_index) @@ -330,8 +315,8 @@ impl Column for PathBuf { } } -impl StaticRowComponent for () { - fn static_column_count() -> usize { +impl StaticColumnCount for () { + fn column_count() -> usize { 0 } } @@ -350,11 +335,10 @@ impl Column for () { macro_rules! impl_tuple_row_traits { ( $($local:ident: $type:ident),+ ) => { - impl<$($type: RowComponent),+> RowComponent for ($($type,)+) { - fn column_count(&self) -> usize { - let ($($local,)+) = self; + impl<$($type: StaticColumnCount),+> StaticColumnCount for ($($type,)+) { + fn column_count() -> usize { let mut count = 0; - $(count += $local.column_count();)+ + $(count += $type::column_count();)+ count } } diff --git a/crates/sqlez/src/statement.rs b/crates/sqlez/src/statement.rs index da4f2388d9f08baa9186dcf4239d87eda42d0503..69d5685ba02ceadeeb5bec364366c76121aa12a9 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -238,15 +238,11 @@ impl<'a> Statement<'a> { pub fn bind(&self, value: T, index: i32) -> Result { debug_assert!(index > 0); - let after = value.bind(self, index)?; - debug_assert_eq!((after - index) as usize, value.column_count()); - Ok(after) + Ok(value.bind(self, index)?) } pub fn column(&mut self) -> Result { - let (result, after) = T::column(self, 0)?; - debug_assert_eq!(T::column_count(&result), after as usize); - Ok(result) + Ok(T::column(self, 0)?.0) } pub fn column_type(&mut self, index: i32) -> Result { @@ -263,9 +259,7 @@ impl<'a> Statement<'a> { } pub fn with_bindings(&mut self, bindings: impl Bind) -> Result<&mut Self> { - let column_count = bindings.column_count(); - let after = self.bind(bindings, 1)?; - debug_assert_eq!((after - 1) as usize, column_count); + self.bind(bindings, 1)?; Ok(self) } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 1e8e72a3d228c684523a93e34bf2daf0d7d771a0..7711ee5141319cf88f44b721bce3c79874a327d1 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -65,70 +65,70 @@ define_connection! { // active: bool, // Indicates if this item is the active one in the pane // ) pub static ref DB: WorkspaceDb<()> = - &[sql!( - CREATE TABLE workspaces( - workspace_id INTEGER PRIMARY KEY, - workspace_location BLOB UNIQUE, - dock_visible INTEGER, // Boolean - dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded' - dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet - left_sidebar_open INTEGER, //Boolean - timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) - ) STRICT; - - CREATE TABLE pane_groups( - group_id INTEGER PRIMARY KEY, - workspace_id INTEGER NOT NULL, - parent_group_id INTEGER, // NULL indicates that this is a root node - position INTEGER, // NULL indicates that this is a root node - axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' - FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE - ) STRICT; - - CREATE TABLE panes( - pane_id INTEGER PRIMARY KEY, - workspace_id INTEGER NOT NULL, - active INTEGER NOT NULL, // Boolean - FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE - ) STRICT; - - CREATE TABLE center_panes( - pane_id INTEGER PRIMARY KEY, - parent_group_id INTEGER, // NULL means that this is a root pane - position INTEGER, // NULL means that this is a root pane - FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, - FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE - ) STRICT; - - CREATE TABLE items( - item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique - workspace_id INTEGER NOT NULL, - pane_id INTEGER NOT NULL, - kind TEXT NOT NULL, - position INTEGER NOT NULL, - active INTEGER NOT NULL, - FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY(pane_id) REFERENCES panes(pane_id) - ON DELETE CASCADE, - PRIMARY KEY(item_id, workspace_id) - ) STRICT; - ), - sql!( - ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean - ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever - )]; + &[sql!( + CREATE TABLE workspaces( + workspace_id INTEGER PRIMARY KEY, + workspace_location BLOB UNIQUE, + dock_visible INTEGER, // Boolean + dock_anchor TEXT, // Enum: 'Bottom' / 'Right' / 'Expanded' + dock_pane INTEGER, // NULL indicates that we don't have a dock pane yet + left_sidebar_open INTEGER, //Boolean + timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) + ) STRICT; + + CREATE TABLE pane_groups( + group_id INTEGER PRIMARY KEY, + workspace_id INTEGER NOT NULL, + parent_group_id INTEGER, // NULL indicates that this is a root node + position INTEGER, // NULL indicates that this is a root node + axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal' + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE + ) STRICT; + + CREATE TABLE panes( + pane_id INTEGER PRIMARY KEY, + workspace_id INTEGER NOT NULL, + active INTEGER NOT NULL, // Boolean + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE + ) STRICT; + + CREATE TABLE center_panes( + pane_id INTEGER PRIMARY KEY, + parent_group_id INTEGER, // NULL means that this is a root pane + position INTEGER, // NULL means that this is a root pane + FOREIGN KEY(pane_id) REFERENCES panes(pane_id) + ON DELETE CASCADE, + FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE + ) STRICT; + + CREATE TABLE items( + item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique + workspace_id INTEGER NOT NULL, + pane_id INTEGER NOT NULL, + kind TEXT NOT NULL, + position INTEGER NOT NULL, + active INTEGER NOT NULL, + FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY(pane_id) REFERENCES panes(pane_id) + ON DELETE CASCADE, + PRIMARY KEY(item_id, workspace_id) + ) STRICT; + ), + sql!( + ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean + ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever + )]; } impl WorkspaceDb { @@ -140,16 +140,16 @@ impl WorkspaceDb { worktree_roots: &[P], ) -> Option { let workspace_location: WorkspaceLocation = worktree_roots.into(); - + // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_x, window_y, window_width, window_height): ( + let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_region): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, bool, - f32, f32, f32, f32 + Option<(f32, f32, f32, f32)> ) = self .select_row_bound(sql! { SELECT @@ -170,7 +170,7 @@ impl WorkspaceDb { .context("No workspaces found") .warn_on_err() .flatten()?; - + Some(SerializedWorkspace { id: workspace_id, location: workspace_location.clone(), @@ -185,13 +185,13 @@ impl WorkspaceDb { dock_position, left_sidebar_open, fullscreen, - bounds: Some(RectF::new( - Vector2F::new(window_x, window_y), - Vector2F::new(window_width, window_height) + bounds: dbg!(window_region).map(|(x, y, width, height)| RectF::new( + Vector2F::new(x, y), + Vector2F::new(width, height) )) }) } - + /// Saves a workspace using the worktree roots. Will garbage collect any workspaces /// that used this workspace previously pub async fn save_workspace(&self, workspace: SerializedWorkspace) { @@ -203,30 +203,30 @@ impl WorkspaceDb { DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) .expect("Clearing old panes"); - + conn.exec_bound(sql!( DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ? ))?((&workspace.location, workspace.id.clone())) .context("clearing out old locations")?; - + // Upsert conn.exec_bound(sql!( - INSERT INTO workspaces( - workspace_id, - workspace_location, - left_sidebar_open, - dock_visible, - dock_anchor, - timestamp - ) - VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) - ON CONFLICT DO - UPDATE SET - workspace_location = ?2, - left_sidebar_open = ?3, - dock_visible = ?4, - dock_anchor = ?5, - timestamp = CURRENT_TIMESTAMP + INSERT INTO workspaces( + workspace_id, + workspace_location, + left_sidebar_open, + dock_visible, + dock_anchor, + timestamp + ) + VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) + ON CONFLICT DO + UPDATE SET + workspace_location = ?2, + left_sidebar_open = ?3, + dock_visible = ?4, + dock_anchor = ?5, + timestamp = CURRENT_TIMESTAMP ))?(( workspace.id, &workspace.location, @@ -234,35 +234,35 @@ impl WorkspaceDb { workspace.dock_position, )) .context("Updating workspace")?; - + // Save center pane group and dock pane Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) .context("save pane group in save workspace")?; - + let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true) .context("save pane in save workspace")?; - + // Complete workspace initialization conn.exec_bound(sql!( UPDATE workspaces SET dock_pane = ? - WHERE workspace_id = ? + WHERE workspace_id = ? ))?((dock_id, workspace.id)) .context("Finishing initialization with dock pane")?; - + Ok(()) }) .log_err(); }) .await; } - + query! { pub async fn next_id() -> Result { INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id } } - + query! { fn recent_workspaces() -> Result> { SELECT workspace_id, workspace_location @@ -271,14 +271,14 @@ impl WorkspaceDb { ORDER BY timestamp DESC } } - + query! { async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> { DELETE FROM workspaces WHERE workspace_id IS ? } } - + // Returns the recent locations which are still valid on disk and deletes ones which no longer // exist. pub async fn recent_workspaces_on_disk(&self) -> Result> { @@ -286,18 +286,18 @@ impl WorkspaceDb { let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { if location.paths().iter().all(|path| path.exists()) - && location.paths().iter().any(|path| path.is_dir()) + && location.paths().iter().any(|path| path.is_dir()) { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); } } - + futures::future::join_all(delete_tasks).await; Ok(result) } - + pub async fn last_workspace(&self) -> Result> { Ok(self .recent_workspaces_on_disk() @@ -306,7 +306,7 @@ impl WorkspaceDb { .next() .map(|(_, location)| location)) } - + fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { Ok(self .get_pane_group(workspace_id, None)? @@ -319,7 +319,7 @@ impl WorkspaceDb { }) })) } - + fn get_pane_group( &self, workspace_id: WorkspaceId, @@ -330,27 +330,27 @@ impl WorkspaceDb { self.select_bound::(sql!( SELECT group_id, axis, pane_id, active FROM (SELECT - group_id, - axis, - NULL as pane_id, - NULL as active, - position, - parent_group_id, - workspace_id - FROM pane_groups - UNION - SELECT - NULL, - NULL, - center_panes.pane_id, - panes.active as active, - position, - parent_group_id, - panes.workspace_id as workspace_id - FROM center_panes - JOIN panes ON center_panes.pane_id = panes.pane_id) - WHERE parent_group_id IS ? AND workspace_id = ? - ORDER BY position + group_id, + axis, + NULL as pane_id, + NULL as active, + position, + parent_group_id, + workspace_id + FROM pane_groups + UNION + SELECT + NULL, + NULL, + center_panes.pane_id, + panes.active as active, + position, + parent_group_id, + panes.workspace_id as workspace_id + FROM center_panes + JOIN panes ON center_panes.pane_id = panes.pane_id) + WHERE parent_group_id IS ? AND workspace_id = ? + ORDER BY position ))?((group_id, workspace_id))? .into_iter() .map(|(group_id, axis, pane_id, active)| { @@ -376,7 +376,7 @@ impl WorkspaceDb { }) .collect::>() } - + fn save_pane_group( conn: &Connection, workspace_id: WorkspaceId, @@ -386,18 +386,18 @@ impl WorkspaceDb { match pane_group { SerializedPaneGroup::Group { axis, children } => { let (parent_id, position) = unzip_option(parent); - + let group_id = conn.select_row_bound::<_, i64>(sql!( - INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) - VALUES (?, ?, ?, ?) - RETURNING group_id + INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) + VALUES (?, ?, ?, ?) + RETURNING group_id ))?((workspace_id, parent_id, position, *axis))? .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; - + for (position, group) in children.iter().enumerate() { Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))? } - + Ok(()) } SerializedPaneGroup::Pane(pane) => { @@ -406,7 +406,7 @@ impl WorkspaceDb { } } } - + fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { let (pane_id, active) = self.select_row_bound(sql!( SELECT pane_id, active @@ -414,13 +414,13 @@ impl WorkspaceDb { WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) ))?(workspace_id)? .context("No dock pane for workspace")?; - + Ok(SerializedPane::new( self.get_items(pane_id).context("Reading items")?, active, )) } - + fn save_pane( conn: &Connection, workspace_id: WorkspaceId, @@ -434,7 +434,7 @@ impl WorkspaceDb { RETURNING pane_id ))?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - + if !dock { let (parent_id, order) = unzip_option(parent); conn.exec_bound(sql!( @@ -442,20 +442,20 @@ impl WorkspaceDb { VALUES (?, ?, ?) ))?((pane_id, parent_id, order))?; } - + Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; - + Ok(pane_id) } - + fn get_items(&self, pane_id: PaneId) -> Result> { Ok(self.select_bound(sql!( SELECT kind, item_id, active FROM items WHERE pane_id = ? - ORDER BY position + ORDER BY position ))?(pane_id)?) } - + fn save_items( conn: &Connection, workspace_id: WorkspaceId, @@ -468,10 +468,10 @@ impl WorkspaceDb { for (position, item) in items.iter().enumerate() { insert((workspace_id, pane_id, position, item))?; } - + Ok(()) } - + query! { pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { UPDATE workspaces @@ -479,9 +479,9 @@ impl WorkspaceDb { WHERE workspace_id = ? } } - + query! { - pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, x: f32, y: f32, width: f32, height: f32) -> Result<()> { + pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, bounds: Option<(f32, f32, f32, f32)>) -> Result<()> { UPDATE workspaces SET fullscreen = ?2, window_x = ?3, @@ -495,20 +495,20 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - + use std::sync::Arc; - + use db::open_test_db; use settings::DockAnchor; - + use super::*; - + #[gpui::test] async fn test_next_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_next_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -517,14 +517,14 @@ mod tests { text TEXT, workspace_id INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE + ON DELETE CASCADE ) STRICT; )], ) .unwrap(); }) .await; - + let id = db.next_id().await.unwrap(); // Assert the empty row got inserted assert_eq!( @@ -535,28 +535,28 @@ mod tests { .unwrap()(id) .unwrap() ); - + db.write(move |conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", id)) - .unwrap() + .unwrap() }) .await; - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_workspace_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -566,13 +566,13 @@ mod tests { workspace_id INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE + ON DELETE CASCADE ) STRICT;)], ) }) .await .unwrap(); - + let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -583,7 +583,7 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -594,57 +594,57 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", 1)) - .unwrap(); + .unwrap(); }) .await; - + db.save_workspace(workspace_2.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-2", 2)) - .unwrap(); + .unwrap(); }) .await; - + workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; - + workspace_2.dock_pane.children.push(SerializedItem { kind: Arc::from("Test"), item_id: 10, active: true, }); db.save_workspace(workspace_2).await; - + let test_text_2 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(2) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_2, "test-text-2"); - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_full_workspace_serialization() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - + let dock_pane = crate::persistence::model::SerializedPane { children: vec![ SerializedItem::new("Terminal", 1, false), @@ -654,7 +654,7 @@ mod tests { ], active: false, }; - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -691,7 +691,7 @@ mod tests { )), ], }; - + let workspace = SerializedWorkspace { id: 5, location: (["/tmp", "/tmp2"]).into(), @@ -702,26 +702,26 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace.clone()).await; let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); - + assert_eq!(workspace, round_trip_workspace.unwrap()); - + // Test guaranteed duplicate IDs db.save_workspace(workspace.clone()).await; db.save_workspace(workspace.clone()).await; - + let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); assert_eq!(workspace, round_trip_workspace.unwrap()); } - + #[gpui::test] async fn test_workspace_assignment() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_basic_functionality").await); - + let workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -732,7 +732,7 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -743,10 +743,10 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_2.clone()).await; - + // Test that paths are treated as a set assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), @@ -756,20 +756,20 @@ mod tests { db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), workspace_1 ); - + // Make sure that other keys work assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); - + // Test 'mutate' case of updating a pre-existing id workspace_2.location = (["/tmp", "/tmp2"]).into(); - + db.save_workspace(workspace_2.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_2 ); - + // Test other mechanism for mutating let mut workspace_3 = SerializedWorkspace { id: 3, @@ -781,13 +781,13 @@ mod tests { fullscreen: false, bounds: Default::default(), }; - + db.save_workspace(workspace_3.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_3 ); - + // Make sure that updating paths differently also works workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); db.save_workspace(workspace_3.clone()).await; @@ -798,11 +798,11 @@ mod tests { workspace_3 ); } - + use crate::dock::DockPosition; use crate::persistence::model::SerializedWorkspace; use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; - + fn default_workspace>( workspace_id: &[P], dock_pane: SerializedPane, @@ -819,13 +819,13 @@ mod tests { bounds: Default::default(), } } - + #[gpui::test] async fn test_basic_dock_pane() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("basic_dock_pane").await); - + let dock_pane = crate::persistence::model::SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -835,22 +835,22 @@ mod tests { ], false, ); - + let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.dock_pane, new_workspace.dock_pane); } - + #[gpui::test] async fn test_simple_split() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("simple_split").await); - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -887,22 +887,22 @@ mod tests { )), ], }; - + let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } - + #[gpui::test] async fn test_cleanup_panes() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - + let center_pane = SerializedPaneGroup::Group { axis: gpui::Axis::Horizontal, children: vec![ @@ -934,13 +934,13 @@ mod tests { )), ], }; - + let id = &["/tmp"]; - + let mut workspace = default_workspace(id, Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + workspace.center_group = SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ @@ -960,11 +960,11 @@ mod tests { )), ], }; - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(id).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index 4bc8881bbae6e1bf1067ab5163ca54872c03b6cc..f6236ced79f6e35e89baf3b384af01436394aebf 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -11,7 +11,7 @@ use gpui::{ }; use db::sqlez::{ - bindable::{Bind, Column, StaticRowComponent}, + bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use project::Project; @@ -42,7 +42,7 @@ impl, T: IntoIterator> From for WorkspaceLocation { } } -impl StaticRowComponent for WorkspaceLocation {} +impl StaticColumnCount for WorkspaceLocation {} impl Bind for &WorkspaceLocation { fn bind(&self, statement: &Statement, start_index: i32) -> Result { bincode::serialize(&self.0) @@ -254,8 +254,8 @@ impl Default for SerializedItem { } } -impl StaticRowComponent for SerializedItem { - fn static_column_count() -> usize { +impl StaticColumnCount for SerializedItem { + fn column_count() -> usize { 3 } } @@ -283,8 +283,8 @@ impl Column for SerializedItem { } } -impl StaticRowComponent for DockPosition { - fn static_column_count() -> usize { +impl StaticColumnCount for DockPosition { + fn column_count() -> usize { 2 } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index addbb9b017f681b7ad51b77fa9fb682f366088f6..4c742cd7faf14f010e340f3e8aa0d8cacb99a03d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -683,9 +683,9 @@ impl Workspace { // Use the serialized workspace to construct the new window let (_, workspace) = cx.add_window( - (app_state.build_window_options)( - serialized_workspace.as_ref().map(|sw| sw.bounds()), - ), + (app_state.build_window_options)(dbg!(serialized_workspace + .as_ref() + .map(|sw| sw.bounds()))), |cx| { let mut workspace = Workspace::new( serialized_workspace, @@ -697,15 +697,18 @@ impl Workspace { (app_state.initialize_workspace)(&mut workspace, &app_state, cx); cx.observe_window_bounds(move |_, bounds, cx| { let fullscreen = cx.window_is_fullscreen(cx.window_id()); - cx.background() - .spawn(DB.set_bounds( - workspace_id, - fullscreen, - bounds.min_x(), - bounds.min_y(), - bounds.width(), - bounds.height(), + let bounds = if let WindowBounds::Fixed(region) = bounds { + Some(( + region.min_x(), + region.min_y(), + region.width(), + region.height(), )) + } else { + None + }; + cx.background() + .spawn(DB.set_bounds(workspace_id, fullscreen, bounds)) .detach_and_log_err(cx); }) .detach(); From 5eac797a93a0d684c0d36d6b4904c0d21f7f9578 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 25 Jan 2023 11:30:03 -0800 Subject: [PATCH 3/5] mostly working now --- Cargo.lock | 11 + crates/collab/src/tests.rs | 2 +- crates/collab_ui/src/collab_ui.rs | 25 +- crates/gpui/Cargo.toml | 3 +- crates/gpui/src/app.rs | 43 +-- crates/gpui/src/platform.rs | 96 ++++++- crates/gpui/src/platform/mac/platform.rs | 12 +- crates/gpui/src/platform/mac/screen.rs | 67 ++++- crates/gpui/src/platform/mac/status_item.rs | 96 ++++--- crates/gpui/src/platform/mac/window.rs | 111 ++++--- crates/gpui/src/platform/test.rs | 167 ++++++----- crates/sqlez/Cargo.toml | 1 + crates/sqlez/src/bindable.rs | 41 ++- crates/workspace/Cargo.toml | 1 + crates/workspace/src/dock.rs | 2 +- crates/workspace/src/persistence.rs | 304 ++++++++++---------- crates/workspace/src/persistence/model.rs | 21 +- crates/workspace/src/workspace.rs | 35 +-- crates/zed/Cargo.toml | 1 + crates/zed/src/zed.rs | 16 +- 20 files changed, 638 insertions(+), 417 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f858a61aaa7a7415fa9d33b0315a3bf974a00212..927010c4d057b8bf318567b6744d57a1c49fc9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,6 +1275,7 @@ source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2f dependencies = [ "core-foundation-sys", "libc", + "uuid 0.5.1", ] [[package]] @@ -2591,6 +2592,7 @@ dependencies = [ "tiny-skia", "usvg", "util", + "uuid 1.2.2", "waker-fn", ] @@ -6014,6 +6016,7 @@ dependencies = [ "parking_lot 0.11.2", "smol", "thread_local", + "uuid 1.2.2", ] [[package]] @@ -7324,6 +7327,12 @@ dependencies = [ "tempdir", ] +[[package]] +name = "uuid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" + [[package]] name = "uuid" version = "0.8.2" @@ -8169,6 +8178,7 @@ dependencies = [ "smallvec", "theme", "util", + "uuid 1.2.2", ] [[package]] @@ -8312,6 +8322,7 @@ dependencies = [ "url", "urlencoding", "util", + "uuid 1.2.2", "vim", "workspace", ] diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index fb018a901759afeab4e0b200ac12c1b4173cba7b..120c577e0f215c42068003ed39b3acc9ebbd07ab 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -196,7 +196,7 @@ impl TestServer { languages: Arc::new(LanguageRegistry::new(Task::ready(()))), themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), - build_window_options: |_| Default::default(), + build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), dock_default_item_factory: |_, _| unimplemented!(), }); diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 2d6b9489d809ca975ed80ceabda3afad27f57a33..38a47e87dcb6b5e2f420297e8d279a80f5f5688c 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -54,17 +54,20 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }) .await?; - let (_, workspace) = cx.add_window((app_state.build_window_options)(None), |cx| { - let mut workspace = Workspace::new( - Default::default(), - 0, - project, - app_state.dock_default_item_factory, - cx, - ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - workspace - }); + let (_, workspace) = cx.add_window( + (app_state.build_window_options)(None, None, cx.platform().as_ref()), + |cx| { + let mut workspace = Workspace::new( + Default::default(), + 0, + project, + app_state.dock_default_item_factory, + cx, + ); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + workspace + }, + ); workspace }; diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index e1b6e11b46896787529a283518982c6811e2c6b5..7be254be4da2b0c0f47b9f03f4ae437df1407261 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -47,6 +47,7 @@ smol = "1.2" time = { version = "0.3", features = ["serde", "serde-well-known"] } tiny-skia = "0.5" usvg = "0.14" +uuid = { version = "1.1.2", features = ["v4"] } waker-fn = "1.1.0" [build-dependencies] @@ -66,7 +67,7 @@ media = { path = "../media" } anyhow = "1" block = "0.1" cocoa = "0.24" -core-foundation = "0.9.3" +core-foundation = { version = "0.9.3", features = ["with-uuid"] } core-graphics = "0.22.3" core-text = "19.2" font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index df62580c4d0a66bffc3899aa3c4a0f571c0fda53..c51de8c0c8333b8de84426cdbd2fe8d5ab7e11a7 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -32,6 +32,7 @@ use collections::{hash_map::Entry, HashMap, HashSet, VecDeque}; use platform::Event; #[cfg(any(test, feature = "test-support"))] pub use test_app_context::{ContextHandle, TestAppContext}; +use uuid::Uuid; use crate::{ elements::ElementBox, @@ -595,7 +596,7 @@ type ReleaseObservationCallback = Box; type WindowActivationCallback = Box bool>; type WindowFullscreenCallback = Box bool>; -type WindowBoundsCallback = Box bool>; +type WindowBoundsCallback = Box bool>; type KeystrokeCallback = Box< dyn FnMut(&Keystroke, &MatchResult, Option<&Box>, &mut MutableAppContext) -> bool, >; @@ -909,10 +910,17 @@ impl MutableAppContext { .map_or(false, |window| window.is_fullscreen) } - pub fn window_bounds(&self, window_id: usize) -> RectF { + pub fn window_bounds(&self, window_id: usize) -> WindowBounds { self.presenters_and_platform_windows[&window_id].1.bounds() } + pub fn window_display_uuid(&self, window_id: usize) -> Uuid { + self.presenters_and_platform_windows[&window_id] + .1 + .screen() + .display_uuid() + } + pub fn render_view(&mut self, params: RenderParams) -> Result { let window_id = params.window_id; let view_id = params.view_id; @@ -1246,7 +1254,7 @@ impl MutableAppContext { fn observe_window_bounds(&mut self, window_id: usize, callback: F) -> Subscription where - F: 'static + FnMut(WindowBounds, &mut MutableAppContext) -> bool, + F: 'static + FnMut(WindowBounds, Uuid, &mut MutableAppContext) -> bool, { let subscription_id = post_inc(&mut self.next_subscription_id); self.pending_effects @@ -2382,14 +2390,12 @@ impl MutableAppContext { callback(is_fullscreen, this) }); - let bounds = if this.window_is_fullscreen(window_id) { - WindowBounds::Fullscreen - } else { - WindowBounds::Fixed(this.window_bounds(window_id)) - }; - + let bounds = this.window_bounds(window_id); + let uuid = this.window_display_uuid(window_id); let mut bounds_observations = this.window_bounds_observations.clone(); - bounds_observations.emit(window_id, this, |callback, this| callback(bounds, this)); + bounds_observations.emit(window_id, this, |callback, this| { + callback(bounds, uuid, this) + }); Some(()) }); @@ -2568,15 +2574,12 @@ impl MutableAppContext { } fn handle_window_moved(&mut self, window_id: usize) { - let bounds = if self.window_is_fullscreen(window_id) { - WindowBounds::Fullscreen - } else { - WindowBounds::Fixed(self.window_bounds(window_id)) - }; + let bounds = self.window_bounds(window_id); + let display = self.window_display_uuid(window_id); self.window_bounds_observations .clone() .emit(window_id, self, move |callback, this| { - callback(bounds, this); + callback(bounds, display, this); true }); } @@ -3717,7 +3720,7 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.toggle_window_full_screen(self.window_id) } - pub fn window_bounds(&self) -> RectF { + pub fn window_bounds(&self) -> WindowBounds { self.app.window_bounds(self.window_id) } @@ -4023,14 +4026,14 @@ impl<'a, T: View> ViewContext<'a, T> { pub fn observe_window_bounds(&mut self, mut callback: F) -> Subscription where - F: 'static + FnMut(&mut T, WindowBounds, &mut ViewContext), + F: 'static + FnMut(&mut T, WindowBounds, Uuid, &mut ViewContext), { let observer = self.weak_handle(); self.app - .observe_window_bounds(self.window_id(), move |bounds, cx| { + .observe_window_bounds(self.window_id(), move |bounds, display, cx| { if let Some(observer) = observer.upgrade(cx) { observer.update(cx, |observer, cx| { - callback(observer, bounds, cx); + callback(observer, bounds, display, cx); }); true } else { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 1db1fe62b0926ab918a24aa95dac17584d92c382..e91c1e87aa0e6b0fe361665982af56073d9fb600 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -18,11 +18,15 @@ use crate::{ text_layout::{LineLayout, RunStyle}, Action, ClipboardItem, Menu, Scene, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use async_task::Runnable; pub use event::*; use postage::oneshot; use serde::Deserialize; +use sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; use std::{ any::Any, fmt::{self, Debug, Display}, @@ -33,6 +37,7 @@ use std::{ sync::Arc, }; use time::UtcOffset; +use uuid::Uuid; pub trait Platform: Send + Sync { fn dispatcher(&self) -> Arc; @@ -44,6 +49,7 @@ pub trait Platform: Send + Sync { fn unhide_other_apps(&self); fn quit(&self); + fn screen_by_id(&self, id: Uuid) -> Option>; fn screens(&self) -> Vec>; fn open_window( @@ -118,17 +124,18 @@ pub trait InputHandler { pub trait Screen: Debug { fn as_any(&self) -> &dyn Any; fn size(&self) -> Vector2F; + fn display_uuid(&self) -> Uuid; } pub trait Window { + fn bounds(&self) -> WindowBounds; + fn content_size(&self) -> Vector2F; + fn scale_factor(&self) -> f32; + fn titlebar_height(&self) -> f32; + fn appearance(&self) -> Appearance; + fn screen(&self) -> Rc; + fn as_any_mut(&mut self) -> &mut dyn Any; - fn on_event(&mut self, callback: Box bool>); - fn on_active_status_change(&mut self, callback: Box); - fn on_resize(&mut self, callback: Box); - fn on_fullscreen(&mut self, callback: Box); - fn on_moved(&mut self, callback: Box); - fn on_should_close(&mut self, callback: Box bool>); - fn on_close(&mut self, callback: Box); fn set_input_handler(&mut self, input_handler: Box); fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); @@ -137,14 +144,16 @@ pub trait Window { fn show_character_palette(&self); fn minimize(&self); fn zoom(&self); + fn present_scene(&mut self, scene: Scene); fn toggle_full_screen(&self); - fn bounds(&self) -> RectF; - fn content_size(&self) -> Vector2F; - fn scale_factor(&self) -> f32; - fn titlebar_height(&self) -> f32; - fn present_scene(&mut self, scene: Scene); - fn appearance(&self) -> Appearance; + fn on_event(&mut self, callback: Box bool>); + fn on_active_status_change(&mut self, callback: Box); + fn on_resize(&mut self, callback: Box); + fn on_fullscreen(&mut self, callback: Box); + fn on_moved(&mut self, callback: Box); + fn on_should_close(&mut self, callback: Box bool>); + fn on_close(&mut self, callback: Box); fn on_appearance_changed(&mut self, callback: Box); fn is_topmost_for_position(&self, position: Vector2F) -> bool; } @@ -187,13 +196,70 @@ pub enum WindowKind { PopUp, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum WindowBounds { Fullscreen, Maximized, Fixed(RectF), } +impl StaticColumnCount for WindowBounds { + fn column_count() -> usize { + 5 + } +} + +impl Bind for WindowBounds { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let (region, next_index) = match self { + WindowBounds::Fullscreen => { + let next_index = statement.bind("Fullscreen", start_index)?; + (None, next_index) + } + WindowBounds::Maximized => { + let next_index = statement.bind("Maximized", start_index)?; + (None, next_index) + } + WindowBounds::Fixed(region) => { + let next_index = statement.bind("Fixed", start_index)?; + (Some(*region), next_index) + } + }; + + statement.bind( + region.map(|region| { + ( + region.min_x(), + region.min_y(), + region.width(), + region.height(), + ) + }), + next_index, + ) + } +} + +impl Column for WindowBounds { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (window_state, next_index) = String::column(statement, start_index)?; + let bounds = match window_state.as_str() { + "Fullscreen" => WindowBounds::Fullscreen, + "Maximized" => WindowBounds::Maximized, + "Fixed" => { + let ((x, y, width, height), _) = Column::column(statement, next_index)?; + WindowBounds::Fixed(RectF::new( + Vector2F::new(x, y), + Vector2F::new(width, height), + )) + } + _ => bail!("Window State did not have a valid string"), + }; + + Ok((bounds, next_index + 4)) + } +} + pub struct PathPromptOptions { pub files: bool, pub directories: bool, diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index dbb1a01f31136c20faa429271d840af0e66d6486..5d132275854c8b92e317b8226cf4d869dd92f422 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -440,6 +440,10 @@ impl platform::Platform for MacPlatform { self.dispatcher.clone() } + fn fonts(&self) -> Arc { + self.fonts.clone() + } + fn activate(&self, ignoring_other_apps: bool) { unsafe { let app = NSApplication::sharedApplication(nil); @@ -488,6 +492,10 @@ impl platform::Platform for MacPlatform { } } + fn screen_by_id(&self, id: uuid::Uuid) -> Option> { + Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) + } + fn screens(&self) -> Vec> { Screen::all() .into_iter() @@ -512,10 +520,6 @@ impl platform::Platform for MacPlatform { Box::new(StatusItem::add(self.fonts())) } - fn fonts(&self) -> Arc { - self.fonts.clone() - } - fn write_to_clipboard(&self, item: ClipboardItem) { unsafe { self.pasteboard.clearContents(); diff --git a/crates/gpui/src/platform/mac/screen.rs b/crates/gpui/src/platform/mac/screen.rs index fdc7fbb50581a1d13f465e23ed1d7c50187fb590..a54ffb3f90f9e322a4053ee777a5e4172e3591fe 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -1,4 +1,4 @@ -use std::any::Any; +use std::{any::Any, ffi::c_void}; use crate::{ geometry::vector::{vec2f, Vector2F}, @@ -7,8 +7,19 @@ use crate::{ use cocoa::{ appkit::NSScreen, base::{id, nil}, - foundation::NSArray, + foundation::{NSArray, NSDictionary, NSString}, }; +use core_foundation::{ + number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef}, + uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}, +}; +use core_graphics::display::CGDirectDisplayID; +use uuid::Uuid; + +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; +} #[derive(Debug)] pub struct Screen { @@ -16,11 +27,23 @@ pub struct Screen { } impl Screen { + pub fn find_by_id(uuid: Uuid) -> Option { + unsafe { + let native_screens = NSScreen::screens(nil); + (0..NSArray::count(native_screens)) + .into_iter() + .map(|ix| Screen { + native_screen: native_screens.objectAtIndex(ix), + }) + .find(|screen| platform::Screen::display_uuid(screen) == uuid) + } + } + pub fn all() -> Vec { let mut screens = Vec::new(); unsafe { let native_screens = NSScreen::screens(nil); - for ix in 0..native_screens.count() { + for ix in 0..NSArray::count(native_screens) { screens.push(Screen { native_screen: native_screens.objectAtIndex(ix), }); @@ -41,4 +64,42 @@ impl platform::Screen for Screen { vec2f(frame.size.width as f32, frame.size.height as f32) } } + + fn display_uuid(&self) -> uuid::Uuid { + unsafe { + // Screen ids are not stable. Further, the default device id is also unstable across restarts. + // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use. + // This approach is similar to that which winit takes + // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99 + let device_description = self.native_screen.deviceDescription(); + let key = NSString::alloc(nil).init_str("NSScreenNumber"); + let device_id_obj = device_description.objectForKey_(key); + let mut device_id: u32 = 0; + CFNumberGetValue( + device_id_obj as CFNumberRef, + kCFNumberIntType, + (&mut device_id) as *mut _ as *mut c_void, + ); + let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID); + let bytes = CFUUIDGetUUIDBytes(cfuuid); + Uuid::from_bytes([ + bytes.byte0, + bytes.byte1, + bytes.byte2, + bytes.byte3, + bytes.byte4, + bytes.byte5, + bytes.byte6, + bytes.byte7, + bytes.byte8, + bytes.byte9, + bytes.byte10, + bytes.byte11, + bytes.byte12, + bytes.byte13, + bytes.byte14, + bytes.byte15, + ]) + } + } } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index 8ac9dbea71de4ccd724d5a718c42a4e11876aad5..812027d35c55a38101539709554a15e9b2ef06f6 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -7,7 +7,7 @@ use crate::{ self, mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer}, }, - Event, FontSystem, Scene, + Event, FontSystem, Scene, WindowBounds, }; use cocoa::{ appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow}, @@ -32,6 +32,8 @@ use std::{ sync::Arc, }; +use super::screen::Screen; + static mut VIEW_CLASS: *const Class = ptr::null(); const STATE_IVAR: &str = "state"; @@ -167,29 +169,41 @@ impl StatusItem { } impl platform::Window for StatusItem { - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self + fn bounds(&self) -> WindowBounds { + self.0.borrow().bounds() } - fn on_event(&mut self, callback: Box bool>) { - self.0.borrow_mut().event_callback = Some(callback); + fn content_size(&self) -> Vector2F { + self.0.borrow().content_size() } - fn on_appearance_changed(&mut self, callback: Box) { - self.0.borrow_mut().appearance_changed_callback = Some(callback); + fn scale_factor(&self) -> f32 { + self.0.borrow().scale_factor() } - fn on_active_status_change(&mut self, _: Box) {} - - fn on_resize(&mut self, _: Box) {} - - fn on_moved(&mut self, _: Box) {} + fn titlebar_height(&self) -> f32 { + 0. + } - fn on_fullscreen(&mut self, _: Box) {} + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = + msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; + crate::Appearance::from_native(appearance) + } + } - fn on_should_close(&mut self, _: Box bool>) {} + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.borrow().native_window().screen(), + }) + } + } - fn on_close(&mut self, _: Box) {} + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } fn set_input_handler(&mut self, _: Box) {} @@ -226,39 +240,35 @@ impl platform::Window for StatusItem { unimplemented!() } + fn present_scene(&mut self, scene: Scene) { + self.0.borrow_mut().scene = Some(scene); + unsafe { + let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES]; + } + } + fn toggle_full_screen(&self) { unimplemented!() } - fn bounds(&self) -> RectF { - self.0.borrow().bounds() + fn on_event(&mut self, callback: Box bool>) { + self.0.borrow_mut().event_callback = Some(callback); } - fn content_size(&self) -> Vector2F { - self.0.borrow().content_size() - } + fn on_active_status_change(&mut self, _: Box) {} - fn scale_factor(&self) -> f32 { - self.0.borrow().scale_factor() - } + fn on_resize(&mut self, _: Box) {} - fn titlebar_height(&self) -> f32 { - 0. - } + fn on_fullscreen(&mut self, _: Box) {} - fn present_scene(&mut self, scene: Scene) { - self.0.borrow_mut().scene = Some(scene); - unsafe { - let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES]; - } - } + fn on_moved(&mut self, _: Box) {} - fn appearance(&self) -> crate::Appearance { - unsafe { - let appearance: id = - msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; - crate::Appearance::from_native(appearance) - } + fn on_should_close(&mut self, _: Box bool>) {} + + fn on_close(&mut self, _: Box) {} + + fn on_appearance_changed(&mut self, callback: Box) { + self.0.borrow_mut().appearance_changed_callback = Some(callback); } fn is_topmost_for_position(&self, _: Vector2F) -> bool { @@ -267,9 +277,9 @@ impl platform::Window for StatusItem { } impl StatusItemState { - fn bounds(&self) -> RectF { + fn bounds(&self) -> WindowBounds { unsafe { - let window: id = msg_send![self.native_item.button(), window]; + let window: id = self.native_window(); let screen_frame = window.screen().visibleFrame(); let window_frame = NSWindow::frame(window); let origin = vec2f( @@ -281,7 +291,7 @@ impl StatusItemState { window_frame.size.width as f32, window_frame.size.height as f32, ); - RectF::new(origin, size) + WindowBounds::Fixed(RectF::new(origin, size)) } } @@ -299,6 +309,10 @@ impl StatusItemState { NSScreen::backingScaleFactor(window.screen()) as f32 } } + + pub fn native_window(&self) -> id { + unsafe { msg_send![self.native_item.button(), window] } + } } extern "C" fn dealloc_view(this: &Object, _: Sel) { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index c4cd7d0e27d1b8688dbd4231e47553c4ad252ebd..e981cc6131b54c9e561bc64db3fc829143c75afe 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -419,19 +419,17 @@ impl Window { WindowBounds::Fixed(top_left_bounds) => { let frame = screen.visibleFrame(); let bottom_left_bounds = RectF::new( - vec2f( + dbg!(vec2f( top_left_bounds.origin_x(), frame.size.height as f32 - top_left_bounds.origin_y() - top_left_bounds.height(), - ), - top_left_bounds.size(), + )), + dbg!(top_left_bounds.size()), ) .to_ns_rect(); - native_window.setFrame_display_( - native_window.convertRectToScreen_(bottom_left_bounds), - YES, - ); + let screen_rect = native_window.convertRectToScreen_(bottom_left_bounds); + native_window.setFrame_display_(screen_rect, YES); } } @@ -585,36 +583,39 @@ impl Drop for Window { } impl platform::Window for Window { - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn on_event(&mut self, callback: Box bool>) { - self.0.as_ref().borrow_mut().event_callback = Some(callback); + fn bounds(&self) -> WindowBounds { + self.0.as_ref().borrow().bounds() } - fn on_resize(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().resize_callback = Some(callback); + fn content_size(&self) -> Vector2F { + self.0.as_ref().borrow().content_size() } - fn on_moved(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().moved_callback = Some(callback); + fn scale_factor(&self) -> f32 { + self.0.as_ref().borrow().scale_factor() } - fn on_fullscreen(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); + fn titlebar_height(&self) -> f32 { + self.0.as_ref().borrow().titlebar_height() } - fn on_should_close(&mut self, callback: Box bool>) { - self.0.as_ref().borrow_mut().should_close_callback = Some(callback); + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance]; + crate::Appearance::from_native(appearance) + } } - fn on_close(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().close_callback = Some(callback); + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.as_ref().borrow().native_window.screen(), + }) + } } - fn on_active_status_change(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().activate_callback = Some(callback); + fn as_any_mut(&mut self) -> &mut dyn Any { + self } fn set_input_handler(&mut self, input_handler: Box) { @@ -726,6 +727,10 @@ impl platform::Window for Window { .detach(); } + fn present_scene(&mut self, scene: Scene) { + self.0.as_ref().borrow_mut().present_scene(scene); + } + fn toggle_full_screen(&self) { let this = self.0.borrow(); let window = this.native_window; @@ -738,31 +743,32 @@ impl platform::Window for Window { .detach(); } - fn bounds(&self) -> RectF { - self.0.as_ref().borrow().bounds() + fn on_event(&mut self, callback: Box bool>) { + self.0.as_ref().borrow_mut().event_callback = Some(callback); } - fn content_size(&self) -> Vector2F { - self.0.as_ref().borrow().content_size() + fn on_active_status_change(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().activate_callback = Some(callback); } - fn scale_factor(&self) -> f32 { - self.0.as_ref().borrow().scale_factor() + fn on_resize(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().resize_callback = Some(callback); } - fn present_scene(&mut self, scene: Scene) { - self.0.as_ref().borrow_mut().present_scene(scene); + fn on_fullscreen(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); } - fn titlebar_height(&self) -> f32 { - self.0.as_ref().borrow().titlebar_height() + fn on_moved(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().moved_callback = Some(callback); } - fn appearance(&self) -> crate::Appearance { - unsafe { - let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance]; - crate::Appearance::from_native(appearance) - } + fn on_should_close(&mut self, callback: Box bool>) { + self.0.as_ref().borrow_mut().should_close_callback = Some(callback); + } + + fn on_close(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().close_callback = Some(callback); } fn on_appearance_changed(&mut self, callback: Box) { @@ -846,20 +852,39 @@ impl WindowState { } } - fn bounds(&self) -> RectF { + fn is_fullscreen(&self) -> bool { unsafe { + let style_mask = self.native_window.styleMask(); + style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask) + } + } + + fn bounds(&self) -> WindowBounds { + unsafe { + if self.is_fullscreen() { + return WindowBounds::Fullscreen; + } + let screen_frame = self.native_window.screen().visibleFrame(); let window_frame = NSWindow::frame(self.native_window); let origin = vec2f( window_frame.origin.x as f32, - (window_frame.origin.y - screen_frame.size.height - window_frame.size.height) + (screen_frame.size.height - window_frame.origin.y - window_frame.size.height) as f32, ); let size = vec2f( window_frame.size.width as f32, window_frame.size.height as f32, ); - RectF::new(origin, size) + + if origin.is_zero() + && size.x() == screen_frame.size.width as f32 + && size.y() == screen_frame.size.height as f32 + { + WindowBounds::Maximized + } else { + WindowBounds::Fixed(RectF::new(origin, size)) + } } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index f3e1ce405577811e7d278359e99586427c285f47..2c8a9409694c96d58ff49b2d902ed652710b04d6 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -20,11 +20,20 @@ use std::{ }; use time::UtcOffset; -pub struct Platform { - dispatcher: Arc, - fonts: Arc, - current_clipboard_item: Mutex>, - cursor: Mutex, +struct Dispatcher; + +impl super::Dispatcher for Dispatcher { + fn is_main_thread(&self) -> bool { + true + } + + fn run_on_main_thread(&self, task: async_task::Runnable) { + task.run(); + } +} + +pub fn foreground_platform() -> ForegroundPlatform { + ForegroundPlatform::default() } #[derive(Default)] @@ -32,24 +41,6 @@ pub struct ForegroundPlatform { last_prompt_for_new_path_args: RefCell>)>>, } -struct Dispatcher; - -pub struct Window { - pub(crate) size: Vector2F, - scale_factor: f32, - current_scene: Option, - event_handlers: Vec bool>>, - pub(crate) resize_handlers: Vec>, - pub(crate) moved_handlers: Vec>, - close_handlers: Vec>, - fullscreen_handlers: Vec>, - pub(crate) active_status_change_handlers: Vec>, - pub(crate) should_close_handler: Option bool>>, - pub(crate) title: Option, - pub(crate) edited: bool, - pub(crate) pending_prompts: RefCell>>, -} - #[cfg(any(test, feature = "test-support"))] impl ForegroundPlatform { pub(crate) fn simulate_new_path_selection( @@ -103,6 +94,17 @@ impl super::ForegroundPlatform for ForegroundPlatform { } } +pub fn platform() -> Platform { + Platform::new() +} + +pub struct Platform { + dispatcher: Arc, + fonts: Arc, + current_clipboard_item: Mutex>, + cursor: Mutex, +} + impl Platform { fn new() -> Self { Self { @@ -133,6 +135,10 @@ impl super::Platform for Platform { fn quit(&self) {} + fn screen_by_id(&self, _id: uuid::Uuid) -> Option> { + None + } + fn screens(&self) -> Vec> { Default::default() } @@ -220,6 +226,39 @@ impl super::Platform for Platform { } } +#[derive(Debug)] +pub struct Screen; + +impl super::Screen for Screen { + fn as_any(&self) -> &dyn Any { + self + } + + fn size(&self) -> Vector2F { + Vector2F::new(1920., 1080.) + } + + fn display_uuid(&self) -> uuid::Uuid { + uuid::Uuid::new_v4() + } +} + +pub struct Window { + pub(crate) size: Vector2F, + scale_factor: f32, + current_scene: Option, + event_handlers: Vec bool>>, + pub(crate) resize_handlers: Vec>, + pub(crate) moved_handlers: Vec>, + close_handlers: Vec>, + fullscreen_handlers: Vec>, + pub(crate) active_status_change_handlers: Vec>, + pub(crate) should_close_handler: Option bool>>, + pub(crate) title: Option, + pub(crate) edited: bool, + pub(crate) pending_prompts: RefCell>>, +} + impl Window { fn new(size: Vector2F) -> Self { Self { @@ -244,43 +283,33 @@ impl Window { } } -impl super::Dispatcher for Dispatcher { - fn is_main_thread(&self) -> bool { - true - } - - fn run_on_main_thread(&self, task: async_task::Runnable) { - task.run(); - } -} - impl super::Window for Window { - fn as_any_mut(&mut self) -> &mut dyn Any { - self + fn bounds(&self) -> WindowBounds { + WindowBounds::Fixed(RectF::new(Vector2F::zero(), self.size)) } - fn on_event(&mut self, callback: Box bool>) { - self.event_handlers.push(callback); + fn content_size(&self) -> Vector2F { + self.size } - fn on_active_status_change(&mut self, callback: Box) { - self.active_status_change_handlers.push(callback); + fn scale_factor(&self) -> f32 { + self.scale_factor } - fn on_fullscreen(&mut self, callback: Box) { - self.fullscreen_handlers.push(callback) + fn titlebar_height(&self) -> f32 { + 24. } - fn on_resize(&mut self, callback: Box) { - self.resize_handlers.push(callback); + fn appearance(&self) -> crate::Appearance { + crate::Appearance::Light } - fn on_moved(&mut self, callback: Box) { - self.moved_handlers.push(callback); + fn screen(&self) -> Rc { + Rc::new(Screen) } - fn on_close(&mut self, callback: Box) { - self.close_handlers.push(callback); + fn as_any_mut(&mut self) -> &mut dyn Any { + self } fn set_input_handler(&mut self, _: Box) {} @@ -301,40 +330,44 @@ impl super::Window for Window { self.edited = edited; } - fn on_should_close(&mut self, callback: Box bool>) { - self.should_close_handler = Some(callback); - } - fn show_character_palette(&self) {} fn minimize(&self) {} fn zoom(&self) {} + fn present_scene(&mut self, scene: crate::Scene) { + self.current_scene = Some(scene); + } + fn toggle_full_screen(&self) {} - fn bounds(&self) -> RectF { - RectF::new(Default::default(), self.size) + fn on_event(&mut self, callback: Box bool>) { + self.event_handlers.push(callback); } - fn content_size(&self) -> Vector2F { - self.size + fn on_active_status_change(&mut self, callback: Box) { + self.active_status_change_handlers.push(callback); } - fn scale_factor(&self) -> f32 { - self.scale_factor + fn on_resize(&mut self, callback: Box) { + self.resize_handlers.push(callback); } - fn titlebar_height(&self) -> f32 { - 24. + fn on_fullscreen(&mut self, callback: Box) { + self.fullscreen_handlers.push(callback) } - fn present_scene(&mut self, scene: crate::Scene) { - self.current_scene = Some(scene); + fn on_moved(&mut self, callback: Box) { + self.moved_handlers.push(callback); } - fn appearance(&self) -> crate::Appearance { - crate::Appearance::Light + fn on_should_close(&mut self, callback: Box bool>) { + self.should_close_handler = Some(callback); + } + + fn on_close(&mut self, callback: Box) { + self.close_handlers.push(callback); } fn on_appearance_changed(&mut self, _: Box) {} @@ -343,11 +376,3 @@ impl super::Window for Window { true } } - -pub fn platform() -> Platform { - Platform::new() -} - -pub fn foreground_platform() -> ForegroundPlatform { - ForegroundPlatform::default() -} diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 8409a1dff5ac2c46c9b3731be660388a9707e61c..716ec766443bc997c57fb77f78d94f0290aad579 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -15,3 +15,4 @@ thread_local = "1.1.4" lazy_static = "1.4" parking_lot = "0.11.1" futures = "0.3" +uuid = { version = "1.1.2", features = ["v4"] } \ No newline at end of file diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 9d5d0c8b2f9a62a43dddca9b8c9460bb0edb5f9f..86d69afe5f8203224f5db5f7fd319b75ecb993c8 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -60,6 +60,14 @@ impl Bind for &[u8; C] { } } +impl Column for [u8; C] { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let bytes_slice = statement.column_blob(start_index)?; + let array = bytes_slice.try_into()?; + Ok((array, start_index + 1)) + } +} + impl StaticColumnCount for Vec {} impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { @@ -236,8 +244,7 @@ impl Bind for Option { if let Some(this) = self { this.bind(statement, start_index) } else { - for i in 0..T::column_count() { - dbg!(i); + for _ in 0..T::column_count() { statement.bind_null(start_index)?; start_index += 1; } @@ -272,17 +279,6 @@ impl Bind for [T; COUNT] { } } -impl Column for [T; COUNT] { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let mut array = [Default::default(); COUNT]; - let mut current_index = start_index; - for i in 0..COUNT { - (array[i], current_index) = T::column(statement, current_index)?; - } - Ok((array, current_index)) - } -} - impl StaticColumnCount for &Path {} impl Bind for &Path { fn bind(&self, statement: &Statement, start_index: i32) -> Result { @@ -315,6 +311,25 @@ impl Column for PathBuf { } } +impl StaticColumnCount for uuid::Uuid { + fn column_count() -> usize { + 1 + } +} + +impl Bind for uuid::Uuid { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + self.as_bytes().bind(statement, start_index) + } +} + +impl Column for uuid::Uuid { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (bytes, next_index) = Column::column(statement, start_index)?; + Ok((uuid::Uuid::from_bytes(bytes), next_index)) + } +} + impl StaticColumnCount for () { fn column_count() -> usize { 0 diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 60680f82a22a3541958e59e6ec219d7174b4b20c..fc069fe6c8d7d481873a22ebc6b679b1aba85632 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -46,6 +46,7 @@ serde = { version = "1.0", features = ["derive", "rc"] } serde_json = { version = "1.0", features = ["preserve_order"] } smallvec = { version = "1.6", features = ["union"] } indoc = "1.0.4" +uuid = { version = "1.1.2", features = ["v4"] } [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7a31d02433193d9f798334486c0027f2c55e5400..1702c6e52112d4a993aed2b94fe6881f57550237 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -534,8 +534,8 @@ mod tests { }], }, left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 7711ee5141319cf88f44b721bce3c79874a327d1..ddbea4c9f9a2fdfa55d8defc0dccef425330c543 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -6,9 +6,10 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::{Axis, geometry::{rect::RectF, vector::Vector2F}}; +use gpui::{Axis, WindowBounds}; use util::{unzip_option, ResultExt}; +use uuid::Uuid; use crate::dock::DockPosition; use crate::WorkspaceId; @@ -29,11 +30,12 @@ define_connection! { // dock_pane: Option, // PaneId // left_sidebar_open: boolean, // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS - // fullscreen bool, // Boolean - // window_x: f32, - // window_y: f32, - // window_width: f32, - // window_height: f32, + // window_state: String, // WindowBounds Discriminant + // window_x: Option, // WindowBounds::Fixed RectF x + // window_y: Option, // WindowBounds::Fixed RectF y + // window_width: Option, // WindowBounds::Fixed RectF width + // window_height: Option, // WindowBounds::Fixed RectF height + // display: Option, // Display id // ) // // pane_groups( @@ -76,7 +78,7 @@ define_connection! { timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) ) STRICT; - + CREATE TABLE pane_groups( group_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, @@ -88,7 +90,7 @@ define_connection! { ON UPDATE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; - + CREATE TABLE panes( pane_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, @@ -97,7 +99,7 @@ define_connection! { ON DELETE CASCADE ON UPDATE CASCADE ) STRICT; - + CREATE TABLE center_panes( pane_id INTEGER PRIMARY KEY, parent_group_id INTEGER, // NULL means that this is a root pane @@ -106,7 +108,7 @@ define_connection! { ON DELETE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; - + CREATE TABLE items( item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique workspace_id INTEGER NOT NULL, @@ -123,11 +125,12 @@ define_connection! { ) STRICT; ), sql!( - ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean - ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_state TEXT; + ALTER TABLE workspaces ADD COLUMN window_x REAL; + ALTER TABLE workspaces ADD COLUMN window_y REAL; + ALTER TABLE workspaces ADD COLUMN window_width REAL; + ALTER TABLE workspaces ADD COLUMN window_height REAL; + ALTER TABLE workspaces ADD COLUMN display BLOB; )]; } @@ -140,16 +143,16 @@ impl WorkspaceDb { worktree_roots: &[P], ) -> Option { let workspace_location: WorkspaceLocation = worktree_roots.into(); - + // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_region): ( + let (workspace_id, workspace_location, left_sidebar_open, dock_position, bounds, display): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, - bool, - Option<(f32, f32, f32, f32)> + Option, + Option, ) = self .select_row_bound(sql! { SELECT @@ -158,11 +161,12 @@ impl WorkspaceDb { left_sidebar_open, dock_visible, dock_anchor, - fullscreen, + window_state, window_x, window_y, window_width, - window_height + window_height, + display FROM workspaces WHERE workspace_location = ? }) @@ -170,7 +174,7 @@ impl WorkspaceDb { .context("No workspaces found") .warn_on_err() .flatten()?; - + Some(SerializedWorkspace { id: workspace_id, location: workspace_location.clone(), @@ -184,14 +188,11 @@ impl WorkspaceDb { .log_err()?, dock_position, left_sidebar_open, - fullscreen, - bounds: dbg!(window_region).map(|(x, y, width, height)| RectF::new( - Vector2F::new(x, y), - Vector2F::new(width, height) - )) + bounds, + display, }) } - + /// Saves a workspace using the worktree roots. Will garbage collect any workspaces /// that used this workspace previously pub async fn save_workspace(&self, workspace: SerializedWorkspace) { @@ -203,12 +204,12 @@ impl WorkspaceDb { DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) .expect("Clearing old panes"); - + conn.exec_bound(sql!( DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ? ))?((&workspace.location, workspace.id.clone())) .context("clearing out old locations")?; - + // Upsert conn.exec_bound(sql!( INSERT INTO workspaces( @@ -222,11 +223,11 @@ impl WorkspaceDb { VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET - workspace_location = ?2, - left_sidebar_open = ?3, - dock_visible = ?4, - dock_anchor = ?5, - timestamp = CURRENT_TIMESTAMP + workspace_location = ?2, + left_sidebar_open = ?3, + dock_visible = ?4, + dock_anchor = ?5, + timestamp = CURRENT_TIMESTAMP ))?(( workspace.id, &workspace.location, @@ -234,14 +235,14 @@ impl WorkspaceDb { workspace.dock_position, )) .context("Updating workspace")?; - + // Save center pane group and dock pane Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) .context("save pane group in save workspace")?; - + let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true) .context("save pane in save workspace")?; - + // Complete workspace initialization conn.exec_bound(sql!( UPDATE workspaces @@ -249,20 +250,20 @@ impl WorkspaceDb { WHERE workspace_id = ? ))?((dock_id, workspace.id)) .context("Finishing initialization with dock pane")?; - + Ok(()) }) .log_err(); }) .await; } - + query! { pub async fn next_id() -> Result { INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id } } - + query! { fn recent_workspaces() -> Result> { SELECT workspace_id, workspace_location @@ -271,14 +272,14 @@ impl WorkspaceDb { ORDER BY timestamp DESC } } - + query! { async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> { DELETE FROM workspaces WHERE workspace_id IS ? } } - + // Returns the recent locations which are still valid on disk and deletes ones which no longer // exist. pub async fn recent_workspaces_on_disk(&self) -> Result> { @@ -286,18 +287,18 @@ impl WorkspaceDb { let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { if location.paths().iter().all(|path| path.exists()) - && location.paths().iter().any(|path| path.is_dir()) + && location.paths().iter().any(|path| path.is_dir()) { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); } } - + futures::future::join_all(delete_tasks).await; Ok(result) } - + pub async fn last_workspace(&self) -> Result> { Ok(self .recent_workspaces_on_disk() @@ -306,7 +307,7 @@ impl WorkspaceDb { .next() .map(|(_, location)| location)) } - + fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { Ok(self .get_pane_group(workspace_id, None)? @@ -319,7 +320,7 @@ impl WorkspaceDb { }) })) } - + fn get_pane_group( &self, workspace_id: WorkspaceId, @@ -376,7 +377,7 @@ impl WorkspaceDb { }) .collect::>() } - + fn save_pane_group( conn: &Connection, workspace_id: WorkspaceId, @@ -386,18 +387,18 @@ impl WorkspaceDb { match pane_group { SerializedPaneGroup::Group { axis, children } => { let (parent_id, position) = unzip_option(parent); - + let group_id = conn.select_row_bound::<_, i64>(sql!( INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) VALUES (?, ?, ?, ?) RETURNING group_id ))?((workspace_id, parent_id, position, *axis))? .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; - + for (position, group) in children.iter().enumerate() { Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))? } - + Ok(()) } SerializedPaneGroup::Pane(pane) => { @@ -406,7 +407,7 @@ impl WorkspaceDb { } } } - + fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { let (pane_id, active) = self.select_row_bound(sql!( SELECT pane_id, active @@ -414,13 +415,13 @@ impl WorkspaceDb { WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) ))?(workspace_id)? .context("No dock pane for workspace")?; - + Ok(SerializedPane::new( self.get_items(pane_id).context("Reading items")?, active, )) } - + fn save_pane( conn: &Connection, workspace_id: WorkspaceId, @@ -434,7 +435,7 @@ impl WorkspaceDb { RETURNING pane_id ))?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - + if !dock { let (parent_id, order) = unzip_option(parent); conn.exec_bound(sql!( @@ -442,12 +443,12 @@ impl WorkspaceDb { VALUES (?, ?, ?) ))?((pane_id, parent_id, order))?; } - + Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; - + Ok(pane_id) } - + fn get_items(&self, pane_id: PaneId) -> Result> { Ok(self.select_bound(sql!( SELECT kind, item_id, active FROM items @@ -455,7 +456,7 @@ impl WorkspaceDb { ORDER BY position ))?(pane_id)?) } - + fn save_items( conn: &Connection, workspace_id: WorkspaceId, @@ -468,10 +469,10 @@ impl WorkspaceDb { for (position, item) in items.iter().enumerate() { insert((workspace_id, pane_id, position, item))?; } - + Ok(()) } - + query! { pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { UPDATE workspaces @@ -479,15 +480,16 @@ impl WorkspaceDb { WHERE workspace_id = ? } } - + query! { - pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, bounds: Option<(f32, f32, f32, f32)>) -> Result<()> { + pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> { UPDATE workspaces - SET fullscreen = ?2, + SET window_state = ?2, window_x = ?3, window_y = ?4, window_width = ?5, - window_height = ?6 + window_height = ?6, + display = ?7 WHERE workspace_id = ?1 } } @@ -495,20 +497,20 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - + use std::sync::Arc; - + use db::open_test_db; use settings::DockAnchor; - + use super::*; - + #[gpui::test] async fn test_next_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_next_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -524,7 +526,7 @@ mod tests { .unwrap(); }) .await; - + let id = db.next_id().await.unwrap(); // Assert the empty row got inserted assert_eq!( @@ -535,28 +537,28 @@ mod tests { .unwrap()(id) .unwrap() ); - + db.write(move |conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", id)) - .unwrap() + .unwrap() }) .await; - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_workspace_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -572,7 +574,7 @@ mod tests { }) .await .unwrap(); - + let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -580,10 +582,10 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -591,60 +593,60 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", 1)) - .unwrap(); + .unwrap(); }) .await; - + db.save_workspace(workspace_2.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-2", 2)) - .unwrap(); + .unwrap(); }) .await; - + workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; - + workspace_2.dock_pane.children.push(SerializedItem { kind: Arc::from("Test"), item_id: 10, active: true, }); db.save_workspace(workspace_2).await; - + let test_text_2 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(2) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_2, "test-text-2"); - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_full_workspace_serialization() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - + let dock_pane = crate::persistence::model::SerializedPane { children: vec![ SerializedItem::new("Terminal", 1, false), @@ -654,7 +656,7 @@ mod tests { ], active: false, }; - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -691,7 +693,7 @@ mod tests { )), ], }; - + let workspace = SerializedWorkspace { id: 5, location: (["/tmp", "/tmp2"]).into(), @@ -699,29 +701,29 @@ mod tests { center_group, dock_pane, left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace.clone()).await; let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); - + assert_eq!(workspace, round_trip_workspace.unwrap()); - + // Test guaranteed duplicate IDs db.save_workspace(workspace.clone()).await; db.save_workspace(workspace.clone()).await; - + let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); assert_eq!(workspace, round_trip_workspace.unwrap()); } - + #[gpui::test] async fn test_workspace_assignment() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_basic_functionality").await); - + let workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -729,10 +731,10 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -740,13 +742,13 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_2.clone()).await; - + // Test that paths are treated as a set assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), @@ -756,20 +758,20 @@ mod tests { db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), workspace_1 ); - + // Make sure that other keys work assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); - + // Test 'mutate' case of updating a pre-existing id workspace_2.location = (["/tmp", "/tmp2"]).into(); - + db.save_workspace(workspace_2.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_2 ); - + // Test other mechanism for mutating let mut workspace_3 = SerializedWorkspace { id: 3, @@ -778,16 +780,16 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_3.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_3 ); - + // Make sure that updating paths differently also works workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); db.save_workspace(workspace_3.clone()).await; @@ -798,11 +800,11 @@ mod tests { workspace_3 ); } - + use crate::dock::DockPosition; use crate::persistence::model::SerializedWorkspace; use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; - + fn default_workspace>( workspace_id: &[P], dock_pane: SerializedPane, @@ -815,17 +817,17 @@ mod tests { center_group: center_group.clone(), dock_pane, left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), } } - + #[gpui::test] async fn test_basic_dock_pane() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("basic_dock_pane").await); - + let dock_pane = crate::persistence::model::SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -835,22 +837,22 @@ mod tests { ], false, ); - + let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.dock_pane, new_workspace.dock_pane); } - + #[gpui::test] async fn test_simple_split() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("simple_split").await); - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -887,22 +889,22 @@ mod tests { )), ], }; - + let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } - + #[gpui::test] async fn test_cleanup_panes() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - + let center_pane = SerializedPaneGroup::Group { axis: gpui::Axis::Horizontal, children: vec![ @@ -934,13 +936,13 @@ mod tests { )), ], }; - + let id = &["/tmp"]; - + let mut workspace = default_workspace(id, Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + workspace.center_group = SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ @@ -960,11 +962,11 @@ mod tests { )), ], }; - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(id).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index f6236ced79f6e35e89baf3b384af01436394aebf..507582b2160d4585c3dac1eb199c234c14ea8de4 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -6,9 +6,7 @@ use std::{ use anyhow::{Context, Result}; use async_recursion::async_recursion; -use gpui::{ - geometry::rect::RectF, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds, -}; +use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds}; use db::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, @@ -17,6 +15,7 @@ use db::sqlez::{ use project::Project; use settings::DockAnchor; use util::ResultExt; +use uuid::Uuid; use crate::{ dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, @@ -69,20 +68,8 @@ pub struct SerializedWorkspace { pub center_group: SerializedPaneGroup, pub dock_pane: SerializedPane, pub left_sidebar_open: bool, - pub fullscreen: bool, - pub bounds: Option, -} - -impl SerializedWorkspace { - pub fn bounds(&self) -> WindowBounds { - if self.fullscreen { - WindowBounds::Fullscreen - } else if let Some(bounds) = self.bounds { - WindowBounds::Fixed(bounds) - } else { - WindowBounds::Maximized - } - } + pub bounds: Option, + pub display: Option, } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4c742cd7faf14f010e340f3e8aa0d8cacb99a03d..4602e498f1e3d3f7bc50f34de48b7981e1ed29a3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -37,8 +37,8 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, - MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, + MouseButton, MutableAppContext, PathPromptOptions, Platform, PromptLevel, RenderContext, + SizeConstraint, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -339,7 +339,8 @@ pub struct AppState { pub client: Arc, pub user_store: ModelHandle, pub fs: Arc, - pub build_window_options: fn(Option) -> WindowOptions<'static>, + pub build_window_options: + fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), pub dock_default_item_factory: DockDefaultItemFactory, } @@ -366,7 +367,7 @@ impl AppState { languages, user_store, initialize_workspace: |_, _, _| {}, - build_window_options: |_| Default::default(), + build_window_options: |_, _, _| Default::default(), dock_default_item_factory: |_, _| unimplemented!(), }) } @@ -681,11 +682,14 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; + let (bounds, display) = dbg!(serialized_workspace + .as_ref() + .and_then(|sw| sw.bounds.zip(sw.display)) + .unzip()); + // Use the serialized workspace to construct the new window let (_, workspace) = cx.add_window( - (app_state.build_window_options)(dbg!(serialized_workspace - .as_ref() - .map(|sw| sw.bounds()))), + (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), |cx| { let mut workspace = Workspace::new( serialized_workspace, @@ -695,20 +699,9 @@ impl Workspace { cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - cx.observe_window_bounds(move |_, bounds, cx| { - let fullscreen = cx.window_is_fullscreen(cx.window_id()); - let bounds = if let WindowBounds::Fixed(region) = bounds { - Some(( - region.min_x(), - region.min_y(), - region.width(), - region.height(), - )) - } else { - None - }; + cx.observe_window_bounds(move |_, bounds, display, cx| { cx.background() - .spawn(DB.set_bounds(workspace_id, fullscreen, bounds)) + .spawn(DB.set_window_bounds(workspace_id, dbg!(bounds), dbg!(display))) .detach_and_log_err(cx); }) .detach(); @@ -2349,8 +2342,8 @@ impl Workspace { dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; cx.background() diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c2b15c7cb5beb9cb5540ad5b4829bf9aaec9de5d..40861a8f262792f639cb2e9506608dfcd2a42d6f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -111,6 +111,7 @@ tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} url = "2.2" urlencoding = "2.1.2" +uuid = { version = "1.1.2", features = ["v4"] } [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3529e1c6dc61257ce2ef9390ed805e98bbe6b4c0..879cc441c8d4279861348ac50eaf6a1809428cec 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,7 +20,8 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, PromptLevel, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, ClipboardItem, Platform, PromptLevel, TitlebarOptions, + ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -33,6 +34,7 @@ use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt}; +use uuid::Uuid; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; @@ -369,7 +371,11 @@ pub fn initialize_workspace( }); } -pub fn build_window_options(bounds: Option) -> WindowOptions<'static> { +pub fn build_window_options( + bounds: Option, + display: Option, + platform: &dyn Platform, +) -> WindowOptions<'static> { let bounds = bounds .or_else(|| { ZED_WINDOW_POSITION @@ -378,8 +384,9 @@ pub fn build_window_options(bounds: Option) -> WindowOptions<'stat }) .unwrap_or(WindowBounds::Maximized); + let screen = display.and_then(|display| platform.screen_by_id(display)); + WindowOptions { - bounds, titlebar: Some(TitlebarOptions { title: None, appears_transparent: true, @@ -389,7 +396,8 @@ pub fn build_window_options(bounds: Option) -> WindowOptions<'stat focus: true, kind: WindowKind::Normal, is_movable: true, - screen: None, + bounds, + screen, } } From a369fb8033d700e0b897bcd4a67423bab098813b Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 25 Jan 2023 17:05:57 -0800 Subject: [PATCH 4/5] better but still broken --- .../src/incoming_call_notification.rs | 5 +- .../src/project_shared_notification.rs | 4 +- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac.rs | 61 +++- crates/gpui/src/platform/mac/geometry.rs | 96 +++++- crates/gpui/src/platform/mac/screen.rs | 26 +- crates/gpui/src/platform/mac/window.rs | 321 +++++++----------- crates/gpui/src/platform/test.rs | 4 +- crates/zed/src/zed.rs | 3 +- 9 files changed, 290 insertions(+), 232 deletions(-) diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 6ad533665e7477494e221da71f85a31646671447..5d888bc093511f593d92732e266bccf7e39263f3 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -32,11 +32,12 @@ pub fn init(cx: &mut MutableAppContext) { }); for screen in cx.platform().screens() { - let screen_size = screen.size(); + let screen_bounds = screen.bounds(); let (window_id, _) = cx.add_window( WindowOptions { bounds: WindowBounds::Fixed(RectF::new( - vec2f(screen_size.x() - window_size.x() - PADDING, PADDING), + screen_bounds.upper_right() + - vec2f(PADDING + window_size.x(), PADDING), window_size, )), titlebar: None, diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 0815d9c8d8c02c6756f65c61ca0b4c5187d0b016..8488f3381e1e23cc8467ee49df2feea6c7f18574 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -31,11 +31,11 @@ pub fn init(cx: &mut MutableAppContext) { let window_size = vec2f(theme.window_width, theme.window_height); for screen in cx.platform().screens() { - let screen_size = screen.size(); + let screen_bounds = screen.bounds(); let (window_id, _) = cx.add_window( WindowOptions { bounds: WindowBounds::Fixed(RectF::new( - vec2f(screen_size.x() - window_size.x() - PADDING, PADDING), + screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING), window_size, )), titlebar: None, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index e91c1e87aa0e6b0fe361665982af56073d9fb600..d1aaed7f4746235712fbf01adf860b8ce73d9786 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -123,7 +123,7 @@ pub trait InputHandler { pub trait Screen: Debug { fn as_any(&self) -> &dyn Any; - fn size(&self) -> Vector2F; + fn bounds(&self) -> RectF; fn display_uuid(&self) -> Uuid; } diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index 7eb080083e9d4e2ed2f74597b95ebc7217f120af..9e3f1040554401288499ac32a3ddd2fd9956cd20 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -12,12 +12,15 @@ mod sprite_cache; mod status_item; mod window; -use cocoa::base::{BOOL, NO, YES}; +use cocoa::{ + base::{id, nil, BOOL, NO, YES}, + foundation::{NSAutoreleasePool, NSNotFound, NSString, NSUInteger}, +}; pub use dispatcher::Dispatcher; pub use fonts::FontSystem; use platform::{MacForegroundPlatform, MacPlatform}; pub use renderer::Surface; -use std::{rc::Rc, sync::Arc}; +use std::{ops::Range, rc::Rc, sync::Arc}; use window::Window; pub(crate) fn platform() -> Arc { @@ -41,3 +44,57 @@ impl BoolExt for bool { } } } + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct NSRange { + pub location: NSUInteger, + pub length: NSUInteger, +} + +impl NSRange { + fn invalid() -> Self { + Self { + location: NSNotFound as NSUInteger, + length: 0, + } + } + + fn is_valid(&self) -> bool { + self.location != NSNotFound as NSUInteger + } + + fn to_range(self) -> Option> { + if self.is_valid() { + let start = self.location as usize; + let end = start + self.length as usize; + Some(start..end) + } else { + None + } + } +} + +impl From> for NSRange { + fn from(range: Range) -> Self { + NSRange { + location: range.start as NSUInteger, + length: range.len() as NSUInteger, + } + } +} + +unsafe impl objc::Encode for NSRange { + fn encode() -> objc::Encoding { + let encoding = format!( + "{{NSRange={}{}}}", + NSUInteger::encode().as_str(), + NSUInteger::encode().as_str() + ); + unsafe { objc::Encoding::from_str(&encoding) } + } +} + +unsafe fn ns_string(string: &str) -> id { + NSString::alloc(nil).init_str(string).autorelease() +} diff --git a/crates/gpui/src/platform/mac/geometry.rs b/crates/gpui/src/platform/mac/geometry.rs index 89da409dbd4ed45e080625fb927c9e8d4eec363d..a59d72a7969abb946002e6555e09b96e622b0962 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -1,27 +1,99 @@ -use cocoa::foundation::{NSPoint, NSRect, NSSize}; -use pathfinder_geometry::{rect::RectF, vector::Vector2F}; +use cocoa::{ + appkit::NSWindow, + base::id, + foundation::{NSPoint, NSRect, NSSize}, +}; +use objc::{msg_send, sel, sel_impl}; +use pathfinder_geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, +}; + +///! Macos screen have a y axis that goings up from the bottom of the screen and +///! an origin at the bottom left of the main display. pub trait Vector2FExt { - fn to_ns_point(&self) -> NSPoint; - fn to_ns_size(&self) -> NSSize; + /// Converts self to an NSPoint with y axis pointing up. + fn to_screen_ns_point(&self, native_window: id) -> NSPoint; +} +impl Vector2FExt for Vector2F { + fn to_screen_ns_point(&self, native_window: id) -> NSPoint { + unsafe { + let point = NSPoint::new(self.x() as f64, -self.y() as f64); + msg_send![native_window, convertPointToScreen: point] + } + } } pub trait RectFExt { + /// Converts self to an NSRect with y axis pointing up. + /// The resulting NSRect will have an origin at the bottom left of the rectangle. + /// Also takes care of converting from window scaled coordinates to screen coordinates + fn to_screen_ns_rect(&self, native_window: id) -> NSRect; + + /// Converts self to an NSRect with y axis point up. + /// The resulting NSRect will have an origin at the bottom left of the rectangle. + /// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale fn to_ns_rect(&self) -> NSRect; } +impl RectFExt for RectF { + fn to_screen_ns_rect(&self, native_window: id) -> NSRect { + unsafe { native_window.convertRectToScreen_(self.to_ns_rect()) } + } -impl Vector2FExt for Vector2F { - fn to_ns_point(&self) -> NSPoint { - NSPoint::new(self.x() as f64, self.y() as f64) + fn to_ns_rect(&self) -> NSRect { + dbg!(&self); + NSRect::new( + NSPoint::new( + dbg!(self.origin_x() as f64), + dbg!(-(self.origin_y() - self.height()) as f64), + ), + NSSize::new(self.width() as f64, self.height() as f64), + ) } +} - fn to_ns_size(&self) -> NSSize { - NSSize::new(self.x() as f64, self.y() as f64) +pub trait NSPointExt { + /// Converts self to a Vector2F with y axis pointing down. + /// Also takes care of converting from window scaled coordinates to screen coordinates + fn to_window_vector2f(&self, native_window: id) -> Vector2F; +} +impl NSPointExt for NSPoint { + fn to_window_vector2f(&self, native_window: id) -> Vector2F { + unsafe { + let point: NSPoint = msg_send![native_window, convertPointFromScreen: self]; + vec2f(point.x as f32, -point.y as f32) + } } } -impl RectFExt for RectF { - fn to_ns_rect(&self) -> NSRect { - NSRect::new(self.origin().to_ns_point(), self.size().to_ns_size()) +pub trait NSRectExt { + /// Converts self to a RectF with y axis pointing down. + /// The resulting RectF will have an origin at the top left of the rectangle. + /// Also takes care of converting from screen scale coordinates to window coordinates + fn to_window_rectf(&self, native_window: id) -> RectF; + + /// Converts self to a RectF with y axis pointing down. + /// The resulting RectF will have an origin at the top left of the rectangle. + /// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale + fn to_rectf(&self) -> RectF; +} +impl NSRectExt for NSRect { + fn to_window_rectf(&self, native_window: id) -> RectF { + unsafe { + dbg!(self.origin.x); + let rect: NSRect = native_window.convertRectFromScreen_(*self); + rect.to_rectf() + } + } + + fn to_rectf(&self) -> RectF { + RectF::new( + vec2f( + dbg!(self.origin.x as f32), + dbg!(-(self.origin.y - self.size.height) as f32), + ), + vec2f(self.size.width as f32, self.size.height as f32), + ) } } diff --git a/crates/gpui/src/platform/mac/screen.rs b/crates/gpui/src/platform/mac/screen.rs index a54ffb3f90f9e322a4053ee777a5e4172e3591fe..27fb4b12d1c8d1ab918874aa1b39ae6f0a2cfaa6 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -1,21 +1,21 @@ use std::{any::Any, ffi::c_void}; -use crate::{ - geometry::vector::{vec2f, Vector2F}, - platform, -}; +use crate::platform; use cocoa::{ appkit::NSScreen, base::{id, nil}, - foundation::{NSArray, NSDictionary, NSString}, + foundation::{NSArray, NSDictionary}, }; use core_foundation::{ number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef}, uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}, }; use core_graphics::display::CGDirectDisplayID; +use pathfinder_geometry::rect::RectF; use uuid::Uuid; +use super::{geometry::NSRectExt, ns_string}; + #[link(name = "ApplicationServices", kind = "framework")] extern "C" { pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; @@ -58,13 +58,6 @@ impl platform::Screen for Screen { self } - fn size(&self) -> Vector2F { - unsafe { - let frame = self.native_screen.frame(); - vec2f(frame.size.width as f32, frame.size.height as f32) - } - } - fn display_uuid(&self) -> uuid::Uuid { unsafe { // Screen ids are not stable. Further, the default device id is also unstable across restarts. @@ -72,7 +65,7 @@ impl platform::Screen for Screen { // This approach is similar to that which winit takes // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99 let device_description = self.native_screen.deviceDescription(); - let key = NSString::alloc(nil).init_str("NSScreenNumber"); + let key = ns_string("NSScreenNumber"); let device_id_obj = device_description.objectForKey_(key); let mut device_id: u32 = 0; CFNumberGetValue( @@ -102,4 +95,11 @@ impl platform::Screen for Screen { ]) } } + + fn bounds(&self) -> RectF { + unsafe { + let frame = self.native_screen.frame(); + frame.to_rectf() + } + } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e981cc6131b54c9e561bc64db3fc829143c75afe..ecea0604f527312008b6382b1ffa32686ea6d7a1 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -17,14 +17,12 @@ use crate::{ use block::ConcreteBlock; use cocoa::{ appkit::{ - CGFloat, CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, - NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, - NSWindowCollectionBehavior, NSWindowStyleMask, + CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, + NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior, + NSWindowStyleMask, }, base::{id, nil}, - foundation::{ - NSAutoreleasePool, NSInteger, NSNotFound, NSPoint, NSRect, NSSize, NSString, NSUInteger, - }, + foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger}, }; use core_graphics::display::CGRect; use ctor::ctor; @@ -52,6 +50,11 @@ use std::{ time::Duration, }; +use super::{ + geometry::{NSRectExt, Vector2FExt}, + ns_string, NSRange, +}; + const WINDOW_STATE_IVAR: &str = "windowState"; static mut WINDOW_CLASS: *const Class = ptr::null(); @@ -76,56 +79,6 @@ const NSTrackingInVisibleRect: NSUInteger = 0x200; #[allow(non_upper_case_globals)] const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4; -#[repr(C)] -#[derive(Copy, Clone, Debug)] -struct NSRange { - pub location: NSUInteger, - pub length: NSUInteger, -} - -impl NSRange { - fn invalid() -> Self { - Self { - location: NSNotFound as NSUInteger, - length: 0, - } - } - - fn is_valid(&self) -> bool { - self.location != NSNotFound as NSUInteger - } - - fn to_range(self) -> Option> { - if self.is_valid() { - let start = self.location as usize; - let end = start + self.length as usize; - Some(start..end) - } else { - None - } - } -} - -impl From> for NSRange { - fn from(range: Range) -> Self { - NSRange { - location: range.start as NSUInteger, - length: range.len() as NSUInteger, - } - } -} - -unsafe impl objc::Encode for NSRange { - fn encode() -> objc::Encoding { - let encoding = format!( - "{{NSRange={}{}}}", - NSUInteger::encode().as_str(), - NSUInteger::encode().as_str() - ); - unsafe { objc::Encoding::from_str(&encoding) } - } -} - #[ctor] unsafe fn build_classes() { WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow)); @@ -315,8 +268,6 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C decl.register() } -pub struct Window(Rc>); - ///Used to track what the IME does when we send it a keystroke. ///This is only used to handle the case where the IME mysteriously ///swallows certain keys. @@ -329,6 +280,11 @@ enum ImeState { None, } +struct InsertText { + replacement_range: Option>, + text: String, +} + struct WindowState { id: usize, native_window: id, @@ -357,11 +313,112 @@ struct WindowState { ime_text: Option, } -struct InsertText { - replacement_range: Option>, - text: String, +impl WindowState { + fn move_traffic_light(&self) { + if let Some(traffic_light_position) = self.traffic_light_position { + let titlebar_height = self.titlebar_height(); + + unsafe { + let close_button: id = msg_send![ + self.native_window, + standardWindowButton: NSWindowButton::NSWindowCloseButton + ]; + let min_button: id = msg_send![ + self.native_window, + standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton + ]; + let zoom_button: id = msg_send![ + self.native_window, + standardWindowButton: NSWindowButton::NSWindowZoomButton + ]; + + let mut close_button_frame: CGRect = msg_send![close_button, frame]; + let mut min_button_frame: CGRect = msg_send![min_button, frame]; + let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame]; + let mut origin = vec2f( + traffic_light_position.x(), + titlebar_height + - traffic_light_position.y() + - close_button_frame.size.height as f32, + ); + let button_spacing = + (min_button_frame.origin.x - close_button_frame.origin.x) as f32; + + close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); + let _: () = msg_send![close_button, setFrame: close_button_frame]; + origin.set_x(origin.x() + button_spacing); + + min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); + let _: () = msg_send![min_button, setFrame: min_button_frame]; + origin.set_x(origin.x() + button_spacing); + + zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); + let _: () = msg_send![zoom_button, setFrame: zoom_button_frame]; + } + } + } + + fn is_fullscreen(&self) -> bool { + unsafe { + let style_mask = self.native_window.styleMask(); + style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask) + } + } + + fn bounds(&self) -> WindowBounds { + unsafe { + if self.is_fullscreen() { + return WindowBounds::Fullscreen; + } + + let screen_frame = self + .native_window + .screen() + .visibleFrame() + .to_window_rectf(self.native_window); + let window_frame = self.frame(); + + if screen_frame == window_frame { + WindowBounds::Maximized + } else { + WindowBounds::Fixed(window_frame) + } + } + } + + // Returns the window bounds in window coordinates + fn frame(&self) -> RectF { + unsafe { NSWindow::frame(self.native_window).to_window_rectf(self.native_window) } + } + + fn content_size(&self) -> Vector2F { + let NSSize { width, height, .. } = + unsafe { NSView::frame(self.native_window.contentView()) }.size; + vec2f(width as f32, height as f32) + } + + fn scale_factor(&self) -> f32 { + get_scale_factor(self.native_window) + } + + fn titlebar_height(&self) -> f32 { + unsafe { + let frame = NSWindow::frame(self.native_window); + let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect]; + (frame.size.height - content_layout_rect.size.height) as f32 + } + } + + fn present_scene(&mut self, scene: Scene) { + self.scene_to_render = Some(scene); + unsafe { + let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES]; + } + } } +pub struct Window(Rc>); + impl Window { pub fn open( id: usize, @@ -395,7 +452,7 @@ impl Window { } }; let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_( - RectF::new(Default::default(), vec2f(1024., 768.)).to_ns_rect(), + NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)), style_mask, NSBackingStoreBuffered, NO, @@ -416,20 +473,8 @@ impl Window { WindowBounds::Maximized => { native_window.setFrame_display_(screen.visibleFrame(), YES); } - WindowBounds::Fixed(top_left_bounds) => { - let frame = screen.visibleFrame(); - let bottom_left_bounds = RectF::new( - dbg!(vec2f( - top_left_bounds.origin_x(), - frame.size.height as f32 - - top_left_bounds.origin_y() - - top_left_bounds.height(), - )), - dbg!(top_left_bounds.size()), - ) - .to_ns_rect(); - let screen_rect = native_window.convertRectToScreen_(bottom_left_bounds); - native_window.setFrame_display_(screen_rect, YES); + WindowBounds::Fixed(rect) => { + native_window.setFrame_display_(rect.to_screen_ns_rect(native_window), YES); } } @@ -776,21 +821,16 @@ impl platform::Window for Window { } fn is_topmost_for_position(&self, position: Vector2F) -> bool { - let window_bounds = self.bounds(); let self_borrow = self.0.borrow(); let self_id = self_borrow.id; unsafe { + let window_frame = self_borrow.frame(); let app = NSApplication::sharedApplication(nil); - // Convert back to bottom-left coordinates - let point = NSPoint::new( - position.x() as CGFloat, - (window_bounds.height() - position.y()) as CGFloat, - ); - - let screen_point: NSPoint = - msg_send![self_borrow.native_window, convertPointToScreen: point]; + // Convert back to screen coordinates + let screen_point = + (position + window_frame.origin()).to_screen_ns_point(self_borrow.native_window); let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0]; let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number]; @@ -807,113 +847,6 @@ impl platform::Window for Window { } } -impl WindowState { - fn move_traffic_light(&self) { - if let Some(traffic_light_position) = self.traffic_light_position { - let titlebar_height = self.titlebar_height(); - - unsafe { - let close_button: id = msg_send![ - self.native_window, - standardWindowButton: NSWindowButton::NSWindowCloseButton - ]; - let min_button: id = msg_send![ - self.native_window, - standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton - ]; - let zoom_button: id = msg_send![ - self.native_window, - standardWindowButton: NSWindowButton::NSWindowZoomButton - ]; - - let mut close_button_frame: CGRect = msg_send![close_button, frame]; - let mut min_button_frame: CGRect = msg_send![min_button, frame]; - let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame]; - let mut origin = vec2f( - traffic_light_position.x(), - titlebar_height - - traffic_light_position.y() - - close_button_frame.size.height as f32, - ); - let button_spacing = - (min_button_frame.origin.x - close_button_frame.origin.x) as f32; - - close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); - let _: () = msg_send![close_button, setFrame: close_button_frame]; - origin.set_x(origin.x() + button_spacing); - - min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); - let _: () = msg_send![min_button, setFrame: min_button_frame]; - origin.set_x(origin.x() + button_spacing); - - zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64); - let _: () = msg_send![zoom_button, setFrame: zoom_button_frame]; - } - } - } - - fn is_fullscreen(&self) -> bool { - unsafe { - let style_mask = self.native_window.styleMask(); - style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask) - } - } - - fn bounds(&self) -> WindowBounds { - unsafe { - if self.is_fullscreen() { - return WindowBounds::Fullscreen; - } - - let screen_frame = self.native_window.screen().visibleFrame(); - let window_frame = NSWindow::frame(self.native_window); - let origin = vec2f( - window_frame.origin.x as f32, - (screen_frame.size.height - window_frame.origin.y - window_frame.size.height) - as f32, - ); - let size = vec2f( - window_frame.size.width as f32, - window_frame.size.height as f32, - ); - - if origin.is_zero() - && size.x() == screen_frame.size.width as f32 - && size.y() == screen_frame.size.height as f32 - { - WindowBounds::Maximized - } else { - WindowBounds::Fixed(RectF::new(origin, size)) - } - } - } - - fn content_size(&self) -> Vector2F { - let NSSize { width, height, .. } = - unsafe { NSView::frame(self.native_window.contentView()) }.size; - vec2f(width as f32, height as f32) - } - - fn scale_factor(&self) -> f32 { - get_scale_factor(self.native_window) - } - - fn titlebar_height(&self) -> f32 { - unsafe { - let frame = NSWindow::frame(self.native_window); - let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect]; - (frame.size.height - content_layout_rect.size.height) as f32 - } - } - - fn present_scene(&mut self, scene: Scene) { - self.scene_to_render = Some(scene); - unsafe { - let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES]; - } - } -} - fn get_scale_factor(native_window: id) -> f32 { unsafe { let screen: id = msg_send![native_window, screen]; @@ -1547,10 +1480,6 @@ async fn synthetic_drag( } } -unsafe fn ns_string(string: &str) -> id { - NSString::alloc(nil).init_str(string).autorelease() -} - fn with_input_handler(window: &Object, f: F) -> Option where F: FnOnce(&mut dyn InputHandler) -> R, diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 2c8a9409694c96d58ff49b2d902ed652710b04d6..6c8637948e0e532d6a69ebc48ca3b4c23dc894c7 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -234,8 +234,8 @@ impl super::Screen for Screen { self } - fn size(&self) -> Vector2F { - Vector2F::new(1920., 1080.) + fn bounds(&self) -> RectF { + RectF::new(Vector2F::zero(), Vector2F::new(1920., 1080.)) } fn display_uuid(&self) -> uuid::Uuid { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 879cc441c8d4279861348ac50eaf6a1809428cec..a0a58a86fb506edb1c419c60b7260ef06b4dc797 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,8 +20,7 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, ClipboardItem, Platform, PromptLevel, TitlebarOptions, - ViewContext, WindowKind, + AssetSource, AsyncAppContext, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; From 1593b1e13d3a842fcb115873c5bd25f85c929bfe Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Thu, 26 Jan 2023 16:35:00 -0800 Subject: [PATCH 5/5] window position restoration working --- crates/gpui/src/platform/mac/geometry.rs | 38 +++++++++++----------- crates/gpui/src/platform/mac/window.rs | 21 +++++++------ crates/workspace/src/workspace.rs | 40 +++++++++++++++++++++--- 3 files changed, 66 insertions(+), 33 deletions(-) diff --git a/crates/gpui/src/platform/mac/geometry.rs b/crates/gpui/src/platform/mac/geometry.rs index a59d72a7969abb946002e6555e09b96e622b0962..0f3b1f6fceaf3f71d8cb26f90a1f0b08d2ad4680 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -42,31 +42,16 @@ impl RectFExt for RectF { } fn to_ns_rect(&self) -> NSRect { - dbg!(&self); NSRect::new( NSPoint::new( - dbg!(self.origin_x() as f64), - dbg!(-(self.origin_y() - self.height()) as f64), + self.origin_x() as f64, + -(self.origin_y() + self.height()) as f64, ), NSSize::new(self.width() as f64, self.height() as f64), ) } } -pub trait NSPointExt { - /// Converts self to a Vector2F with y axis pointing down. - /// Also takes care of converting from window scaled coordinates to screen coordinates - fn to_window_vector2f(&self, native_window: id) -> Vector2F; -} -impl NSPointExt for NSPoint { - fn to_window_vector2f(&self, native_window: id) -> Vector2F { - unsafe { - let point: NSPoint = msg_send![native_window, convertPointFromScreen: self]; - vec2f(point.x as f32, -point.y as f32) - } - } -} - pub trait NSRectExt { /// Converts self to a RectF with y axis pointing down. /// The resulting RectF will have an origin at the top left of the rectangle. @@ -77,11 +62,13 @@ pub trait NSRectExt { /// The resulting RectF will have an origin at the top left of the rectangle. /// Unlike to_screen_ns_rect, coordinates are not converted and are assumed to already be in screen scale fn to_rectf(&self) -> RectF; + + fn intersects(&self, other: Self) -> bool; } impl NSRectExt for NSRect { fn to_window_rectf(&self, native_window: id) -> RectF { unsafe { - dbg!(self.origin.x); + self.origin.x; let rect: NSRect = native_window.convertRectFromScreen_(*self); rect.to_rectf() } @@ -90,10 +77,21 @@ impl NSRectExt for NSRect { fn to_rectf(&self) -> RectF { RectF::new( vec2f( - dbg!(self.origin.x as f32), - dbg!(-(self.origin.y - self.size.height) as f32), + self.origin.x as f32, + -(self.origin.y + self.size.height) as f32, ), vec2f(self.size.width as f32, self.size.height as f32), ) } + + fn intersects(&self, other: Self) -> bool { + self.size.width > 0. + && self.size.height > 0. + && other.size.width > 0. + && other.size.height > 0. + && self.origin.x <= other.origin.x + other.size.width + && self.origin.x + self.size.width >= other.origin.x + && self.origin.y <= other.origin.y + other.size.height + && self.origin.y + self.size.height >= other.origin.y + } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index ecea0604f527312008b6382b1ffa32686ea6d7a1..bc934703be1b36a57d5ae5c9416022e90867d5ed 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -371,14 +371,8 @@ impl WindowState { return WindowBounds::Fullscreen; } - let screen_frame = self - .native_window - .screen() - .visibleFrame() - .to_window_rectf(self.native_window); let window_frame = self.frame(); - - if screen_frame == window_frame { + if window_frame == self.native_window.screen().visibleFrame().to_rectf() { WindowBounds::Maximized } else { WindowBounds::Fixed(window_frame) @@ -388,7 +382,10 @@ impl WindowState { // Returns the window bounds in window coordinates fn frame(&self) -> RectF { - unsafe { NSWindow::frame(self.native_window).to_window_rectf(self.native_window) } + unsafe { + let ns_frame = NSWindow::frame(self.native_window); + ns_frame.to_rectf() + } } fn content_size(&self) -> Vector2F { @@ -474,7 +471,13 @@ impl Window { native_window.setFrame_display_(screen.visibleFrame(), YES); } WindowBounds::Fixed(rect) => { - native_window.setFrame_display_(rect.to_screen_ns_rect(native_window), YES); + let screen_frame = screen.visibleFrame(); + let ns_rect = rect.to_ns_rect(); + if ns_rect.intersects(screen_frame) { + native_window.setFrame_display_(ns_rect, YES); + } else { + native_window.setFrame_display_(screen_frame, YES); + } } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4602e498f1e3d3f7bc50f34de48b7981e1ed29a3..80c52e0bc82e253916bd53cea4c1bee62d103690 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -682,10 +682,28 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; - let (bounds, display) = dbg!(serialized_workspace + let (bounds, display) = serialized_workspace .as_ref() .and_then(|sw| sw.bounds.zip(sw.display)) - .unzip()); + .and_then(|(mut bounds, display)| { + // Stored bounds are relative to the containing display. So convert back to global coordinates if that screen still exists + if let WindowBounds::Fixed(mut window_bounds) = bounds { + if let Some(screen) = cx.platform().screen_by_id(display) { + let screen_bounds = screen.bounds(); + window_bounds + .set_origin_x(window_bounds.origin_x() + screen_bounds.origin_x()); + window_bounds + .set_origin_y(window_bounds.origin_y() + screen_bounds.origin_y()); + bounds = WindowBounds::Fixed(window_bounds); + } else { + // Screen no longer exists. Return none here. + return None; + } + } + + Some((bounds, display)) + }) + .unzip(); // Use the serialized workspace to construct the new window let (_, workspace) = cx.add_window( @@ -699,9 +717,23 @@ impl Workspace { cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - cx.observe_window_bounds(move |_, bounds, display, cx| { + cx.observe_window_bounds(move |_, mut bounds, display, cx| { + // Transform fixed bounds to be stored in terms of the containing display + if let WindowBounds::Fixed(mut window_bounds) = bounds { + if let Some(screen) = cx.platform().screen_by_id(display) { + let screen_bounds = screen.bounds(); + window_bounds.set_origin_x( + window_bounds.origin_x() - screen_bounds.origin_x(), + ); + window_bounds.set_origin_y( + window_bounds.origin_y() - screen_bounds.origin_y(), + ); + bounds = WindowBounds::Fixed(window_bounds); + } + } + cx.background() - .spawn(DB.set_window_bounds(workspace_id, dbg!(bounds), dbg!(display))) + .spawn(DB.set_window_bounds(workspace_id, bounds, display)) .detach_and_log_err(cx); }) .detach();