diff --git a/Cargo.lock b/Cargo.lock index 09eac8c264..0c0744d3a9 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 f257d95493..120c577e0f 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 b19bc92455..38a47e87dc 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)(), |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/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index 6ad533665e..5d888bc093 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 0815d9c8d8..8488f3381e 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/editor/src/persistence.rs b/crates/editor/src/persistence.rs index 2d8d1a74fd..6e37735c13 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/Cargo.toml b/crates/gpui/Cargo.toml index e1b6e11b46..7be254be4d 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 9b1e13910d..95967ed485 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, @@ -43,6 +44,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 +596,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 +627,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 +685,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(), @@ -905,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; @@ -1240,6 +1252,23 @@ impl MutableAppContext { ) } + fn observe_window_bounds(&mut self, window_id: usize, callback: F) -> Subscription + where + F: 'static + FnMut(WindowBounds, Uuid, &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 @@ -1759,6 +1788,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| { @@ -2056,6 +2092,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 { @@ -2088,6 +2129,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; } @@ -2182,6 +2233,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, @@ -2314,11 +2370,18 @@ 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 = 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, uuid, this) + }); + Some(()) }); } @@ -2495,6 +2558,17 @@ impl MutableAppContext { } } + fn handle_window_moved(&mut self, window_id: usize) { + 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, display, this); + true + }); + } + pub fn focus(&mut self, window_id: usize, view_id: Option) { self.pending_effects .push_back(Effect::Focus { window_id, view_id }); @@ -2898,9 +2972,8 @@ pub enum Effect { ResizeWindow { window_id: usize, }, - FullscreenWindow { + MoveWindow { window_id: usize, - is_fullscreen: bool, }, ActivateWindow { window_id: usize, @@ -2911,11 +2984,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, @@ -3026,6 +3108,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, @@ -3060,6 +3146,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") @@ -3635,7 +3731,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) } @@ -3939,6 +4035,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, Uuid, &mut ViewContext), + { + let observer = self.weak_handle(); + self.app + .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, display, 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, @@ -5103,6 +5217,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>), @@ -5118,6 +5233,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(), @@ -5134,6 +5250,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 05ba61a9ad..d1aaed7f47 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( @@ -117,17 +123,19 @@ 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; } 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_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); @@ -136,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; } @@ -186,12 +196,70 @@ pub enum WindowKind { PopUp, } -#[derive(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.rs b/crates/gpui/src/platform/mac.rs index 7eb080083e..9e3f104055 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 89da409dbd..0f3b1f6fce 100644 --- a/crates/gpui/src/platform/mac/geometry.rs +++ b/crates/gpui/src/platform/mac/geometry.rs @@ -1,27 +1,97 @@ -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 Vector2FExt for Vector2F { - fn to_ns_point(&self) -> NSPoint { - NSPoint::new(self.x() as f64, self.y() as f64) - } - - fn to_ns_size(&self) -> NSSize { - NSSize::new(self.x() as f64, self.y() as f64) - } -} - impl RectFExt for RectF { + fn to_screen_ns_rect(&self, native_window: id) -> NSRect { + unsafe { native_window.convertRectToScreen_(self.to_ns_rect()) } + } + fn to_ns_rect(&self) -> NSRect { - NSRect::new(self.origin().to_ns_point(), self.size().to_ns_size()) + NSRect::new( + NSPoint::new( + self.origin_x() as f64, + -(self.origin_y() + self.height()) as f64, + ), + NSSize::new(self.width() as f64, self.height() as f64), + ) + } +} + +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; + + fn intersects(&self, other: Self) -> bool; +} +impl NSRectExt for NSRect { + fn to_window_rectf(&self, native_window: id) -> RectF { + unsafe { + self.origin.x; + let rect: NSRect = native_window.convertRectFromScreen_(*self); + rect.to_rectf() + } + } + + fn to_rectf(&self) -> RectF { + RectF::new( + vec2f( + 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/platform.rs b/crates/gpui/src/platform/mac/platform.rs index dbb1a01f31..5d13227585 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 fdc7fbb505..27fb4b12d1 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -1,14 +1,25 @@ -use std::any::Any; +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, + 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; +} #[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), }); @@ -35,10 +58,48 @@ impl platform::Screen for Screen { self } - fn size(&self) -> Vector2F { + 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 = ns_string("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, + ]) + } + } + + fn bounds(&self) -> RectF { unsafe { let frame = self.native_screen.frame(); - vec2f(frame.size.width as f32, frame.size.height as f32) + frame.to_rectf() } } } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index 2da7caab7e..812027d35c 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,28 +169,42 @@ impl StatusItem { } impl platform::Window for StatusItem { + fn bounds(&self) -> WindowBounds { + self.0.borrow().bounds() + } + + fn content_size(&self) -> Vector2F { + self.0.borrow().content_size() + } + + fn scale_factor(&self) -> f32 { + self.0.borrow().scale_factor() + } + + fn titlebar_height(&self) -> f32 { + 0. + } + + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = + msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; + crate::Appearance::from_native(appearance) + } + } + + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.borrow().native_window().screen(), + }) + } + } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } - fn on_event(&mut self, callback: Box bool>) { - self.0.borrow_mut().event_callback = Some(callback); - } - - fn on_appearance_changed(&mut self, callback: Box) { - self.0.borrow_mut().appearance_changed_callback = Some(callback); - } - - fn on_active_status_change(&mut self, _: Box) {} - - fn on_resize(&mut self, _: Box) {} - - fn on_fullscreen(&mut self, _: Box) {} - - fn on_should_close(&mut self, _: Box bool>) {} - - fn on_close(&mut self, _: Box) {} - fn set_input_handler(&mut self, _: Box) {} fn prompt( @@ -224,26 +240,6 @@ impl platform::Window for StatusItem { unimplemented!() } - fn toggle_full_screen(&self) { - unimplemented!() - } - - fn bounds(&self) -> RectF { - self.0.borrow().bounds() - } - - fn content_size(&self) -> Vector2F { - self.0.borrow().content_size() - } - - fn scale_factor(&self) -> f32 { - self.0.borrow().scale_factor() - } - - fn titlebar_height(&self) -> f32 { - 0. - } - fn present_scene(&mut self, scene: Scene) { self.0.borrow_mut().scene = Some(scene); unsafe { @@ -251,12 +247,28 @@ impl platform::Window for StatusItem { } } - fn appearance(&self) -> crate::Appearance { - unsafe { - let appearance: id = - msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; - crate::Appearance::from_native(appearance) - } + fn toggle_full_screen(&self) { + unimplemented!() + } + + fn on_event(&mut self, callback: Box bool>) { + self.0.borrow_mut().event_callback = Some(callback); + } + + fn on_active_status_change(&mut self, _: Box) {} + + fn on_resize(&mut self, _: Box) {} + + fn on_fullscreen(&mut self, _: Box) {} + + fn on_moved(&mut self, _: Box) {} + + 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 { @@ -265,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( @@ -279,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)) } } @@ -297,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 bef6f65e42..bc934703be 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)); @@ -295,6 +248,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), @@ -311,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. @@ -325,6 +280,11 @@ enum ImeState { None, } +struct InsertText { + replacement_range: Option>, + text: String, +} + struct WindowState { id: usize, native_window: id, @@ -333,6 +293,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>, @@ -352,11 +313,109 @@ 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 window_frame = self.frame(); + if window_frame == self.native_window.screen().visibleFrame().to_rectf() { + WindowBounds::Maximized + } else { + WindowBounds::Fixed(window_frame) + } + } + } + + // Returns the window bounds in window coordinates + fn frame(&self) -> RectF { + unsafe { + let ns_frame = NSWindow::frame(self.native_window); + ns_frame.to_rectf() + } + } + + 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, @@ -390,7 +449,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, @@ -405,25 +464,20 @@ 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); } - WindowBounds::Fixed(top_left_bounds) => { - let frame = screen.visibleFrame(); - let bottom_left_bounds = RectF::new( - vec2f( - top_left_bounds.origin_x(), - frame.size.height as f32 - - top_left_bounds.origin_y() - - top_left_bounds.height(), - ), - top_left_bounds.size(), - ) - .to_ns_rect(); - native_window.setFrame_display_( - native_window.convertRectToScreen_(bottom_left_bounds), - YES, - ); + WindowBounds::Fixed(rect) => { + 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); + } } } @@ -441,6 +495,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, @@ -576,34 +631,41 @@ impl Drop for Window { } impl platform::Window for Window { + fn bounds(&self) -> WindowBounds { + self.0.as_ref().borrow().bounds() + } + + fn content_size(&self) -> Vector2F { + self.0.as_ref().borrow().content_size() + } + + fn scale_factor(&self) -> f32 { + self.0.as_ref().borrow().scale_factor() + } + + fn titlebar_height(&self) -> f32 { + self.0.as_ref().borrow().titlebar_height() + } + + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance]; + crate::Appearance::from_native(appearance) + } + } + + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.as_ref().borrow().native_window.screen(), + }) + } + } + 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 on_resize(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().resize_callback = Some(callback); - } - - fn on_fullscreen(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); - } - - 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_active_status_change(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().activate_callback = Some(callback); - } - fn set_input_handler(&mut self, input_handler: Box) { self.0.as_ref().borrow_mut().input_handler = Some(input_handler); } @@ -713,6 +775,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; @@ -725,31 +791,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) { @@ -757,21 +824,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]; @@ -788,94 +850,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 bounds(&self) -> RectF { - unsafe { - 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) - as f32, - ); - let size = vec2f( - window_frame.size.width as f32, - window_frame.size.height as f32, - ); - 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]; @@ -1137,6 +1111,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(); @@ -1499,10 +1483,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 d33e3e2fca..6c8637948e 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,23 +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>, - 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( @@ -102,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 { @@ -132,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() } @@ -143,7 +150,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(), })) } @@ -219,12 +226,46 @@ impl super::Platform for Platform { } } +#[derive(Debug)] +pub struct Screen; + +impl super::Screen for Screen { + fn as_any(&self) -> &dyn Any { + self + } + + fn bounds(&self) -> RectF { + RectF::new(Vector2F::zero(), 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 { 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(), @@ -242,41 +283,35 @@ 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 bounds(&self) -> WindowBounds { + WindowBounds::Fixed(RectF::new(Vector2F::zero(), self.size)) + } + + fn content_size(&self) -> Vector2F { + self.size + } + + fn scale_factor(&self) -> f32 { + self.scale_factor + } + + fn titlebar_height(&self) -> f32 { + 24. + } + + fn appearance(&self) -> crate::Appearance { + crate::Appearance::Light + } + + fn screen(&self) -> Rc { + Rc::new(Screen) + } + fn as_any_mut(&mut self) -> &mut dyn Any { self } - fn on_event(&mut self, callback: Box bool>) { - self.event_handlers.push(callback); - } - - fn on_active_status_change(&mut self, callback: Box) { - self.active_status_change_handlers.push(callback); - } - - fn on_fullscreen(&mut self, callback: Box) { - self.fullscreen_handlers.push(callback) - } - - fn on_resize(&mut self, callback: Box) { - self.resize_handlers.push(callback); - } - - fn on_close(&mut self, callback: Box) { - self.close_handlers.push(callback); - } - fn set_input_handler(&mut self, _: Box) {} fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver { @@ -295,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 toggle_full_screen(&self) {} - - fn bounds(&self) -> RectF { - RectF::new(Default::default(), self.size) - } - - fn content_size(&self) -> Vector2F { - self.size - } - - fn scale_factor(&self) -> f32 { - self.scale_factor - } - - fn titlebar_height(&self) -> f32 { - 24. - } - fn present_scene(&mut self, scene: crate::Scene) { self.current_scene = Some(scene); } - fn appearance(&self) -> crate::Appearance { - crate::Appearance::Light + fn toggle_full_screen(&self) {} + + fn on_event(&mut self, callback: Box bool>) { + self.event_handlers.push(callback); + } + + fn on_active_status_change(&mut self, callback: Box) { + self.active_status_change_handlers.push(callback); + } + + fn on_resize(&mut self, callback: Box) { + self.resize_handlers.push(callback); + } + + fn on_fullscreen(&mut self, callback: Box) { + self.fullscreen_handlers.push(callback) + } + + fn on_moved(&mut self, callback: Box) { + self.moved_handlers.push(callback); + } + + 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) {} @@ -337,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/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 499e0df93e..66c991f0a8 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, StaticColumnCount}, statement::Statement, }; use std::{ @@ -932,6 +932,7 @@ impl ToJson 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 8b2c12a59b..6e25222d96 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, StaticColumnCount}, statement::Statement, }; use std::{collections::HashMap, fmt::Write as _, num::NonZeroU32, str, sync::Arc}; @@ -253,6 +253,7 @@ pub enum DockAnchor { Expanded, } +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/Cargo.toml b/crates/sqlez/Cargo.toml index 8409a1dff5..716ec76644 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 62212d8f18..86d69afe5f 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -9,6 +9,12 @@ use anyhow::{Context, Result}; use crate::statement::{SqlType, Statement}; +pub trait StaticColumnCount { + fn column_count() -> usize { + 1 + } +} + pub trait Bind { fn bind(&self, statement: &Statement, start_index: i32) -> Result; } @@ -17,6 +23,7 @@ pub trait Column: Sized { fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)>; } +impl StaticColumnCount for bool {} impl Bind for bool { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -33,6 +40,7 @@ impl Column for bool { } } +impl StaticColumnCount for &[u8] {} impl Bind for &[u8] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -42,6 +50,7 @@ impl Bind for &[u8] { } } +impl StaticColumnCount for &[u8; C] {} impl Bind for &[u8; C] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -51,6 +60,15 @@ 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 { statement @@ -70,6 +88,7 @@ impl Column for Vec { } } +impl StaticColumnCount for f64 {} impl Bind for f64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -89,6 +108,7 @@ impl Column for f64 { } } +impl StaticColumnCount for f32 {} impl Bind for f32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -109,6 +129,7 @@ impl Column for f32 { } } +impl StaticColumnCount for i32 {} impl Bind for i32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -126,6 +147,7 @@ impl Column for i32 { } } +impl StaticColumnCount for i64 {} impl Bind for i64 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement @@ -142,6 +164,7 @@ impl Column for i64 { } } +impl StaticColumnCount for u32 {} impl Bind for u32 { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -157,6 +180,7 @@ impl Column for u32 { } } +impl StaticColumnCount for usize {} impl Bind for usize { fn bind(&self, statement: &Statement, start_index: i32) -> Result { (*self as i64) @@ -172,6 +196,7 @@ impl Column for usize { } } +impl StaticColumnCount for &str {} impl Bind for &str { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -179,6 +204,7 @@ impl Bind for &str { } } +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())?; @@ -186,6 +212,7 @@ impl Bind for Arc { } } +impl StaticColumnCount for String {} impl Bind for String { fn bind(&self, statement: &Statement, start_index: i32) -> Result { statement.bind_text(start_index, self)?; @@ -207,27 +234,40 @@ impl Column for String { } } -impl Bind for Option { - fn bind(&self, statement: &Statement, start_index: i32) -> Result { +impl StaticColumnCount for Option { + fn column_count() -> usize { + T::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::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::column_count() as i32)) } else { T::column(statement, start_index).map(|(result, next_index)| (Some(result), next_index)) } } } +impl StaticColumnCount for [T; COUNT] { + fn column_count() -> usize { + T::column_count() * COUNT + } +} impl Bind for [T; COUNT] { fn bind(&self, statement: &Statement, start_index: i32) -> Result { let mut current_index = start_index; @@ -239,51 +279,21 @@ 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 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 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 StaticColumnCount for Arc {} impl Bind for Arc { fn bind(&self, statement: &Statement, start_index: i32) -> Result { self.as_ref().bind(statement, start_index) } } +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) @@ -301,6 +311,30 @@ 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 + } +} /// Unit impls do nothing. This simplifies query macros impl Bind for () { fn bind(&self, _statement: &Statement, start_index: i32) -> Result { @@ -314,74 +348,79 @@ 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) +macro_rules! impl_tuple_row_traits { + ( $($local:ident: $type:ident),+ ) => { + impl<$($type: StaticColumnCount),+> StaticColumnCount for ($($type,)+) { + fn column_count() -> usize { + let mut count = 0; + $(count += $type::column_count();)+ + count + } + } + + 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<$($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) { - 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) - } -} - -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 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 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 f3ec6d4854..69d5685ba0 100644 --- a/crates/sqlez/src/statement.rs +++ b/crates/sqlez/src/statement.rs @@ -238,12 +238,11 @@ impl<'a> Statement<'a> { pub fn bind(&self, value: T, index: i32) -> Result { debug_assert!(index > 0); - value.bind(self, index) + Ok(value.bind(self, index)?) } pub fn column(&mut self) -> Result { - let (result, _) = T::column(self, 0)?; - Ok(result) + Ok(T::column(self, 0)?.0) } pub fn column_type(&mut self, index: i32) -> Result { diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 60680f82a2..fc069fe6c8 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 747541f87d..1702c6e521 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -534,6 +534,8 @@ mod tests { }], }, left_sidebar_open: 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 03a866f2f6..ddbea4c9f9 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; +use gpui::{Axis, WindowBounds}; use util::{unzip_option, ResultExt}; +use uuid::Uuid; use crate::dock::DockPosition; use crate::WorkspaceId; @@ -19,64 +20,118 @@ 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 + // 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( + // 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( - 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; + &[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 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 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 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; - )]; + 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 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; + )]; } impl WorkspaceDb { @@ -91,14 +146,27 @@ 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, bounds, display): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, - ) = - self.select_row_bound(sql!{ - SELECT workspace_id, workspace_location, left_sidebar_open, dock_visible, dock_anchor + Option, + Option, + ) = self + .select_row_bound(sql! { + SELECT + workspace_id, + workspace_location, + left_sidebar_open, + dock_visible, + dock_anchor, + window_state, + window_x, + window_y, + window_width, + window_height, + display FROM workspaces WHERE workspace_location = ? }) @@ -120,6 +188,8 @@ impl WorkspaceDb { .log_err()?, dock_position, left_sidebar_open, + bounds, + display, }) } @@ -142,22 +212,22 @@ impl WorkspaceDb { // 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, @@ -177,7 +247,7 @@ impl WorkspaceDb { 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")?; @@ -261,27 +331,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)| { @@ -319,9 +389,9 @@ impl WorkspaceDb { 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"))?; @@ -383,7 +453,7 @@ impl WorkspaceDb { Ok(self.select_bound(sql!( SELECT kind, item_id, active FROM items WHERE pane_id = ? - ORDER BY position + ORDER BY position ))?(pane_id)?) } @@ -410,6 +480,19 @@ impl WorkspaceDb { WHERE workspace_id = ? } } + + query! { + pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> { + UPDATE workspaces + SET window_state = ?2, + window_x = ?3, + window_y = ?4, + window_width = ?5, + window_height = ?6, + display = ?7 + WHERE workspace_id = ?1 + } + } } #[cfg(test)] @@ -436,7 +519,7 @@ mod tests { text TEXT, workspace_id INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE + ON DELETE CASCADE ) STRICT; )], ) @@ -485,7 +568,7 @@ mod tests { workspace_id INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id) - ON DELETE CASCADE + ON DELETE CASCADE ) STRICT;)], ) }) @@ -499,6 +582,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, + bounds: Default::default(), + display: Default::default(), }; let mut workspace_2 = SerializedWorkspace { @@ -508,6 +593,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + bounds: Default::default(), + display: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -614,6 +701,8 @@ mod tests { center_group, dock_pane, left_sidebar_open: true, + bounds: Default::default(), + display: Default::default(), }; db.save_workspace(workspace.clone()).await; @@ -642,6 +731,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, + bounds: Default::default(), + display: Default::default(), }; let mut workspace_2 = SerializedWorkspace { @@ -651,6 +742,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + bounds: Default::default(), + display: Default::default(), }; db.save_workspace(workspace_1.clone()).await; @@ -687,6 +780,8 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, + bounds: Default::default(), + display: Default::default(), }; db.save_workspace(workspace_3.clone()).await; @@ -722,6 +817,8 @@ mod tests { center_group: center_group.clone(), dock_pane, left_sidebar_open: true, + bounds: Default::default(), + display: Default::default(), } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index b264114fb6..507582b216 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -6,15 +6,16 @@ use std::{ use anyhow::{Context, Result}; use async_recursion::async_recursion; -use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle}; +use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds}; use db::sqlez::{ - bindable::{Bind, Column}, + bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; use project::Project; use settings::DockAnchor; use util::ResultExt; +use uuid::Uuid; use crate::{ dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, @@ -40,6 +41,7 @@ impl, T: IntoIterator> From for WorkspaceLocation { } } +impl StaticColumnCount for WorkspaceLocation {} impl Bind for &WorkspaceLocation { fn bind(&self, statement: &Statement, start_index: i32) -> Result { bincode::serialize(&self.0) @@ -58,7 +60,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 +68,8 @@ pub struct SerializedWorkspace { pub center_group: SerializedPaneGroup, pub dock_pane: SerializedPane, pub left_sidebar_open: bool, + pub bounds: Option, + pub display: Option, } #[derive(Debug, PartialEq, Eq, Clone)] @@ -237,6 +241,11 @@ impl Default for SerializedItem { } } +impl StaticColumnCount for SerializedItem { + fn 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 +270,11 @@ impl Column for SerializedItem { } } +impl StaticColumnCount for DockPosition { + fn 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 9c64eada5a..e5f22d1169 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, + MouseButton, MutableAppContext, PathPromptOptions, Platform, PromptLevel, RenderContext, + SizeConstraint, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -340,7 +340,8 @@ 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, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), pub dock_default_item_factory: DockDefaultItemFactory, } @@ -367,7 +368,7 @@ impl AppState { languages, user_store, initialize_workspace: |_, _, _| {}, - build_window_options: Default::default, + build_window_options: |_, _, _| Default::default(), dock_default_item_factory: |_, _| unimplemented!(), }) } @@ -682,18 +683,64 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; + let (bounds, display) = serialized_workspace + .as_ref() + .and_then(|sw| sw.bounds.zip(sw.display)) + .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((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)(bounds, display, cx.platform().as_ref()), + |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 |_, 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, bounds, display)) + .detach_and_log_err(cx); + }) + .detach(); + workspace + }, + ); notify_if_database_failed(&workspace, &mut cx); @@ -2345,6 +2392,8 @@ impl Workspace { dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), + bounds: Default::default(), + display: Default::default(), }; cx.background() diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c82e778d8d..7cb1158a06 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 793172a111..a8773d2c0b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,7 +20,7 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, PromptLevel, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, Platform, PromptLevel, TitlebarOptions, ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -33,6 +33,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,14 +370,22 @@ 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, + display: Option, + platform: &dyn Platform, +) -> 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); + + let screen = display.and_then(|display| platform.screen_by_id(display)); + WindowOptions { - bounds, titlebar: Some(TitlebarOptions { title: None, appears_transparent: true, @@ -386,7 +395,8 @@ pub fn build_window_options() -> WindowOptions<'static> { focus: true, kind: WindowKind::Normal, is_movable: true, - screen: None, + bounds, + screen, } }