diff --git a/Cargo.lock b/Cargo.lock index 1c74b49738e5703537ea5e43d0e0d067695b89c8..0a4801524eae5937fd476fd2cd6dac129cae228e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ dependencies = [ "alacritty_config", "alacritty_config_derive", "base64 0.13.1", - "bitflags 2.4.0", + "bitflags 2.4.1", "home", "libc", "log", @@ -200,7 +200,7 @@ dependencies = [ "toml 0.7.8", "unicode-width", "vte", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -293,7 +293,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -303,7 +303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -580,7 +580,7 @@ dependencies = [ "futures-lite", "rustix 0.37.23", "signal-hook", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1025,9 +1025,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -1949,6 +1949,7 @@ name = "collab_ui2" version = "0.1.0" dependencies = [ "anyhow", + "auto_update2", "call2", "channel2", "client2", @@ -1957,6 +1958,7 @@ dependencies = [ "db2", "editor2", "feature_flags2", + "feedback2", "futures 0.3.28", "fuzzy2", "gpui2", @@ -1978,6 +1980,7 @@ dependencies = [ "settings2", "smallvec", "theme2", + "theme_selector2", "time", "tree-sitter-markdown", "ui2", @@ -2085,6 +2088,19 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "const-cstr" version = "0.3.0" @@ -2572,7 +2588,7 @@ dependencies = [ "openssl-sys", "pkg-config", "vcpkg", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2781,6 +2797,20 @@ dependencies = [ "workspace2", ] +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "fuzzy-matcher", + "shell-words", + "tempfile", + "thiserror", + "zeroize", +] + [[package]] name = "diff" version = "0.1.13" @@ -3026,6 +3056,12 @@ dependencies = [ "serde", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -3104,7 +3140,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3135,7 +3171,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if 1.0.0", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3247,6 +3283,7 @@ name = "feedback2" version = "0.1.0" dependencies = [ "anyhow", + "bitflags 2.4.1", "client2", "db2", "editor2", @@ -3343,7 +3380,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.3.5", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3722,6 +3759,15 @@ dependencies = [ "util", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "fuzzy2" version = "0.1.0" @@ -3984,7 +4030,7 @@ dependencies = [ "async-task", "backtrace", "bindgen 0.65.1", - "bitflags 2.4.0", + "bitflags 2.4.1", "block", "cbindgen", "cocoa", @@ -4243,7 +4289,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4520,7 +4566,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.3", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4577,7 +4623,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.3", "rustix 0.38.14", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -5000,7 +5046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb" dependencies = [ "cfg-if 1.0.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -5493,7 +5539,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -6153,7 +6199,7 @@ version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if 1.0.0", "foreign-types", "libc", @@ -6674,7 +6720,7 @@ dependencies = [ "libc", "log", "pin-project-lite 0.2.13", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -7873,7 +7919,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -7980,7 +8026,7 @@ dependencies = [ "io-lifetimes 1.0.11", "libc", "linux-raw-sys 0.3.8", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -7989,11 +8035,11 @@ version = "0.38.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno 0.3.3", "libc", "linux-raw-sys 0.4.7", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -8106,7 +8152,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -8706,6 +8752,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shellexpand" version = "2.1.2" @@ -8905,7 +8957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -9086,7 +9138,7 @@ dependencies = [ "atoi", "base64 0.21.4", "bigdecimal", - "bitflags 2.4.0", + "bitflags 2.4.1", "byteorder", "bytes 1.5.0", "chrono", @@ -9133,7 +9185,7 @@ dependencies = [ "atoi", "base64 0.21.4", "bigdecimal", - "bitflags 2.4.0", + "bitflags 2.4.1", "byteorder", "chrono", "crc", @@ -9223,6 +9275,7 @@ dependencies = [ "backtrace-on-stack-overflow", "chrono", "clap 4.4.4", + "dialoguer", "editor2", "fuzzy2", "gpui2", @@ -9524,7 +9577,7 @@ dependencies = [ "fastrand 2.0.0", "redox_syscall 0.3.5", "rustix 0.38.14", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -9963,7 +10016,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.4", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -11593,6 +11646,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -11732,7 +11794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if 1.0.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/crates/auto_update2/src/auto_update.rs b/crates/auto_update2/src/auto_update.rs index 31e474242ac7fd9cf918a785b35620ae7764df0e..79269acc27e78164f11407ea80dfae8df8631554 100644 --- a/crates/auto_update2/src/auto_update.rs +++ b/crates/auto_update2/src/auto_update.rs @@ -6,15 +6,17 @@ use db::kvp::KEY_VALUE_STORE; use db::RELEASE_CHANNEL; use gpui::{ actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task, - ViewContext, VisualContext, + ViewContext, VisualContext, WindowContext, }; use isahc::AsyncBody; + use serde::Deserialize; use serde_derive::Serialize; use smol::io::AsyncReadExt; use settings::{Settings, SettingsStore}; use smol::{fs::File, process::Command}; + use std::{ffi::OsString, sync::Arc, time::Duration}; use update_notification::UpdateNotification; use util::channel::{AppCommitSha, ReleaseChannel}; @@ -24,16 +26,7 @@ use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); -//todo!(remove CheckThatAutoUpdaterWorks) -actions!( - auto_update, - [ - Check, - DismissErrorMessage, - ViewReleaseNotes, - CheckThatAutoUpdaterWorks - ] -); +actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes]); #[derive(Serialize)] struct UpdateRequestBody { @@ -90,7 +83,10 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo cx.observe_new_views(|workspace: &mut Workspace, _cx| { workspace.register_action(|_, action: &Check, cx| check(action, cx)); + workspace.register_action(|_, action, cx| view_release_notes(action, cx)); + // @nate - code to trigger update notification on launch + // todo!("remove this when Nate is done") // workspace.show_notification(0, _cx, |cx| { // cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap())) // }); @@ -119,13 +115,10 @@ pub fn init(http_client: Arc, server_url: String, cx: &mut AppCo updater }); cx.set_global(Some(auto_updater)); - //todo!(action) - // cx.add_global_action(view_release_notes); - // cx.add_action(UpdateNotification::dismiss); } } -pub fn check(_: &Check, cx: &mut ViewContext) { +pub fn check(_: &Check, cx: &mut WindowContext) { if let Some(updater) = AutoUpdater::get(cx) { updater.update(cx, |updater, cx| updater.poll(cx)); } else { diff --git a/crates/auto_update2/src/update_notification.rs b/crates/auto_update2/src/update_notification.rs index 4a2efcf8076bb882a67c1f25b6300dd4cd48c1d1..8bb08912b01a13712afe2dbc14e34a9abd49bc24 100644 --- a/crates/auto_update2/src/update_notification.rs +++ b/crates/auto_update2/src/update_notification.rs @@ -2,6 +2,7 @@ use gpui::{ div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render, SemanticVersion, StatefulInteractiveElement, Styled, ViewContext, }; +use menu::Cancel; use util::channel::ReleaseChannel; use workspace::ui::{h_stack, v_stack, Icon, IconElement, Label, StyledExt}; @@ -18,6 +19,7 @@ impl Render for UpdateNotification { let app_name = cx.global::().display_name(); v_stack() + .on_action(cx.listener(UpdateNotification::dismiss)) .elevation_3(cx) .p_4() .child( @@ -32,7 +34,7 @@ impl Render for UpdateNotification { .id("cancel") .child(IconElement::new(Icon::Close)) .cursor_pointer() - .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), + .on_click(cx.listener(|this, _, cx| this.dismiss(&menu::Cancel, cx))), ), ) .child( @@ -50,7 +52,7 @@ impl UpdateNotification { Self { version } } - pub fn dismiss(&mut self, cx: &mut ViewContext) { + pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext) { cx.emit(DismissEvent); } } diff --git a/crates/client2/src/client2.rs b/crates/client2/src/client2.rs index bf8d5dda771c3d9ddea8a3b8b3978318f051fdc3..2c9787298944cba3e49134ccb215768707f4c803 100644 --- a/crates/client2/src/client2.rs +++ b/crates/client2/src/client2.rs @@ -11,8 +11,8 @@ use async_tungstenite::tungstenite::{ http::{Request, StatusCode}, }; use futures::{ - future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, - TryStreamExt, + channel::oneshot, future::LocalBoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, + TryFutureExt as _, TryStreamExt, }; use gpui::{ actions, serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, @@ -1020,91 +1020,116 @@ impl Client { ) -> Task> { let http = self.http.clone(); cx.spawn(|cx| async move { - // Generate a pair of asymmetric encryption keys. The public key will be used by the - // zed server to encrypt the user's access token, so that it can'be intercepted by - // any other app running on the user's device. - let (public_key, private_key) = - rpc::auth::keypair().expect("failed to generate keypair for auth"); - let public_key_string = - String::try_from(public_key).expect("failed to serialize public key for auth"); - - if let Some((login, token)) = IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) { - return Self::authenticate_as_admin(http, login.clone(), token.clone()).await; - } - - // Start an HTTP server to receive the redirect from Zed's sign-in page. - let server = tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port"); - let port = server.server_addr().port(); + let background = cx.background_executor().clone(); - // Open the Zed sign-in page in the user's browser, with query parameters that indicate - // that the user is signing in from a Zed app running on the same device. - let mut url = format!( - "{}/native_app_signin?native_app_port={}&native_app_public_key={}", - *ZED_SERVER_URL, port, public_key_string - ); + let (open_url_tx, open_url_rx) = oneshot::channel::(); + cx.update(|cx| { + cx.spawn(move |cx| async move { + let url = open_url_rx.await?; + cx.update(|cx| cx.open_url(&url)) + }) + .detach_and_log_err(cx); + }) + .log_err(); + + let credentials = background + .clone() + .spawn(async move { + // Generate a pair of asymmetric encryption keys. The public key will be used by the + // zed server to encrypt the user's access token, so that it can'be intercepted by + // any other app running on the user's device. + let (public_key, private_key) = + rpc::auth::keypair().expect("failed to generate keypair for auth"); + let public_key_string = String::try_from(public_key) + .expect("failed to serialize public key for auth"); + + if let Some((login, token)) = + IMPERSONATE_LOGIN.as_ref().zip(ADMIN_API_TOKEN.as_ref()) + { + return Self::authenticate_as_admin(http, login.clone(), token.clone()) + .await; + } - if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() { - log::info!("impersonating user @{}", impersonate_login); - write!(&mut url, "&impersonate={}", impersonate_login).unwrap(); - } + // Start an HTTP server to receive the redirect from Zed's sign-in page. + let server = + tiny_http::Server::http("127.0.0.1:0").expect("failed to find open port"); + let port = server.server_addr().port(); + + // Open the Zed sign-in page in the user's browser, with query parameters that indicate + // that the user is signing in from a Zed app running on the same device. + let mut url = format!( + "{}/native_app_signin?native_app_port={}&native_app_public_key={}", + *ZED_SERVER_URL, port, public_key_string + ); + + if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() { + log::info!("impersonating user @{}", impersonate_login); + write!(&mut url, "&impersonate={}", impersonate_login).unwrap(); + } - cx.update(|cx| cx.open_url(&url))?; - - // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted - // access token from the query params. - // - // TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a - // custom URL scheme instead of this local HTTP server. - let (user_id, access_token) = cx - .spawn(|_| async move { - for _ in 0..100 { - if let Some(req) = server.recv_timeout(Duration::from_secs(1))? { - let path = req.url(); - let mut user_id = None; - let mut access_token = None; - let url = Url::parse(&format!("http://example.com{}", path)) - .context("failed to parse login notification url")?; - for (key, value) in url.query_pairs() { - if key == "access_token" { - access_token = Some(value.to_string()); - } else if key == "user_id" { - user_id = Some(value.to_string()); + open_url_tx.send(url).log_err(); + + // Receive the HTTP request from the user's browser. Retrieve the user id and encrypted + // access token from the query params. + // + // TODO - Avoid ever starting more than one HTTP server. Maybe switch to using a + // custom URL scheme instead of this local HTTP server. + let (user_id, access_token) = background + .spawn(async move { + for _ in 0..100 { + if let Some(req) = server.recv_timeout(Duration::from_secs(1))? { + let path = req.url(); + let mut user_id = None; + let mut access_token = None; + let url = Url::parse(&format!("http://example.com{}", path)) + .context("failed to parse login notification url")?; + for (key, value) in url.query_pairs() { + if key == "access_token" { + access_token = Some(value.to_string()); + } else if key == "user_id" { + user_id = Some(value.to_string()); + } + } + + let post_auth_url = + format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL); + req.respond( + tiny_http::Response::empty(302).with_header( + tiny_http::Header::from_bytes( + &b"Location"[..], + post_auth_url.as_bytes(), + ) + .unwrap(), + ), + ) + .context("failed to respond to login http request")?; + return Ok(( + user_id + .ok_or_else(|| anyhow!("missing user_id parameter"))?, + access_token.ok_or_else(|| { + anyhow!("missing access_token parameter") + })?, + )); } } - let post_auth_url = - format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL); - req.respond( - tiny_http::Response::empty(302).with_header( - tiny_http::Header::from_bytes( - &b"Location"[..], - post_auth_url.as_bytes(), - ) - .unwrap(), - ), - ) - .context("failed to respond to login http request")?; - return Ok(( - user_id.ok_or_else(|| anyhow!("missing user_id parameter"))?, - access_token - .ok_or_else(|| anyhow!("missing access_token parameter"))?, - )); - } - } + Err(anyhow!("didn't receive login redirect")) + }) + .await?; - Err(anyhow!("didn't receive login redirect")) + let access_token = private_key + .decrypt_string(&access_token) + .context("failed to decrypt access token")?; + + Ok(Credentials { + user_id: user_id.parse()?, + access_token, + }) }) .await?; - let access_token = private_key - .decrypt_string(&access_token) - .context("failed to decrypt access token")?; cx.update(|cx| cx.activate(true))?; - - Ok(Credentials { - user_id: user_id.parse()?, - access_token, - }) + Ok(credentials) }) } diff --git a/crates/collab_ui2/Cargo.toml b/crates/collab_ui2/Cargo.toml index 9d84d7f887c56b77df18c237d697be063f745091..da13da8306810b3bae744db12e9dcfeefbda4664 100644 --- a/crates/collab_ui2/Cargo.toml +++ b/crates/collab_ui2/Cargo.toml @@ -22,7 +22,7 @@ test-support = [ ] [dependencies] -# auto_update = { path = "../auto_update" } +auto_update = { package = "auto_update2", path = "../auto_update2" } db = { package = "db2", path = "../db2" } call = { package = "call2", path = "../call2" } client = { package = "client2", path = "../client2" } @@ -32,7 +32,7 @@ collections = { path = "../collections" } # context_menu = { path = "../context_menu" } # drag_and_drop = { path = "../drag_and_drop" } editor = { package="editor2", path = "../editor2" } -#feedback = { path = "../feedback" } +feedback = { package = "feedback2", path = "../feedback2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } @@ -46,7 +46,7 @@ rpc = { package ="rpc2", path = "../rpc2" } settings = { package = "settings2", path = "../settings2" } feature_flags = { package = "feature_flags2", path = "../feature_flags2"} theme = { package = "theme2", path = "../theme2" } -# theme_selector = { path = "../theme_selector" } +theme_selector = { package = "theme_selector2", path = "../theme_selector2" } vcs_menu = { package = "vcs_menu2", path = "../vcs_menu2" } ui = { package = "ui2", path = "../ui2" } util = { path = "../util" } diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 298c7682eb663bdd470e80826de26f30d83ed807..4edf5ef35ba4495d5f3ef4922414d0f79cd46d7c 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -2142,7 +2142,7 @@ impl CollabPanel { } fn render_signed_out(&mut self, cx: &mut ViewContext) -> Div { - v_stack().child( + v_stack().border_1().border_color(gpui::red()).child( Button::new("sign_in", "Sign in to collaborate").on_click(cx.listener( |this, _, cx| { let client = this.client.clone(); @@ -2301,9 +2301,14 @@ impl CollabPanel { .into_any_element() }), Section::Contacts => Some( - IconButton::new("add-contact", Icon::Plus) - .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) - .tooltip(|cx| Tooltip::text("Search for new contact", cx)) + div() + .border_1() + .border_color(gpui::red()) + .child( + IconButton::new("add-contact", Icon::Plus) + .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx))) + .tooltip(|cx| Tooltip::text("Search for new contact", cx)), + ) .into_any_element(), ), Section::Channels => Some( @@ -2323,7 +2328,7 @@ impl CollabPanel { | Section::Offline => true, }; - h_stack() + let mut row = h_stack() .w_full() .group("section-header") .child( @@ -2350,7 +2355,13 @@ impl CollabPanel { .detach_and_log_err(cx) }, )) - }) + }); + + if section == Section::Offline { + row = div().border_1().border_color(gpui::red()).child(row); + } + + row } fn render_contact( @@ -2552,12 +2563,11 @@ impl CollabPanel { .group("") .flex() .w_full() - .on_drag({ - let channel = channel.clone(); - move |cx| { - let channel = channel.clone(); - cx.build_view(|cx| DraggedChannelView { channel, width }) - } + .on_drag(channel.clone(), move |channel, cx| { + cx.build_view(|cx| DraggedChannelView { + channel: channel.clone(), + width, + }) }) .drag_over::(|style| { style.bg(cx.theme().colors().ghost_element_hover) diff --git a/crates/collab_ui2/src/collab_panel/contact_finder.rs b/crates/collab_ui2/src/collab_panel/contact_finder.rs index bd0c5d4b074beef1e8a433c03eec71df4a2d2c7c..3087e6812f2afe93e2ba11a4a9bd836af8ec899a 100644 --- a/crates/collab_ui2/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui2/src/collab_panel/contact_finder.rs @@ -11,14 +11,8 @@ use ui::{prelude::*, Avatar}; use util::{ResultExt as _, TryFutureExt}; use workspace::ModalView; -pub fn init(cx: &mut AppContext) { - //Picker::::init(cx); - //cx.add_action(ContactFinder::dismiss) -} - pub struct ContactFinder { picker: View>, - has_focus: bool, } impl ContactFinder { @@ -31,16 +25,12 @@ impl ContactFinder { }; let picker = cx.build_view(|cx| Picker::new(delegate, cx)); - Self { - picker, - has_focus: false, - } + Self { picker } } pub fn set_query(&mut self, query: String, cx: &mut ViewContext) { self.picker.update(cx, |picker, cx| { - // todo!() - // picker.set_query(query, cx); + picker.set_query(query, cx); }); } } @@ -62,32 +52,9 @@ impl Render for ContactFinder { .w(rems(34.)) } - // fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { - // self.has_focus = true; - // if cx.is_self_focused() { - // cx.focus(&self.picker) - // } - // } - - // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { - // self.has_focus = false; - // } - type Element = Div; } -// impl Modal for ContactFinder { -// fn has_focus(&self) -> bool { -// self.has_focus -// } - -// fn dismiss_on_event(event: &Self::Event) -> bool { -// match event { -// PickerEvent::Dismiss => true, -// } -// } -// } - pub struct ContactFinderDelegate { parent: WeakView, potential_contacts: Arc<[Arc]>, @@ -161,7 +128,6 @@ impl PickerDelegate for ContactFinderDelegate { } fn dismissed(&mut self, cx: &mut ViewContext>) { - //cx.emit(PickerEvent::Dismiss); self.parent .update(cx, |_, cx| cx.emit(DismissEvent)) .log_err(); @@ -191,6 +157,7 @@ impl PickerDelegate for ContactFinderDelegate { .child(Label::new(user.github_login.clone())) .children(icon_path.map(|icon_path| svg().path(icon_path))), ) + // todo!() // Flex::row() // .with_children(user.avatar.clone().map(|avatar| { // Image::from_data(avatar) diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 5b3d4c0942f30b33332f3be7913244eec8cd1dd9..b127708fa690dfc4976c8df246c9d3afa7b3f1a4 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -1,9 +1,10 @@ use crate::face_pile::FacePile; +use auto_update::AutoUpdateStatus; use call::{ActiveCall, ParticipantLocation, Room}; use client::{proto::PeerId, Client, ParticipantIndex, User, UserStore}; use gpui::{ - actions, canvas, div, overlay, point, px, rems, AnyElement, AppContext, DismissEvent, Div, - Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, + actions, canvas, div, overlay, point, px, rems, Action, AnyElement, AppContext, DismissEvent, + Div, Element, FocusableView, Hsla, InteractiveElement, IntoElement, Model, ParentElement, Path, Render, Stateful, StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowBounds, }; @@ -53,7 +54,6 @@ pub struct CollabTitlebarItem { workspace: WeakView, branch_popover: Option>, project_popover: Option, - //user_menu: ViewHandle, _subscriptions: Vec, } @@ -233,56 +233,17 @@ impl Render for CollabTitlebarItem { }), ) }) - .child(h_stack().px_1p5().map(|this| { - if let Some(user) = current_user { - // TODO: Finish implementing user menu popover - // - this.child( - popover_menu("user-menu") - .menu(|cx| { - ContextMenu::build(cx, |menu, _| menu.header("ADADA")) - }) - .trigger( - ButtonLike::new("user-menu") - .child( - h_stack() - .gap_0p5() - .child(Avatar::new(user.avatar_uri.clone())) - .child( - IconElement::new(Icon::ChevronDown) - .color(Color::Muted), - ), - ) - .style(ButtonStyle::Subtle) - .tooltip(move |cx| { - Tooltip::text("Toggle User Menu", cx) - }), - ) - .anchor(gpui::AnchorCorner::TopRight), - ) - // this.child( - // ButtonLike::new("user-menu") - // .child( - // h_stack().gap_0p5().child(Avatar::data(avatar)).child( - // IconElement::new(Icon::ChevronDown).color(Color::Muted), - // ), - // ) - // .style(ButtonStyle::Subtle) - // .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), - // ) + .map(|el| { + let status = self.client.status(); + let status = &*status.borrow(); + if matches!(status, client::Status::Connected { .. }) { + el.child(self.render_user_menu_button(cx)) } else { - this.child(Button::new("sign_in", "Sign in").on_click(move |_, cx| { - let client = client.clone(); - cx.spawn(move |mut cx| async move { - client - .authenticate_and_connect(true, &cx) - .await - .notify_async_err(&mut cx); - }) - .detach(); - })) + el.children(self.render_connection_status(status, cx)) + .child(self.render_sign_in_button(cx)) + .child(self.render_user_menu_button(cx)) } - })), + }), ) } } @@ -324,12 +285,6 @@ impl CollabTitlebarItem { project, user_store, client, - // user_menu: cx.add_view(|cx| { - // let view_id = cx.view_id(); - // let mut menu = ContextMenu::new(view_id, cx); - // menu.set_position_mode(OverlayPositionMode::Local); - // menu - // }), branch_popover: None, project_popover: None, _subscriptions: subscriptions, @@ -570,154 +525,113 @@ impl CollabTitlebarItem { cx.notify(); } - // fn render_user_menu_button( - // &self, - // theme: &Theme, - // avatar: Option>, - // cx: &mut ViewContext, - // ) -> AnyElement { - // let tooltip = theme.tooltip.clone(); - // let user_menu_button_style = if avatar.is_some() { - // &theme.titlebar.user_menu.user_menu_button_online - // } else { - // &theme.titlebar.user_menu.user_menu_button_offline - // }; - - // let avatar_style = &user_menu_button_style.avatar; - // Stack::new() - // .with_child( - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = user_menu_button_style - // .user_menu - // .inactive_state() - // .style_for(state); - - // let mut dropdown = Flex::row().align_children_center(); - - // if let Some(avatar_img) = avatar { - // dropdown = dropdown.with_child(Self::render_face( - // avatar_img, - // *avatar_style, - // Color::transparent_black(), - // None, - // )); - // }; - - // dropdown - // .with_child( - // Svg::new("icons/caret_down.svg") - // .with_color(user_menu_button_style.icon.color) - // .constrained() - // .with_width(user_menu_button_style.icon.width) - // .contained() - // .into_any(), - // ) - // .aligned() - // .constrained() - // .with_height(style.width) - // .contained() - // .with_style(style.container) - // .into_any() - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_down(MouseButton::Left, move |_, this, cx| { - // this.user_menu.update(cx, |menu, _| menu.delay_cancel()); - // }) - // .on_click(MouseButton::Left, move |_, this, cx| { - // this.toggle_user_menu(&Default::default(), cx) - // }) - // .with_tooltip::( - // 0, - // "Toggle User Menu".to_owned(), - // Some(Box::new(ToggleUserMenu)), - // tooltip, - // cx, - // ) - // .contained(), - // ) - // .with_child( - // ChildView::new(&self.user_menu, cx) - // .aligned() - // .bottom() - // .right(), - // ) - // .into_any() - // } - - // fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext) -> AnyElement { - // let titlebar = &theme.titlebar; - // MouseEventHandler::new::(0, cx, |state, _| { - // let style = titlebar.sign_in_button.inactive_state().style_for(state); - // Label::new("Sign In", style.text.clone()) - // .contained() - // .with_style(style.container) - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, move |_, this, cx| { - // let client = this.client.clone(); - // cx.app_context() - // .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await }) - // .detach_and_log_err(cx); - // }) - // .into_any() - // } - - // fn render_connection_status( - // &self, - // status: &client::Status, - // cx: &mut ViewContext, - // ) -> Option> { - // enum ConnectionStatusButton {} - - // let theme = &theme::current(cx).clone(); - // match status { - // client::Status::ConnectionError - // | client::Status::ConnectionLost - // | client::Status::Reauthenticating { .. } - // | client::Status::Reconnecting { .. } - // | client::Status::ReconnectionError { .. } => Some( - // Svg::new("icons/disconnected.svg") - // .with_color(theme.titlebar.offline_icon.color) - // .constrained() - // .with_width(theme.titlebar.offline_icon.width) - // .aligned() - // .contained() - // .with_style(theme.titlebar.offline_icon.container) - // .into_any(), - // ), - // client::Status::UpgradeRequired => { - // let auto_updater = auto_update::AutoUpdater::get(cx); - // let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) { - // Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate", - // Some(AutoUpdateStatus::Installing) - // | Some(AutoUpdateStatus::Downloading) - // | Some(AutoUpdateStatus::Checking) => "Updating...", - // Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => { - // "Please update Zed to Collaborate" - // } - // }; - - // Some( - // MouseEventHandler::new::(0, cx, |_, _| { - // Label::new(label, theme.titlebar.outdated_warning.text.clone()) - // .contained() - // .with_style(theme.titlebar.outdated_warning.container) - // .aligned() - // }) - // .with_cursor_style(CursorStyle::PointingHand) - // .on_click(MouseButton::Left, |_, _, cx| { - // if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { - // if auto_updater.read(cx).status() == AutoUpdateStatus::Updated { - // workspace::restart(&Default::default(), cx); - // return; - // } - // } - // auto_update::check(&Default::default(), cx); - // }) - // .into_any(), - // ) - // } - // _ => None, - // } - // } + fn render_connection_status( + &self, + status: &client::Status, + cx: &mut ViewContext, + ) -> Option { + match status { + client::Status::ConnectionError + | client::Status::ConnectionLost + | client::Status::Reauthenticating { .. } + | client::Status::Reconnecting { .. } + | client::Status::ReconnectionError { .. } => Some( + div() + .id("disconnected") + .bg(gpui::red()) // todo!() @nate + .child(IconElement::new(Icon::Disconnected)) + .tooltip(|cx| Tooltip::text("Disconnected", cx)) + .into_any_element(), + ), + client::Status::UpgradeRequired => { + let auto_updater = auto_update::AutoUpdater::get(cx); + let label = match auto_updater.map(|auto_update| auto_update.read(cx).status()) { + Some(AutoUpdateStatus::Updated) => "Please restart Zed to Collaborate", + Some(AutoUpdateStatus::Installing) + | Some(AutoUpdateStatus::Downloading) + | Some(AutoUpdateStatus::Checking) => "Updating...", + Some(AutoUpdateStatus::Idle) | Some(AutoUpdateStatus::Errored) | None => { + "Please update Zed to Collaborate" + } + }; + + Some( + div() + .bg(gpui::red()) // todo!() @nate + .child(Button::new("connection-status", label).on_click(|_, cx| { + if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { + if auto_updater.read(cx).status() == AutoUpdateStatus::Updated { + workspace::restart(&Default::default(), cx); + return; + } + } + auto_update::check(&Default::default(), cx); + })) + .into_any_element(), + ) + } + _ => None, + } + } + + pub fn render_sign_in_button(&mut self, _: &mut ViewContext) -> Button { + let client = self.client.clone(); + Button::new("sign_in", "Sign in").on_click(move |_, cx| { + let client = client.clone(); + cx.spawn(move |mut cx| async move { + client + .authenticate_and_connect(true, &cx) + .await + .notify_async_err(&mut cx); + }) + .detach(); + }) + } + + pub fn render_user_menu_button(&mut self, cx: &mut ViewContext) -> impl Element { + if let Some(user) = self.user_store.read(cx).current_user() { + popover_menu("user-menu") + .menu(|cx| { + ContextMenu::build(cx, |menu, _| { + menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) + .action("Theme", theme_selector::Toggle.boxed_clone()) + .separator() + .action("Share Feedback", feedback::GiveFeedback.boxed_clone()) + .action("Sign Out", client::SignOut.boxed_clone()) + }) + }) + .trigger( + ButtonLike::new("user-menu") + .child( + h_stack() + .gap_0p5() + .child(Avatar::new(user.avatar_uri.clone())) + .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + ) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), + ) + .anchor(gpui::AnchorCorner::TopRight) + } else { + popover_menu("user-menu") + .menu(|cx| { + ContextMenu::build(cx, |menu, _| { + menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) + .action("Theme", theme_selector::Toggle.boxed_clone()) + .separator() + .action("Share Feedback", feedback::GiveFeedback.boxed_clone()) + }) + }) + .trigger( + ButtonLike::new("user-menu") + .child( + h_stack() + .gap_0p5() + .child(IconElement::new(Icon::ChevronDown).color(Color::Muted)), + ) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Toggle User Menu", cx)), + ) + } + } } diff --git a/crates/collab_ui2/src/face_pile.rs b/crates/collab_ui2/src/face_pile.rs index d181509c46f1e568460b935f6d403dd6b15a1fff..fd675127e4a0786da76f3f5eb044d836652f7fab 100644 --- a/crates/collab_ui2/src/face_pile.rs +++ b/crates/collab_ui2/src/face_pile.rs @@ -17,7 +17,7 @@ impl RenderOnce for FacePile { let isnt_last = ix < player_count - 1; div() - .z_index((player_count - ix) as u32) + .z_index((player_count - ix) as u8) .when(isnt_last, |div| div.neg_mr_1()) .child(player) }); diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 89b5fd2efb91ff43f4683b0923f5658888ecc72a..664d1d7380634426aa4a8264c4ed66aef008c265 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -9297,7 +9297,7 @@ impl Render for Editor { let settings = ThemeSettings::get_global(cx); let text_style = match self.mode { EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle { - color: cx.theme().colors().text, + color: cx.theme().colors().editor_foreground, font_family: settings.ui_font.family.clone(), font_features: settings.ui_font.features, font_size: rems(0.875).into(), @@ -9310,7 +9310,7 @@ impl Render for Editor { }, EditorMode::Full => TextStyle { - color: cx.theme().colors().text, + color: cx.theme().colors().editor_foreground, font_family: settings.buffer_font.family.clone(), font_features: settings.buffer_font.features, font_size: settings.buffer_font_size(cx).into(), @@ -9739,12 +9739,8 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend }; highlighted_lines.push(line); } - let message = diagnostic.message; Arc::new(move |cx: &mut BlockContext| { - let message = message.clone(); let copy_id: SharedString = format!("copy-{}", cx.block_id.clone()).to_string().into(); - let write_to_clipboard = cx.write_to_clipboard(ClipboardItem::new(message.clone())); - // TODO: Nate: We should tint the background of the block with the severity color // We need to extend the theme before we can do this v_stack() @@ -9754,7 +9750,6 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend .bg(gpui::red()) .children(highlighted_lines.iter().map(|(line, highlights)| { let group_id = cx.block_id.to_string(); - h_stack() .group(group_id.clone()) .gap_2() @@ -9769,7 +9764,12 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend .size(ButtonSize::Compact) .style(ButtonStyle::Transparent) .visible_on_hover(group_id) - .on_click(cx.listener(move |_, _, cx| write_to_clipboard)) + .on_click(cx.listener({ + let message = diagnostic.message.clone(); + move |_, _, cx| { + cx.write_to_clipboard(ClipboardItem::new(message.clone())) + } + })) .tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)), ), ) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 2b6db125daed950c21536d876d0b22add51a5ec0..a6d4bc20b157d4f80a816d5dc78eb7980277cdc2 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -933,7 +933,7 @@ impl EditorElement { cx.stop_propagation(); }, )) - .draw( + .draw_and_update_state( fold_bounds.origin, fold_bounds.size, cx, @@ -1094,121 +1094,117 @@ impl EditorElement { cursor.paint(content_origin, cx); } }); + }, + ) + } - cx.with_z_index(1, |cx| { - if let Some((position, mut context_menu)) = layout.context_menu.take() { - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - let context_menu_size = context_menu.measure(available_space, cx); + fn paint_overlays( + &mut self, + text_bounds: Bounds, + layout: &mut LayoutState, + cx: &mut WindowContext, + ) { + let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); + let start_row = layout.visible_display_row_range.start; + if let Some((position, mut context_menu)) = layout.context_menu.take() { + let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); + let context_menu_size = context_menu.measure(available_space, cx); + + let cursor_row_layout = + &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; + let x = cursor_row_layout.x_for_index(position.column() as usize) + - layout.position_map.scroll_position.x; + let y = (position.row() + 1) as f32 * layout.position_map.line_height + - layout.position_map.scroll_position.y; + let mut list_origin = content_origin + point(x, y); + let list_width = context_menu_size.width; + let list_height = context_menu_size.height; + + // Snap the right edge of the list to the right edge of the window if + // its horizontal bounds overflow. + if list_origin.x + list_width > cx.viewport_size().width { + list_origin.x = (cx.viewport_size().width - list_width).max(Pixels::ZERO); + } - let cursor_row_layout = &layout.position_map.line_layouts - [(position.row() - start_row) as usize] - .line; - let x = cursor_row_layout.x_for_index(position.column() as usize) - - layout.position_map.scroll_position.x; - let y = (position.row() + 1) as f32 * layout.position_map.line_height - - layout.position_map.scroll_position.y; - let mut list_origin = content_origin + point(x, y); - let list_width = context_menu_size.width; - let list_height = context_menu_size.height; - - // Snap the right edge of the list to the right edge of the window if - // its horizontal bounds overflow. - if list_origin.x + list_width > cx.viewport_size().width { - list_origin.x = - (cx.viewport_size().width - list_width).max(Pixels::ZERO); - } + if list_origin.y + list_height > text_bounds.lower_right().y { + list_origin.y -= layout.position_map.line_height + list_height; + } - if list_origin.y + list_height > text_bounds.lower_right().y { - list_origin.y -= layout.position_map.line_height + list_height; - } + cx.break_content_mask(|cx| context_menu.draw(list_origin, available_space, cx)); + } - cx.break_content_mask(|cx| { - context_menu.draw(list_origin, available_space, cx) - }); + if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { + let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); + + // This is safe because we check on layout whether the required row is available + let hovered_row_layout = + &layout.position_map.line_layouts[(position.row() - start_row) as usize].line; + + // Minimum required size: Take the first popover, and add 1.5 times the minimum popover + // height. This is the size we will use to decide whether to render popovers above or below + // the hovered line. + let first_size = hover_popovers[0].measure(available_space, cx); + let height_to_reserve = + first_size.height + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height; + + // Compute Hovered Point + let x = hovered_row_layout.x_for_index(position.column() as usize) + - layout.position_map.scroll_position.x; + let y = position.row() as f32 * layout.position_map.line_height + - layout.position_map.scroll_position.y; + let hovered_point = content_origin + point(x, y); + + if hovered_point.y - height_to_reserve > Pixels::ZERO { + // There is enough space above. Render popovers above the hovered point + let mut current_y = hovered_point.y; + for mut hover_popover in hover_popovers { + let size = hover_popover.measure(available_space, cx); + let mut popover_origin = point(hovered_point.x, current_y - size.height); + + let x_out_of_bounds = + text_bounds.upper_right().x - (popover_origin.x + size.width); + if x_out_of_bounds < Pixels::ZERO { + popover_origin.x = popover_origin.x + x_out_of_bounds; } - if let Some((position, mut hover_popovers)) = layout.hover_popovers.take() { - let available_space = - size(AvailableSpace::MinContent, AvailableSpace::MinContent); - - // This is safe because we check on layout whether the required row is available - let hovered_row_layout = &layout.position_map.line_layouts - [(position.row() - start_row) as usize] - .line; - - // Minimum required size: Take the first popover, and add 1.5 times the minimum popover - // height. This is the size we will use to decide whether to render popovers above or below - // the hovered line. - let first_size = hover_popovers[0].measure(available_space, cx); - let height_to_reserve = first_size.height - + 1.5 * MIN_POPOVER_LINE_HEIGHT * layout.position_map.line_height; - - // Compute Hovered Point - let x = hovered_row_layout.x_for_index(position.column() as usize) - - layout.position_map.scroll_position.x; - let y = position.row() as f32 * layout.position_map.line_height - - layout.position_map.scroll_position.y; - let hovered_point = content_origin + point(x, y); - - if hovered_point.y - height_to_reserve > Pixels::ZERO { - // There is enough space above. Render popovers above the hovered point - let mut current_y = hovered_point.y; - for mut hover_popover in hover_popovers { - let size = hover_popover.measure(available_space, cx); - let mut popover_origin = - point(hovered_point.x, current_y - size.height); - - let x_out_of_bounds = - text_bounds.upper_right().x - (popover_origin.x + size.width); - if x_out_of_bounds < Pixels::ZERO { - popover_origin.x = popover_origin.x + x_out_of_bounds; - } - - cx.break_content_mask(|cx| { - hover_popover.draw(popover_origin, available_space, cx) - }); + cx.break_content_mask(|cx| { + hover_popover.draw(popover_origin, available_space, cx) + }); - current_y = popover_origin.y - HOVER_POPOVER_GAP; - } - } else { - // There is not enough space above. Render popovers below the hovered point - let mut current_y = hovered_point.y + layout.position_map.line_height; - for mut hover_popover in hover_popovers { - let size = hover_popover.measure(available_space, cx); - let mut popover_origin = point(hovered_point.x, current_y); - - let x_out_of_bounds = - text_bounds.upper_right().x - (popover_origin.x + size.width); - if x_out_of_bounds < Pixels::ZERO { - popover_origin.x = popover_origin.x + x_out_of_bounds; - } + current_y = popover_origin.y - HOVER_POPOVER_GAP; + } + } else { + // There is not enough space above. Render popovers below the hovered point + let mut current_y = hovered_point.y + layout.position_map.line_height; + for mut hover_popover in hover_popovers { + let size = hover_popover.measure(available_space, cx); + let mut popover_origin = point(hovered_point.x, current_y); + + let x_out_of_bounds = + text_bounds.upper_right().x - (popover_origin.x + size.width); + if x_out_of_bounds < Pixels::ZERO { + popover_origin.x = popover_origin.x + x_out_of_bounds; + } - hover_popover.draw(popover_origin, available_space, cx); + hover_popover.draw(popover_origin, available_space, cx); - current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; - } - } - } + current_y = popover_origin.y + size.height + HOVER_POPOVER_GAP; + } + } + } - if let Some(mouse_context_menu) = - self.editor.read(cx).mouse_context_menu.as_ref() - { - let element = overlay() - .position(mouse_context_menu.position) - .child(mouse_context_menu.context_menu.clone()) - .anchor(AnchorCorner::TopLeft) - .snap_to_window(); - element.draw( - gpui::Point::default(), - size(AvailableSpace::MinContent, AvailableSpace::MinContent), - cx, - |_, _| {}, - ); - } - }) - }, - ) + if let Some(mouse_context_menu) = self.editor.read(cx).mouse_context_menu.as_ref() { + let element = overlay() + .position(mouse_context_menu.position) + .child(mouse_context_menu.context_menu.clone()) + .anchor(AnchorCorner::TopLeft) + .snap_to_window(); + element.into_any().draw( + gpui::Point::default(), + size(AvailableSpace::MinContent, AvailableSpace::MinContent), + cx, + ); + } } fn scrollbar_left(&self, bounds: &Bounds) -> Pixels { @@ -1499,7 +1495,7 @@ impl EditorElement { let scroll_left = scroll_position.x * layout.position_map.em_width; let scroll_top = scroll_position.y * layout.position_map.line_height; - for block in layout.blocks.drain(..) { + for mut block in layout.blocks.drain(..) { let mut origin = bounds.origin + point( Pixels::ZERO, @@ -2288,8 +2284,8 @@ impl EditorElement { .cursor_pointer() .hover(|style| style.bg(cx.theme().colors().element_hover)) .on_click(cx.listener(|_editor, _event, _cx| { - // TODO: Implement collapsing path headers - todo!("Clicking path header") + // todo!() Implement collapsing path headers + // todo!("Clicking path header") })) .child( h_stack() @@ -2784,7 +2780,7 @@ impl Element for EditorElement { } fn paint( - mut self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut gpui::WindowContext, @@ -2807,32 +2803,30 @@ impl Element for EditorElement { self.register_actions(cx); self.register_key_listeners(cx); - // We call with_z_index to establish a new stacking context. - cx.with_z_index(0, |cx| { - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - // Paint mouse listeners at z-index 0 so any elements we paint on top of the editor - // take precedence. - cx.with_z_index(0, |cx| { - self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); - }); - let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); - cx.handle_input(&focus_handle, input_handler); + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + let input_handler = ElementInputHandler::new(bounds, self.editor.clone(), cx); + cx.handle_input(&focus_handle, input_handler); - self.paint_background(gutter_bounds, text_bounds, &layout, cx); - if layout.gutter_size.width > Pixels::ZERO { - self.paint_gutter(gutter_bounds, &mut layout, cx); - } - self.paint_text(text_bounds, &mut layout, cx); + self.paint_background(gutter_bounds, text_bounds, &layout, cx); + if layout.gutter_size.width > Pixels::ZERO { + self.paint_gutter(gutter_bounds, &mut layout, cx); + } + self.paint_text(text_bounds, &mut layout, cx); + + cx.with_z_index(0, |cx| { + self.paint_mouse_listeners(bounds, gutter_bounds, text_bounds, &layout, cx); if !layout.blocks.is_empty() { - cx.with_z_index(1, |cx| { - cx.with_element_id(Some("editor_blocks"), |cx| { - self.paint_blocks(bounds, &mut layout, cx); - }); - }) + cx.with_element_id(Some("editor_blocks"), |cx| { + self.paint_blocks(bounds, &mut layout, cx); + }); } + }); + + cx.with_z_index(1, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); - cx.with_z_index(2, |cx| self.paint_scrollbar(bounds, &mut layout, cx)); + cx.with_z_index(2, |cx| { + self.paint_overlays(text_bounds, &mut layout, cx); }); }); }) diff --git a/crates/feedback2/Cargo.toml b/crates/feedback2/Cargo.toml index 0e34ee410b5aa26c1b0417b739c928c464447312..9fe125ec57e53f10209f75072c98ada88fe25947 100644 --- a/crates/feedback2/Cargo.toml +++ b/crates/feedback2/Cargo.toml @@ -18,7 +18,6 @@ gpui = { package = "gpui2", path = "../gpui2" } language = { package = "language2", path = "../language2" } menu = { package = "menu2", path = "../menu2" } project = { package = "project2", path = "../project2" } -regex.workspace = true search = { package = "search2", path = "../search2" } settings = { package = "settings2", path = "../settings2" } theme = { package = "theme2", path = "../theme2" } @@ -26,13 +25,16 @@ ui = { package = "ui2", path = "../ui2" } util = { path = "../util" } workspace = { package = "workspace2", path = "../workspace2"} +bitflags = "2.4.1" +human_bytes = "0.4.1" + anyhow.workspace = true futures.workspace = true -human_bytes = "0.4.1" isahc.workspace = true lazy_static.workspace = true log.workspace = true postage.workspace = true +regex.workspace = true serde.workspace = true serde_derive.workspace = true smallvec.workspace = true diff --git a/crates/feedback2/src/feedback_modal.rs b/crates/feedback2/src/feedback_modal.rs index 5f9b6c4120b313dbc930d9b023130091a652d797..22904f3a0a2c14857e13bde40c2049e1a0982988 100644 --- a/crates/feedback2/src/feedback_modal.rs +++ b/crates/feedback2/src/feedback_modal.rs @@ -1,6 +1,7 @@ use std::{ops::RangeInclusive, sync::Arc, time::Duration}; use anyhow::{anyhow, bail}; +use bitflags::bitflags; use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorEvent}; @@ -48,19 +49,33 @@ struct FeedbackRequestBody<'a> { token: &'a str, } -#[derive(PartialEq)] -enum FeedbackModalState { +bitflags! { + #[derive(Debug, Clone, PartialEq)] + struct InvalidStateFlags: u8 { + const EmailAddress = 0b00000001; + const CharacterCount = 0b00000010; + } +} + +#[derive(Debug, Clone, PartialEq)] +enum CannotSubmitReason { + InvalidState { flags: InvalidStateFlags }, + AwaitingSubmission, +} + +#[derive(Debug, Clone, PartialEq)] +enum SubmissionState { CanSubmit, - DismissModal, - AwaitingSubmissionResponse, + CannotSubmit { reason: CannotSubmitReason }, } pub struct FeedbackModal { system_specs: SystemSpecs, feedback_editor: View, email_address_editor: View, + submission_state: Option, + dismiss_modal: bool, character_count: i32, - state: FeedbackModalState, } impl FocusableView for FeedbackModal { @@ -72,7 +87,7 @@ impl EventEmitter for FeedbackModal {} impl ModalView for FeedbackModal { fn on_before_dismiss(&mut self, cx: &mut ViewContext) -> bool { - if self.state == FeedbackModalState::DismissModal { + if self.dismiss_modal { return true; } @@ -86,7 +101,7 @@ impl ModalView for FeedbackModal { cx.spawn(move |this, mut cx| async move { if answer.await.ok() == Some(0) { this.update(&mut cx, |this, cx| { - this.state = FeedbackModalState::DismissModal; + this.dismiss_modal = true; cx.emit(DismissEvent) }) .log_err(); @@ -179,7 +194,8 @@ impl FeedbackModal { system_specs: system_specs.clone(), feedback_editor, email_address_editor, - state: FeedbackModalState::CanSubmit, + submission_state: None, + dismiss_modal: false, character_count: 0, } } @@ -214,7 +230,9 @@ impl FeedbackModal { }; this.update(&mut cx, |this, cx| { - this.state = FeedbackModalState::AwaitingSubmissionResponse; + this.submission_state = Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::AwaitingSubmission, + }); cx.notify(); }) .log_err(); @@ -225,7 +243,7 @@ impl FeedbackModal { match res { Ok(_) => { this.update(&mut cx, |this, cx| { - this.state = FeedbackModalState::DismissModal; + this.dismiss_modal = true; cx.notify(); cx.emit(DismissEvent) }) @@ -244,7 +262,7 @@ impl FeedbackModal { }) .detach(); - this.state = FeedbackModalState::CanSubmit; + this.submission_state = Some(SubmissionState::CanSubmit); cx.notify(); }) .log_err(); @@ -302,29 +320,79 @@ impl FeedbackModal { Ok(()) } - fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { - cx.emit(DismissEvent) - } -} + fn update_submission_state(&mut self, cx: &mut ViewContext) { + if self.awaiting_submission() { + return; + } -impl Render for FeedbackModal { - type Element = Div; + let mut invalid_state_flags = InvalidStateFlags::empty(); - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let valid_email_address = match self.email_address_editor.read(cx).text_option(cx) { Some(email_address) => Regex::new(EMAIL_REGEX).unwrap().is_match(&email_address), None => true, }; - let valid_character_count = FEEDBACK_CHAR_LIMIT.contains(&self.character_count); + if !valid_email_address { + invalid_state_flags |= InvalidStateFlags::EmailAddress; + } + + if !FEEDBACK_CHAR_LIMIT.contains(&self.character_count) { + invalid_state_flags |= InvalidStateFlags::CharacterCount; + } + + if invalid_state_flags.is_empty() { + self.submission_state = Some(SubmissionState::CanSubmit); + } else { + self.submission_state = Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::InvalidState { + flags: invalid_state_flags, + }, + }); + } + } + + fn valid_email_address(&self) -> bool { + !self.in_invalid_state(InvalidStateFlags::EmailAddress) + } + + fn valid_character_count(&self) -> bool { + !self.in_invalid_state(InvalidStateFlags::CharacterCount) + } + + fn in_invalid_state(&self, flag: InvalidStateFlags) -> bool { + match self.submission_state { + Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::InvalidState { ref flags }, + }) => flags.contains(flag), + _ => false, + } + } - let awaiting_submission_response = - self.state == FeedbackModalState::AwaitingSubmissionResponse; + fn awaiting_submission(&self) -> bool { + matches!( + self.submission_state, + Some(SubmissionState::CannotSubmit { + reason: CannotSubmitReason::AwaitingSubmission + }) + ) + } - let allow_submission = - valid_character_count && valid_email_address && !awaiting_submission_response; + fn can_submit(&self) -> bool { + matches!(self.submission_state, Some(SubmissionState::CanSubmit)) + } - let submit_button_text = if awaiting_submission_response { + fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext) { + cx.emit(DismissEvent) + } +} + +impl Render for FeedbackModal { + type Element = Div; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + self.update_submission_state(cx); + + let submit_button_text = if self.awaiting_submission() { "Submitting..." } else { "Submit" @@ -362,7 +430,7 @@ impl Render for FeedbackModal { *FEEDBACK_CHAR_LIMIT.end() - self.character_count ) }) - .color(if valid_character_count { + .color(if self.valid_character_count() { Color::Success } else { Color::Error @@ -386,7 +454,7 @@ impl Render for FeedbackModal { .p_2() .border() .rounded_md() - .border_color(if valid_email_address { + .border_color(if self.valid_email_address() { cx.theme().colors().border } else { red() @@ -423,7 +491,7 @@ impl Render for FeedbackModal { })), ) .child( - Button::new("send_feedback", submit_button_text) + Button::new("submit_feedback", submit_button_text) .color(Color::Accent) .style(ButtonStyle::Filled) .on_click(cx.listener(|this, _, cx| { @@ -437,7 +505,7 @@ impl Render for FeedbackModal { cx, ) }) - .when(!allow_submission, |this| this.disabled(true)), + .when(!self.can_submit(), |this| this.disabled(true)), ), ), ), @@ -447,3 +515,42 @@ impl Render for FeedbackModal { // TODO: Maybe store email address whenever the modal is closed, versus just on submit, so users can remove it if they want without submitting // TODO: Testing of various button states, dismissal prompts, etc. + +// #[cfg(test)] +// mod test { +// use super::*; + +// #[test] +// fn test_invalid_email_addresses() { +// let markdown = markdown.await.log_err(); +// let buffer = project.update(&mut cx, |project, cx| { +// project.create_buffer("", markdown, cx) +// })??; + +// workspace.update(&mut cx, |workspace, cx| { +// let system_specs = SystemSpecs::new(cx); + +// workspace.toggle_modal(cx, move |cx| { +// let feedback_modal = FeedbackModal::new(system_specs, project, buffer, cx); + +// assert!(!feedback_modal.can_submit()); +// assert!(!feedback_modal.valid_email_address(cx)); +// assert!(!feedback_modal.valid_character_count()); + +// feedback_modal +// .email_address_editor +// .update(cx, |this, cx| this.set_text("a", cx)); +// feedback_modal.set_submission_state(cx); + +// assert!(!feedback_modal.valid_email_address(cx)); + +// feedback_modal +// .email_address_editor +// .update(cx, |this, cx| this.set_text("a&b.com", cx)); +// feedback_modal.set_submission_state(cx); + +// assert!(feedback_modal.valid_email_address(cx)); +// }); +// })?; +// } +// } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 18f688f179e34c6a272351a7bcf7cae9e5dc7bd3..8748a6024906307c9f8b0d581a390c8e9a3c179d 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -17,11 +17,10 @@ use time::UtcOffset; use crate::{ current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, - DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, - ForegroundExecutor, KeyBinding, Keymap, Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, - Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, - SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, - WindowContext, WindowHandle, WindowId, + DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap, + Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, + SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, + TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{FxHashMap, FxHashSet, VecDeque}; @@ -572,28 +571,27 @@ impl AppContext { loop { self.release_dropped_entities(); self.release_dropped_focus_handles(); + if let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify { emitter } => { self.apply_notify_effect(emitter); } + Effect::Emit { emitter, event_type, event, } => self.apply_emit_effect(emitter, event_type, event), - Effect::FocusChanged { - window_handle, - focused, - } => { - self.apply_focus_changed_effect(window_handle, focused); - } + Effect::Refresh => { self.apply_refresh_effect(); } + Effect::NotifyGlobalObservers { global_type } => { self.apply_notify_global_observers_effect(global_type); } + Effect::Defer { callback } => { self.apply_defer_effect(callback); } @@ -613,7 +611,7 @@ impl AppContext { .values() .filter_map(|window| { let window = window.as_ref()?; - window.dirty.then_some(window.handle) + (window.dirty || window.focus_invalidated).then_some(window.handle) }) .collect::>() { @@ -693,51 +691,6 @@ impl AppContext { }); } - fn apply_focus_changed_effect( - &mut self, - window_handle: AnyWindowHandle, - focused: Option, - ) { - window_handle - .update(self, |_, cx| { - // The window might change focus multiple times in an effect cycle. - // We only honor effects for the most recently focused handle. - if cx.window.focus == focused { - // if someone calls focus multiple times in one frame with the same handle - // the first apply_focus_changed_effect will have taken the last blur already - // and run the rest of this, so we can return. - let Some(last_blur) = cx.window.last_blur.take() else { - return; - }; - - let focused = focused - .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); - - let blurred = - last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); - - let focus_changed = focused.is_some() || blurred.is_some(); - let event = FocusEvent { focused, blurred }; - - let mut listeners = mem::take(&mut cx.window.rendered_frame.focus_listeners); - if focus_changed { - for listener in &mut listeners { - listener(&event, cx); - } - } - cx.window.rendered_frame.focus_listeners = listeners; - - if focus_changed { - cx.window - .focus_listeners - .clone() - .retain(&(), |listener| listener(&event, cx)); - } - } - }) - .ok(); - } - fn apply_refresh_effect(&mut self) { for window in self.windows.values_mut() { if let Some(window) = window.as_mut() { @@ -1139,8 +1092,10 @@ impl AppContext { self.active_drag.is_some() } - pub fn active_drag(&self) -> Option { - self.active_drag.as_ref().map(|drag| drag.view.clone()) + pub fn active_drag(&self) -> Option<&T> { + self.active_drag + .as_ref() + .and_then(|drag| drag.value.downcast_ref()) } } @@ -1250,10 +1205,6 @@ pub(crate) enum Effect { event_type: TypeId, event: Box, }, - FocusChanged { - window_handle: AnyWindowHandle, - focused: Option, - }, Refresh, NotifyGlobalObservers { global_type: TypeId, @@ -1296,6 +1247,7 @@ impl DerefMut for GlobalLease { /// within the window or by dragging into the app from the underlying platform. pub struct AnyDrag { pub view: AnyView, + pub value: Box, pub cursor_offset: Point, } diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index e5ecd195baa14694bc65a15e9102d5cbd56be10a..b446c2fe86eb4bd4ce12ddc183d55f58262aeaa9 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -23,7 +23,7 @@ pub trait IntoElement: Sized { self.into_element().into_any() } - fn draw( + fn draw_and_update_state( self, origin: Point, available_space: Size, @@ -92,7 +92,7 @@ pub trait Element: 'static + IntoElement { cx: &mut WindowContext, ) -> (LayoutId, Self::State); - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext); + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext); fn into_any(self) -> AnyElement { AnyElement::new(self) @@ -150,8 +150,8 @@ impl Element for Component { } } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - let element = state.rendered_element.take().unwrap(); + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + let mut element = state.rendered_element.take().unwrap(); if let Some(element_id) = element.element_id() { cx.with_element_state(element_id, |element_state, cx| { let mut element_state = element_state.unwrap(); @@ -420,7 +420,7 @@ impl AnyElement { self.0.layout(cx) } - pub fn paint(mut self, cx: &mut WindowContext) { + pub fn paint(&mut self, cx: &mut WindowContext) { self.0.paint(cx) } @@ -435,7 +435,7 @@ impl AnyElement { /// Initializes this element and performs layout in the available space, then paints it at the given origin. pub fn draw( - mut self, + &mut self, origin: Point, available_space: Size, cx: &mut WindowContext, @@ -465,8 +465,8 @@ impl Element for AnyElement { (layout_id, ()) } - fn paint(self, _: Bounds, _: &mut Self::State, cx: &mut WindowContext) { - self.paint(cx); + fn paint(&mut self, _: Bounds, _: &mut Self::State, cx: &mut WindowContext) { + self.paint(cx) } } @@ -508,5 +508,11 @@ impl Element for () { (cx.request_layout(&crate::Style::default(), None), ()) } - fn paint(self, _bounds: Bounds, _state: &mut Self::State, _cx: &mut WindowContext) {} + fn paint( + &mut self, + _bounds: Bounds, + _state: &mut Self::State, + _cx: &mut WindowContext, + ) { + } } diff --git a/crates/gpui2/src/elements/canvas.rs b/crates/gpui2/src/elements/canvas.rs index b3afd335d41d4544267a9453e8ffedea5c990b18..d04c65811fffeec45bc24b72c3d08a401c48eb52 100644 --- a/crates/gpui2/src/elements/canvas.rs +++ b/crates/gpui2/src/elements/canvas.rs @@ -4,13 +4,13 @@ use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled pub fn canvas(callback: impl 'static + FnOnce(&Bounds, &mut WindowContext)) -> Canvas { Canvas { - paint_callback: Box::new(callback), + paint_callback: Some(Box::new(callback)), style: StyleRefinement::default(), } } pub struct Canvas { - paint_callback: Box, &mut WindowContext)>, + paint_callback: Option, &mut WindowContext)>>, style: StyleRefinement, } @@ -27,7 +27,7 @@ impl IntoElement for Canvas { } impl Element for Canvas { - type State = (); + type State = Style; fn layout( &mut self, @@ -37,11 +37,13 @@ impl Element for Canvas { let mut style = Style::default(); style.refine(&self.style); let layout_id = cx.request_layout(&style, []); - (layout_id, ()) + (layout_id, style) } - fn paint(self, bounds: Bounds, _: &mut (), cx: &mut WindowContext) { - (self.paint_callback)(&bounds, cx) + fn paint(&mut self, bounds: Bounds, style: &mut Style, cx: &mut WindowContext) { + style.paint(bounds, cx, |cx| { + (self.paint_callback.take().unwrap())(&bounds, cx) + }); } } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 1954e3086c6b8f2c2b7bd3ea705a76e33c895d6f..1f56f44900477780085b556f46adc508f4ff119a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,9 @@ use crate::{ point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, - IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, - SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, - WindowContext, + BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement, + KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, + StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, }; use collections::HashMap; @@ -15,6 +14,7 @@ use std::{ cell::RefCell, cmp::Ordering, fmt::Debug, + marker::PhantomData, mem, rc::Rc, time::Duration, @@ -30,9 +30,299 @@ pub struct GroupStyle { pub style: Box, } -pub struct DragMoveEvent { +pub struct DragMoveEvent { pub event: MouseMoveEvent, - pub drag: View, + pub bounds: Bounds, + drag: PhantomData, +} + +impl DragMoveEvent { + pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T { + cx.active_drag + .as_ref() + .and_then(|drag| drag.value.downcast_ref::()) + .expect("DragMoveEvent is only valid when the stored active drag is of the same type.") + } +} + +impl Interactivity { + pub fn on_mouse_down( + &mut self, + button: MouseButton, + listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) { + self.mouse_down_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == button + && bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx) + } + })); + } + + pub fn on_any_mouse_down( + &mut self, + listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) { + self.mouse_down_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx) + } + })); + } + + pub fn on_mouse_up( + &mut self, + button: MouseButton, + listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, + ) { + self.mouse_up_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble + && event.button == button + && bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx) + } + })); + } + + pub fn on_any_mouse_up( + &mut self, + listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, + ) { + self.mouse_up_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx) + } + })); + } + + pub fn on_mouse_down_out( + &mut self, + listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, + ) { + self.mouse_down_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx) + } + })); + } + + pub fn on_mouse_up_out( + &mut self, + button: MouseButton, + listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, + ) { + self.mouse_up_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Capture + && event.button == button + && !bounds.visibly_contains(&event.position, cx) + { + (listener)(event, cx); + } + })); + } + + pub fn on_mouse_move( + &mut self, + listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, + ) { + self.mouse_move_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx); + } + })); + } + + pub fn on_drag_move( + &mut self, + listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, + ) where + T: 'static, + { + self.mouse_move_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Capture + && bounds.drag_target_contains(&event.position, cx) + { + if cx + .active_drag + .as_ref() + .is_some_and(|drag| drag.value.as_ref().type_id() == TypeId::of::()) + { + (listener)( + &DragMoveEvent { + event: event.clone(), + bounds: bounds.bounds, + drag: PhantomData, + }, + cx, + ); + } + } + })); + } + + pub fn on_scroll_wheel( + &mut self, + listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static, + ) { + self.scroll_wheel_listeners + .push(Box::new(move |event, bounds, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { + (listener)(event, cx); + } + })); + } + + pub fn capture_action( + &mut self, + listener: impl Fn(&A, &mut WindowContext) + 'static, + ) { + self.action_listeners.push(( + TypeId::of::(), + Box::new(move |action, phase, cx| { + let action = action.downcast_ref().unwrap(); + if phase == DispatchPhase::Capture { + (listener)(action, cx) + } + }), + )); + } + + pub fn on_action(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) { + self.action_listeners.push(( + TypeId::of::(), + Box::new(move |action, phase, cx| { + let action = action.downcast_ref().unwrap(); + if phase == DispatchPhase::Bubble { + (listener)(action, cx) + } + }), + )); + } + + pub fn on_boxed_action( + &mut self, + action: &Box, + listener: impl Fn(&Box, &mut WindowContext) + 'static, + ) { + let action = action.boxed_clone(); + self.action_listeners.push(( + (*action).type_id(), + Box::new(move |_, phase, cx| { + if phase == DispatchPhase::Bubble { + (listener)(&action, cx) + } + }), + )); + } + + pub fn on_key_down(&mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static) { + self.key_down_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Bubble { + (listener)(event, cx) + } + })); + } + + pub fn capture_key_down( + &mut self, + listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, + ) { + self.key_down_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Capture { + listener(event, cx) + } + })); + } + + pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) { + self.key_up_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Bubble { + listener(event, cx) + } + })); + } + + pub fn capture_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) { + self.key_up_listeners + .push(Box::new(move |event, phase, cx| { + if phase == DispatchPhase::Capture { + listener(event, cx) + } + })); + } + + pub fn on_drop(&mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) { + self.drop_listeners.push(( + TypeId::of::(), + Box::new(move |dragged_value, cx| { + listener(dragged_value.downcast_ref().unwrap(), cx); + }), + )); + } + + pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) + where + Self: Sized, + { + self.click_listeners + .push(Box::new(move |event, cx| listener(event, cx))); + } + + pub fn on_drag( + &mut self, + value: T, + constructor: impl Fn(&T, &mut WindowContext) -> View + 'static, + ) where + Self: Sized, + T: 'static, + W: 'static + Render, + { + debug_assert!( + self.drag_listener.is_none(), + "calling on_drag more than once on the same element is not supported" + ); + self.drag_listener = Some(( + Box::new(value), + Box::new(move |value, cx| constructor(value.downcast_ref().unwrap(), cx).into()), + )); + } + + pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) + where + Self: Sized, + { + debug_assert!( + self.hover_listener.is_none(), + "calling on_hover more than once on the same element is not supported" + ); + self.hover_listener = Some(Box::new(listener)); + } + + pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) + where + Self: Sized, + { + debug_assert!( + self.tooltip_builder.is_none(), + "calling tooltip more than once on the same element is not supported" + ); + self.tooltip_builder = Some(Rc::new(build_tooltip)); + } } pub trait InteractiveElement: Sized { @@ -92,16 +382,7 @@ pub trait InteractiveElement: Sized { button: MouseButton, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_down_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == button - && bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx) - } - }, - )); + self.interactivity().on_mouse_down(button, listener); self } @@ -109,13 +390,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_down_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx) - } - }, - )); + self.interactivity().on_any_mouse_down(listener); self } @@ -124,30 +399,7 @@ pub trait InteractiveElement: Sized { button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble - && event.button == button - && bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx) - } - })); - self - } - - fn on_any_mouse_up( - mut self, - listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, - ) -> Self { - self.interactivity() - .mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx) - } - })); + self.interactivity().on_mouse_up(button, listener); self } @@ -155,14 +407,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_down_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx) - } - }, - )); + self.interactivity().on_mouse_down_out(listener); self } @@ -171,16 +416,7 @@ pub trait InteractiveElement: Sized { button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .mouse_up_listeners - .push(Box::new(move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture - && event.button == button - && !bounds.visibly_contains(&event.position, cx) - { - (listener)(event, cx); - } - })); + self.interactivity().on_mouse_up_out(button, listener); self } @@ -188,41 +424,18 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().mouse_move_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx); - } - }, - )); + self.interactivity().on_mouse_move(listener); self } - fn on_drag_move( + fn on_drag_move( mut self, - listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, + listener: impl Fn(&DragMoveEvent, &mut WindowContext) + 'static, ) -> Self where - W: Render, + T: 'static, { - self.interactivity().mouse_move_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Capture - && bounds.drag_target_contains(&event.position, cx) - { - if let Some(view) = cx.active_drag().and_then(|view| view.downcast::().ok()) - { - (listener)( - &DragMoveEvent { - event: event.clone(), - drag: view, - }, - cx, - ); - } - } - }, - )); + self.interactivity().on_drag_move(listener); self } @@ -230,13 +443,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().scroll_wheel_listeners.push(Box::new( - move |event, bounds, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) { - (listener)(event, cx); - } - }, - )); + self.interactivity().on_scroll_wheel(listener); self } @@ -245,29 +452,13 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&A, &mut WindowContext) + 'static, ) -> Self { - self.interactivity().action_listeners.push(( - TypeId::of::(), - Box::new(move |action, phase, cx| { - let action = action.downcast_ref().unwrap(); - if phase == DispatchPhase::Capture { - (listener)(action, cx) - } - }), - )); + self.interactivity().capture_action(listener); self } /// Add a listener for the given action, fires during the bubble event phase fn on_action(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self { - self.interactivity().action_listeners.push(( - TypeId::of::(), - Box::new(move |action, phase, cx| { - let action = action.downcast_ref().unwrap(); - if phase == DispatchPhase::Bubble { - (listener)(action, cx) - } - }), - )); + self.interactivity().on_action(listener); self } @@ -276,15 +467,7 @@ pub trait InteractiveElement: Sized { action: &Box, listener: impl Fn(&Box, &mut WindowContext) + 'static, ) -> Self { - let action = action.boxed_clone(); - self.interactivity().action_listeners.push(( - (*action).type_id(), - Box::new(move |_, phase, cx| { - if phase == DispatchPhase::Bubble { - (listener)(&action, cx) - } - }), - )); + self.interactivity().on_boxed_action(action, listener); self } @@ -292,13 +475,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .key_down_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Bubble { - (listener)(event, cx) - } - })); + self.interactivity().on_key_down(listener); self } @@ -306,24 +483,12 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .key_down_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Capture { - listener(event, cx) - } - })); + self.interactivity().capture_key_down(listener); self } fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self { - self.interactivity() - .key_up_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Bubble { - listener(event, cx) - } - })); + self.interactivity().on_key_up(listener); self } @@ -331,13 +496,7 @@ pub trait InteractiveElement: Sized { mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static, ) -> Self { - self.interactivity() - .key_up_listeners - .push(Box::new(move |event, phase, cx| { - if phase == DispatchPhase::Capture { - listener(event, cx) - } - })); + self.interactivity().capture_key_up(listener); self } @@ -363,16 +522,8 @@ pub trait InteractiveElement: Sized { self } - fn on_drop( - mut self, - listener: impl Fn(&View, &mut WindowContext) + 'static, - ) -> Self { - self.interactivity().drop_listeners.push(( - TypeId::of::(), - Box::new(move |dragged_view, cx| { - listener(&dragged_view.downcast().unwrap(), cx); - }), - )); + fn on_drop(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self { + self.interactivity().on_drop(listener); self } } @@ -431,25 +582,21 @@ pub trait StatefulInteractiveElement: InteractiveElement { where Self: Sized, { - self.interactivity() - .click_listeners - .push(Box::new(move |event, cx| listener(event, cx))); + self.interactivity().on_click(listener); self } - fn on_drag(mut self, constructor: impl Fn(&mut WindowContext) -> View + 'static) -> Self + fn on_drag( + mut self, + value: T, + constructor: impl Fn(&T, &mut WindowContext) -> View + 'static, + ) -> Self where Self: Sized, + T: 'static, W: 'static + Render, { - debug_assert!( - self.interactivity().drag_listener.is_none(), - "calling on_drag more than once on the same element is not supported" - ); - self.interactivity().drag_listener = Some(Box::new(move |cursor_offset, cx| AnyDrag { - view: constructor(cx).into(), - cursor_offset, - })); + self.interactivity().on_drag(value, constructor); self } @@ -457,11 +604,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { where Self: Sized, { - debug_assert!( - self.interactivity().hover_listener.is_none(), - "calling on_hover more than once on the same element is not supported" - ); - self.interactivity().hover_listener = Some(Box::new(listener)); + self.interactivity().on_hover(listener); self } @@ -469,11 +612,7 @@ pub trait StatefulInteractiveElement: InteractiveElement { where Self: Sized, { - debug_assert!( - self.interactivity().tooltip_builder.is_none(), - "calling tooltip more than once on the same element is not supported" - ); - self.interactivity().tooltip_builder = Some(Rc::new(build_tooltip)); + self.interactivity().tooltip(build_tooltip); self } } @@ -496,10 +635,6 @@ pub trait FocusableElement: InteractiveElement { } } -pub type FocusListeners = Vec; - -pub type FocusListener = Box; - pub type MouseDownListener = Box; pub type MouseUpListener = @@ -513,9 +648,9 @@ pub type ScrollWheelListener = pub type ClickListener = Box; -pub type DragListener = Box, &mut WindowContext) -> AnyDrag + 'static>; +pub type DragListener = Box AnyView + 'static>; -type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static; +type DropListener = Box; pub type TooltipBuilder = Rc AnyView + 'static>; @@ -529,17 +664,20 @@ pub type ActionListener = Box Div { - let mut div = Div { - interactivity: Interactivity::default(), - children: SmallVec::default(), + #[cfg(debug_assertions)] + let interactivity = { + let mut interactivity = Interactivity::default(); + interactivity.location = Some(*core::panic::Location::caller()); + interactivity }; - #[cfg(debug_assertions)] - { - div.interactivity.location = Some(*core::panic::Location::caller()); - } + #[cfg(not(debug_assertions))] + let interactivity = Interactivity::default(); - div + Div { + interactivity, + children: SmallVec::default(), + } } pub struct Div { @@ -598,7 +736,7 @@ impl Element for Div { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, @@ -642,10 +780,6 @@ impl Element for Div { &mut element_state.interactive_state, cx, |style, scroll_offset, cx| { - if style.visibility == Visibility::Hidden { - return; - } - let z_index = style.z_index.unwrap_or(0); cx.with_z_index(z_index, |cx| { @@ -653,7 +787,7 @@ impl Element for Div { cx.with_text_style(style.text_style().cloned(), |cx| { cx.with_content_mask(style.overflow_mask(bounds), |cx| { cx.with_element_offset(scroll_offset, |cx| { - for child in self.children { + for child in &mut self.children { child.paint(cx); } }) @@ -685,7 +819,10 @@ pub struct DivState { impl DivState { pub fn is_active(&self) -> bool { - self.interactive_state.pending_mouse_down.borrow().is_some() + self.interactive_state + .pending_mouse_down + .as_ref() + .map_or(false, |pending| pending.borrow().is_some()) } } @@ -712,9 +849,9 @@ pub struct Interactivity { pub key_down_listeners: Vec, pub key_up_listeners: Vec, pub action_listeners: Vec<(TypeId, ActionListener)>, - pub drop_listeners: Vec<(TypeId, Box)>, + pub drop_listeners: Vec<(TypeId, DropListener)>, pub click_listeners: Vec, - pub drag_listener: Option, + pub drag_listener: Option<(Box, DragListener)>, pub hover_listener: Option>, pub tooltip_builder: Option, @@ -770,7 +907,7 @@ impl Interactivity { } pub fn paint( - mut self, + &mut self, bounds: Bounds, content_size: Size, element_state: &mut InteractiveElementState, @@ -779,13 +916,17 @@ impl Interactivity { ) { let style = self.compute_style(Some(bounds), element_state, cx); + if style.visibility == Visibility::Hidden { + return; + } + #[cfg(debug_assertions)] if self.element_id.is_some() && (style.debug || style.debug_below || cx.has_global::()) && bounds.contains(&cx.mouse_position()) { const FONT_SIZE: crate::Pixels = crate::Pixels(10.); - let element_id = format!("{:?}", self.element_id.unwrap()); + let element_id = format!("{:?}", self.element_id.as_ref().unwrap()); let str_len = element_id.len(); let render_debug_text = |cx: &mut WindowContext| { @@ -893,15 +1034,15 @@ impl Interactivity { if style .background .as_ref() - .is_some_and(|fill| fill.color().is_some()) + .is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent())) { cx.with_z_index(style.z_index.unwrap_or(0), |cx| cx.add_opaque_layer(bounds)) } - let interactive_bounds = Rc::new(InteractiveBounds { + let interactive_bounds = InteractiveBounds { bounds: bounds.intersect(&cx.content_mask().bounds), stacking_order: cx.stacking_order().clone(), - }); + }; if let Some(mouse_cursor) = style.mouse_cursor { let mouse_position = &cx.mouse_position(); @@ -934,28 +1075,28 @@ impl Interactivity { for listener in self.mouse_down_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } for listener in self.mouse_up_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } for listener in self.mouse_move_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } for listener in self.scroll_wheel_listeners.drain(..) { let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - listener(event, &*interactive_bounds, phase, cx); + listener(event, &interactive_bounds, phase, cx); }) } @@ -998,8 +1139,10 @@ impl Interactivity { if phase == DispatchPhase::Bubble && interactive_bounds.drag_target_contains(&event.position, cx) { - if let Some(drag_state_type) = - cx.active_drag.as_ref().map(|drag| drag.view.entity_type()) + if let Some(drag_state_type) = cx + .active_drag + .as_ref() + .map(|drag| drag.value.as_ref().type_id()) { for (drop_state_type, listener) in &drop_listeners { if *drop_state_type == drag_state_type { @@ -1008,7 +1151,7 @@ impl Interactivity { .take() .expect("checked for type drag state type above"); - listener(drag.view.clone(), cx); + listener(drag.value.as_ref(), cx); cx.notify(); cx.stop_propagation(); } @@ -1022,14 +1165,20 @@ impl Interactivity { } let click_listeners = mem::take(&mut self.click_listeners); - let drag_listener = mem::take(&mut self.drag_listener); + let mut drag_listener = mem::take(&mut self.drag_listener); if !click_listeners.is_empty() || drag_listener.is_some() { - let pending_mouse_down = element_state.pending_mouse_down.clone(); + let pending_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); let mouse_down = pending_mouse_down.borrow().clone(); if let Some(mouse_down) = mouse_down { - if let Some(drag_listener) = drag_listener { - let active_state = element_state.clicked_state.clone(); + if drag_listener.is_some() { + let active_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .clone(); let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { @@ -1041,10 +1190,18 @@ impl Interactivity { && interactive_bounds.visibly_contains(&event.position, cx) && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD { + let (drag_value, drag_listener) = drag_listener + .take() + .expect("The notify below should invalidate this callback"); + *active_state.borrow_mut() = ElementClickedState::default(); let cursor_offset = event.position - bounds.origin; - let drag = drag_listener(cursor_offset, cx); - cx.active_drag = Some(drag); + let drag = (drag_listener)(drag_value.as_ref(), cx); + cx.active_drag = Some(AnyDrag { + view: drag, + value: drag_value, + cursor_offset, + }); cx.notify(); cx.stop_propagation(); } @@ -1082,8 +1239,14 @@ impl Interactivity { } if let Some(hover_listener) = self.hover_listener.take() { - let was_hovered = element_state.hover_state.clone(); - let has_mouse_down = element_state.pending_mouse_down.clone(); + let was_hovered = element_state + .hover_state + .get_or_insert_with(Default::default) + .clone(); + let has_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { @@ -1104,8 +1267,14 @@ impl Interactivity { } if let Some(tooltip_builder) = self.tooltip_builder.take() { - let active_tooltip = element_state.active_tooltip.clone(); - let pending_mouse_down = element_state.pending_mouse_down.clone(); + let active_tooltip = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .clone(); + let pending_mouse_down = element_state + .pending_mouse_down + .get_or_insert_with(Default::default) + .clone(); let interactive_bounds = interactive_bounds.clone(); cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| { @@ -1147,19 +1316,30 @@ impl Interactivity { } }); - let active_tooltip = element_state.active_tooltip.clone(); + let active_tooltip = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .clone(); cx.on_mouse_event(move |_: &MouseDownEvent, _, _| { active_tooltip.borrow_mut().take(); }); - if let Some(active_tooltip) = element_state.active_tooltip.borrow().as_ref() { + if let Some(active_tooltip) = element_state + .active_tooltip + .get_or_insert_with(Default::default) + .borrow() + .as_ref() + { if active_tooltip.tooltip.is_some() { cx.active_tooltip = active_tooltip.tooltip.clone() } } } - let active_state = element_state.clicked_state.clone(); + let active_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .clone(); if active_state.borrow().is_clicked() { cx.on_mouse_event(move |_: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Capture { @@ -1235,23 +1415,26 @@ impl Interactivity { .as_ref() .map(|scroll_offset| *scroll_offset.borrow()); + let key_down_listeners = mem::take(&mut self.key_down_listeners); + let key_up_listeners = mem::take(&mut self.key_up_listeners); + let action_listeners = mem::take(&mut self.action_listeners); cx.with_key_dispatch( self.key_context.clone(), element_state.focus_handle.clone(), |_, cx| { - for listener in self.key_down_listeners.drain(..) { + for listener in key_down_listeners { cx.on_key_event(move |event: &KeyDownEvent, phase, cx| { listener(event, phase, cx); }) } - for listener in self.key_up_listeners.drain(..) { + for listener in key_up_listeners { cx.on_key_event(move |event: &KeyUpEvent, phase, cx| { listener(event, phase, cx); }) } - for (action_type, listener) in self.action_listeners { + for (action_type, listener) in action_listeners { cx.on_action(action_type, listener) } @@ -1312,7 +1495,7 @@ impl Interactivity { if let Some(drag) = cx.active_drag.take() { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { - if *state_type == drag.view.entity_type() + if *state_type == drag.value.as_ref().type_id() && group_bounds.contains(&mouse_position) { style.refine(&group_drag_style.style); @@ -1321,7 +1504,7 @@ impl Interactivity { } for (state_type, drag_over_style) in &self.drag_over_styles { - if *state_type == drag.view.entity_type() + if *state_type == drag.value.as_ref().type_id() && bounds .intersect(&cx.content_mask().bounds) .contains(&mouse_position) @@ -1338,7 +1521,10 @@ impl Interactivity { } } - let clicked_state = element_state.clicked_state.borrow(); + let clicked_state = element_state + .clicked_state + .get_or_insert_with(Default::default) + .borrow(); if clicked_state.group { if let Some(group) = self.group_active_style.as_ref() { style.refine(&group.style) @@ -1397,11 +1583,11 @@ impl Default for Interactivity { #[derive(Default)] pub struct InteractiveElementState { pub focus_handle: Option, - pub clicked_state: Rc>, - pub hover_state: Rc>, - pub pending_mouse_down: Rc>>, + pub clicked_state: Option>>, + pub hover_state: Option>>, + pub pending_mouse_down: Option>>>, pub scroll_offset: Option>>>, - pub active_tooltip: Rc>>, + pub active_tooltip: Option>>>, } pub struct ActiveTooltip { @@ -1487,7 +1673,7 @@ where self.element.layout(state, cx) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { self.element.paint(bounds, state, cx) } } @@ -1561,7 +1747,7 @@ where self.element.layout(state, cx) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { self.element.paint(bounds, state, cx) } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index f6aae2de66aebb7bc894bd63a8d9a85a6e74e089..4f81f604c8d3799923b9fb76742019cb1724c0b5 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -81,11 +81,12 @@ impl Element for Img { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, ) { + let source = self.source.clone(); self.interactivity.paint( bounds, bounds.size, @@ -94,7 +95,7 @@ impl Element for Img { |style, _scroll_offset, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); cx.with_z_index(1, |cx| { - match self.source { + match source { ImageSource::Uri(uri) => { let image_future = cx.image_cache.get(uri.clone()); if let Some(data) = image_future diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index ba479e1ea8f460ecf5915661f877a89f132f288d..6818c5c7a2e62baab27aad701dfa27cfa80dbce4 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -257,7 +257,7 @@ impl Element for List { } fn paint( - self, + &mut self, bounds: crate::Bounds, _state: &mut Self::State, cx: &mut crate::WindowContext, @@ -385,7 +385,7 @@ impl Element for List { // Paint the visible items let mut item_origin = bounds.origin; item_origin.y -= scroll_top.offset_in_item; - for mut item_element in item_elements { + for item_element in &mut item_elements { let item_height = item_element.measure(available_item_space, cx).height; item_element.draw(item_origin, available_item_space, cx); item_origin.y += item_height; diff --git a/crates/gpui2/src/elements/overlay.rs b/crates/gpui2/src/elements/overlay.rs index e925d03d275ea75d40633b1add4228b3c1ffce38..5b72019f177f899f886a375fe4468bcf403c336c 100644 --- a/crates/gpui2/src/elements/overlay.rs +++ b/crates/gpui2/src/elements/overlay.rs @@ -81,7 +81,7 @@ impl Element for Overlay { } fn paint( - self, + &mut self, bounds: crate::Bounds, element_state: &mut Self::State, cx: &mut WindowContext, @@ -149,7 +149,7 @@ impl Element for Overlay { cx.with_element_offset(desired.origin - bounds.origin, |cx| { cx.break_content_mask(|cx| { - for child in self.children { + for child in &mut self.children { child.paint(cx); } }) diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index aba31686f588fbfca2d9aabf2e04e52daa15d5bd..9ca9baf470e6c3bdeec2fafb1806d34430c44aea 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -36,8 +36,12 @@ impl Element for Svg { }) } - fn paint(self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext) - where + fn paint( + &mut self, + bounds: Bounds, + element_state: &mut Self::State, + cx: &mut WindowContext, + ) where Self: Sized, { self.interactivity diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index b8fe5e68666d7abe3e7f07b90feb4e7cdc7a5354..175a79c19a512832c7c70571780adb6268b91ab2 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -6,7 +6,7 @@ use crate::{ use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; use smallvec::SmallVec; -use std::{cell::Cell, ops::Range, rc::Rc, sync::Arc}; +use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc}; use util::ResultExt; impl Element for &'static str { @@ -22,7 +22,7 @@ impl Element for &'static str { (layout_id, state) } - fn paint(self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { state.paint(bounds, self, cx) } } @@ -52,7 +52,7 @@ impl Element for SharedString { (layout_id, state) } - fn paint(self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut TextState, cx: &mut WindowContext) { let text_str: &str = self.as_ref(); state.paint(bounds, text_str, cx) } @@ -128,7 +128,7 @@ impl Element for StyledText { (layout_id, state) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { state.paint(bounds, &self.text, cx) } } @@ -356,8 +356,8 @@ impl Element for InteractiveText { } } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { - if let Some(click_listener) = self.click_listener { + fn paint(&mut self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + if let Some(click_listener) = self.click_listener.take() { if let Some(ix) = state .text_state .index_for_position(bounds, cx.mouse_position()) @@ -374,13 +374,14 @@ impl Element for InteractiveText { let text_state = state.text_state.clone(); let mouse_down = state.mouse_down_index.clone(); if let Some(mouse_down_index) = mouse_down.get() { + let clickable_ranges = mem::take(&mut self.clickable_ranges); cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Bubble { if let Some(mouse_up_index) = text_state.index_for_position(bounds, event.position) { click_listener( - &self.clickable_ranges, + &clickable_ranges, InteractiveTextClickEvent { mouse_down_index, mouse_up_index, diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index debd365c87da2da4fbf402dc6bf4188267c191ee..9fedbad41c580b767d5c4425be1aa7a5c5f3b9b9 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -155,7 +155,7 @@ impl Element for UniformList { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, @@ -220,11 +220,11 @@ impl Element for UniformList { let visible_range = first_visible_element_ix ..cmp::min(last_visible_element_ix, self.item_count); - let items = (self.render_items)(visible_range.clone(), cx); + let mut items = (self.render_items)(visible_range.clone(), cx); cx.with_z_index(1, |cx| { let content_mask = ContentMask { bounds }; cx.with_content_mask(Some(content_mask), |cx| { - for (item, ix) in items.into_iter().zip(visible_range) { + for (item, ix) in items.iter_mut().zip(visible_range) { let item_origin = padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset.y); let available_space = size( diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 1c6955111cd2f31870ff92fb2dfeff11db4b02ef..58ee849aad4a6d665c6ec14176f72eb457c9a82e 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,6 +1,5 @@ use crate::{ - div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point, - Render, ViewContext, + div, point, Div, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; @@ -290,11 +289,6 @@ impl InputEvent { } } -pub struct FocusEvent { - pub blurred: Option, - pub focused: Option, -} - #[cfg(test)] mod test { use crate::{ diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index a44987aa39e65ecbabacbca40868fc27d61a3b49..ddb1f1e6ca8b4fd4db5d9c317144ca302519aa87 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -29,6 +29,7 @@ pub(crate) struct DispatchNode { pub key_listeners: Vec, pub action_listeners: Vec, pub context: Option, + focus_id: Option, parent: Option, } @@ -127,8 +128,9 @@ impl DispatchTree { } pub fn make_focusable(&mut self, focus_id: FocusId) { - self.focusable_node_ids - .insert(focus_id, self.active_node_id()); + let node_id = self.active_node_id(); + self.active_node().focus_id = Some(focus_id); + self.focusable_node_ids.insert(focus_id, node_id); } pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { @@ -247,6 +249,20 @@ impl DispatchTree { dispatch_path } + pub fn focus_path(&self, focus_id: FocusId) -> SmallVec<[FocusId; 8]> { + let mut focus_path: SmallVec<[FocusId; 8]> = SmallVec::new(); + let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied(); + while let Some(node_id) = current_node_id { + let node = self.node(node_id); + if let Some(focus_id) = node.focus_id { + focus_path.push(focus_id); + } + current_node_id = node.parent; + } + focus_path.reverse(); // Reverse the path so it goes from the root to the focused node. + focus_path + } + pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { &self.nodes[node_id.0] } diff --git a/crates/gpui2/src/platform/mac/metal_renderer.rs b/crates/gpui2/src/platform/mac/metal_renderer.rs index c477440df5d888658e96c8e9d4d3f7e70d214679..68768521ee474cd25c2e98c8d38c7ea669fba1e4 100644 --- a/crates/gpui2/src/platform/mac/metal_renderer.rs +++ b/crates/gpui2/src/platform/mac/metal_renderer.rs @@ -303,6 +303,7 @@ impl MetalRenderer { command_buffer.commit(); self.sprite_atlas.clear_textures(AtlasTextureKind::Path); + command_buffer.wait_until_completed(); drawable.present(); } diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 115db997843fe3b94e3b31ec292cab02c599dc96..f777e2d8cf2b950fb1770810d38e18b383745229 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -110,7 +110,7 @@ pub struct Style { /// The mouse cursor style shown when the mouse pointer is over an element. pub mouse_cursor: Option, - pub z_index: Option, + pub z_index: Option, #[cfg(debug_assertions)] pub debug: bool, diff --git a/crates/gpui2/src/styled.rs b/crates/gpui2/src/styled.rs index b9f5e8e4e814333fe703b0808c35293e43d0bea0..54af9bb0ef6c7e4d55b1cd4eb87ccc0036017983 100644 --- a/crates/gpui2/src/styled.rs +++ b/crates/gpui2/src/styled.rs @@ -12,7 +12,7 @@ pub trait Styled: Sized { gpui2_macros::style_helpers!(); - fn z_index(mut self, z_index: u32) -> Self { + fn z_index(mut self, z_index: u8) -> Self { self.style().z_index = Some(z_index); self } diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 280c52df2afad19af029a75e336222eae82aa74e..1b4c2b6346fb56320737ab74a8057da589aba354 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -7,6 +7,7 @@ use crate::{ use anyhow::{Context, Result}; use std::{ any::TypeId, + fmt, hash::{Hash, Hasher}, }; @@ -90,7 +91,7 @@ impl Element for View { (layout_id, Some(element)) } - fn paint(self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { element.take().unwrap().paint(cx); } } @@ -170,7 +171,7 @@ impl Eq for WeakView {} pub struct AnyView { model: AnyModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, AnyElement, &mut WindowContext), + paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), } impl AnyView { @@ -208,9 +209,9 @@ impl AnyView { cx: &mut WindowContext, ) { cx.with_absolute_element_offset(origin, |cx| { - let (layout_id, rendered_element) = (self.layout)(self, cx); + let (layout_id, mut rendered_element) = (self.layout)(self, cx); cx.compute_layout(layout_id, available_space); - (self.paint)(self, rendered_element, cx); + (self.paint)(self, &mut rendered_element, cx); }) } } @@ -237,12 +238,12 @@ impl Element for AnyView { (layout_id, Some(state)) } - fn paint(self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + fn paint(&mut self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { debug_assert!( state.is_some(), "state is None. Did you include an AnyView twice in the tree?" ); - (self.paint)(&self, state.take().unwrap(), cx) + (self.paint)(&self, state.as_mut().unwrap(), cx) } } @@ -273,7 +274,7 @@ impl IntoElement for AnyView { pub struct AnyWeakView { model: AnyWeakModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), - paint: fn(&AnyView, AnyElement, &mut WindowContext), + paint: fn(&AnyView, &mut AnyElement, &mut WindowContext), } impl AnyWeakView { @@ -297,6 +298,20 @@ impl From> for AnyWeakView { } } +impl PartialEq for AnyWeakView { + fn eq(&self, other: &Self) -> bool { + self.model == other.model + } +} + +impl std::fmt::Debug for AnyWeakView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AnyWeakView") + .field("entity_id", &self.model.entity_id) + .finish_non_exhaustive() + } +} + impl Render for T where T: 'static + FnMut(&mut WindowContext) -> E, @@ -324,7 +339,7 @@ mod any_view { pub(crate) fn paint( _view: &AnyView, - element: AnyElement, + element: &mut AnyElement, cx: &mut WindowContext, ) { element.paint(cx); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 585db90c6fe8bc6da15e250e14cf89ab4e8b8802..74c7204048d781a2dd9d01ae95cef28c85b78ab6 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2,17 +2,17 @@ use crate::{ key_dispatch::DispatchActionListener, px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, - EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, - Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, - LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, - MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, - PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, - RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, - Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, + ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, + Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, + Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, + RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style, + SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, + VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; -use collections::HashMap; +use collections::FxHashMap; use derive_more::{Deref, DerefMut}; use futures::{ channel::{mpsc, oneshot}, @@ -38,12 +38,24 @@ use std::{ }; use util::ResultExt; -const ACTIVE_DRAG_Z_INDEX: u32 = 1; +const ACTIVE_DRAG_Z_INDEX: u8 = 1; /// A global stacking order, which is created by stacking successive z-index values. /// Each z-index will always be interpreted in the context of its parent z-index. -#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default, Debug)] -pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>); +#[derive(Deref, DerefMut, Clone, Debug, Ord, PartialOrd, PartialEq, Eq)] +pub struct StackingOrder { + #[deref] + #[deref_mut] + z_indices: SmallVec<[u8; 64]>, +} + +impl Default for StackingOrder { + fn default() -> Self { + StackingOrder { + z_indices: SmallVec::new(), + } + } +} /// Represents the two different phases when dispatching events. #[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] @@ -74,9 +86,13 @@ impl DispatchPhase { type AnyObserver = Box bool + 'static>; type AnyMouseListener = Box; -type AnyFocusListener = Box; type AnyWindowFocusListener = Box bool + 'static>; +struct FocusEvent { + previous_focus_path: SmallVec<[FocusId; 8]>, + current_focus_path: SmallVec<[FocusId; 8]>, +} + slotmap::new_key_type! { pub struct FocusId; } impl FocusId { @@ -227,8 +243,8 @@ pub struct Window { pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, pub(crate) focus_handles: Arc>>, - pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, - pub(crate) blur_listeners: SubscriberSet<(), AnyObserver>, + focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, + blur_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, mouse_position: Point, modifiers: Modifiers, @@ -238,9 +254,12 @@ pub struct Window { bounds_observers: SubscriberSet<(), AnyObserver>, active: bool, pub(crate) dirty: bool, + pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, - pub(crate) last_blur: Option>, pub(crate) focus: Option, + + #[cfg(any(test, feature = "test-support"))] + pub(crate) focus_invalidated: bool, } pub(crate) struct ElementStateBox { @@ -251,10 +270,10 @@ pub(crate) struct ElementStateBox { // #[derive(Default)] pub(crate) struct Frame { - pub(crate) element_states: HashMap, - mouse_listeners: HashMap>, + focus: Option, + pub(crate) element_states: FxHashMap, + mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, - pub(crate) focus_listeners: Vec, pub(crate) scene_builder: SceneBuilder, pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, pub(crate) z_index_stack: StackingOrder, @@ -265,10 +284,10 @@ pub(crate) struct Frame { impl Frame { fn new(dispatch_tree: DispatchTree) -> Self { Frame { - element_states: HashMap::default(), - mouse_listeners: HashMap::default(), + focus: None, + element_states: FxHashMap::default(), + mouse_listeners: FxHashMap::default(), dispatch_tree, - focus_listeners: Vec::new(), scene_builder: SceneBuilder::default(), z_index_stack: StackingOrder::default(), depth_map: Default::default(), @@ -280,10 +299,15 @@ impl Frame { fn clear(&mut self) { self.element_states.clear(); self.mouse_listeners.values_mut().for_each(Vec::clear); - self.focus_listeners.clear(); self.dispatch_tree.clear(); self.depth_map.clear(); } + + fn focus_path(&self) -> SmallVec<[FocusId; 8]> { + self.focus + .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) + .unwrap_or_default() + } } impl Window { @@ -374,9 +398,12 @@ impl Window { bounds_observers: SubscriberSet::new(), active: false, dirty: false, + drawing: false, activation_observers: SubscriberSet::new(), - last_blur: None, focus: None, + + #[cfg(any(test, feature = "test-support"))] + focus_invalidated: false, } } } @@ -425,7 +452,9 @@ impl<'a> WindowContext<'a> { /// Mark the window as dirty, scheduling it to be redrawn on the next frame. pub fn notify(&mut self) { - self.window.dirty = true; + if !self.window.drawing { + self.window.dirty = true; + } } /// Close this window. @@ -452,35 +481,23 @@ impl<'a> WindowContext<'a> { return; } - let focus_id = handle.id; - - if self.window.last_blur.is_none() { - self.window.last_blur = Some(self.window.focus); - } - - self.window.focus = Some(focus_id); + self.window.focus = Some(handle.id); self.window .rendered_frame .dispatch_tree .clear_pending_keystrokes(); - self.app.push_effect(Effect::FocusChanged { - window_handle: self.window.handle, - focused: Some(focus_id), - }); + + #[cfg(any(test, feature = "test-support"))] + { + self.window.focus_invalidated = true; + } + self.notify(); } /// Remove focus from all elements within this context's window. pub fn blur(&mut self) { - if self.window.last_blur.is_none() { - self.window.last_blur = Some(self.window.focus); - } - self.window.focus = None; - self.app.push_effect(Effect::FocusChanged { - window_handle: self.window.handle, - focused: None, - }); self.notify(); } @@ -806,7 +823,7 @@ impl<'a> WindowContext<'a> { /// a specific need to register a global listener. pub fn on_mouse_event( &mut self, - handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, + mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { let order = self.window.next_frame.z_index_stack.clone(); self.window @@ -891,7 +908,7 @@ impl<'a> WindowContext<'a> { /// Called during painting to invoke the given closure in a new stacking context. The given /// z-index is interpreted relative to the previous call to `stack`. - pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { + pub fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { self.window.next_frame.z_index_stack.push(z_index); let result = f(self); self.window.next_frame.z_index_stack.pop(); @@ -1238,16 +1255,14 @@ impl<'a> WindowContext<'a> { /// Draw pixels to the display for this window based on the contents of its scene. pub(crate) fn draw(&mut self) -> Scene { - let window_was_focused = self - .window - .focus - .and_then(|focus_id| { - self.window - .rendered_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) - .is_some(); + self.window.dirty = false; + self.window.drawing = true; + + #[cfg(any(test, feature = "test-support"))] + { + self.window.focus_invalidated = false; + } + self.text_system().start_frame(); self.window.platform_window.clear_input_handler(); self.window.layout_engine.as_mut().unwrap().clear(); @@ -1286,23 +1301,6 @@ impl<'a> WindowContext<'a> { }); } - let window_is_focused = self - .window - .focus - .and_then(|focus_id| { - self.window - .next_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) - .is_some(); - if window_was_focused && !window_is_focused { - self.window - .blur_listeners - .clone() - .retain(&(), |listener| listener(self)); - } - self.window .next_frame .dispatch_tree @@ -1310,10 +1308,30 @@ impl<'a> WindowContext<'a> { &mut self.window.rendered_frame.dispatch_tree, self.window.focus, ); + self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); - let window = &mut self.window; - mem::swap(&mut window.rendered_frame, &mut window.next_frame); + let previous_focus_path = self.window.rendered_frame.focus_path(); + mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); + let current_focus_path = self.window.rendered_frame.focus_path(); + + if previous_focus_path != current_focus_path { + if !previous_focus_path.is_empty() && current_focus_path.is_empty() { + self.window + .blur_listeners + .clone() + .retain(&(), |listener| listener(self)); + } + + let event = FocusEvent { + previous_focus_path, + current_focus_path, + }; + self.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, self)); + } let scene = self.window.rendered_frame.scene_builder.build(); @@ -1327,7 +1345,7 @@ impl<'a> WindowContext<'a> { self.platform.set_cursor_style(cursor_style); } - self.window.dirty = false; + self.window.drawing = false; scene } @@ -1379,6 +1397,7 @@ impl<'a> WindowContext<'a> { self.window.mouse_position = position; if self.active_drag.is_none() { self.active_drag = Some(AnyDrag { + value: Box::new(files.clone()), view: self.build_view(|_| files).into(), cursor_offset: position, }); @@ -1719,22 +1738,6 @@ impl<'a> WindowContext<'a> { result } - /// Register a focus listener for the next frame only. It will be cleared - /// on the next frame render. You should use this method only from within elements, - /// and we may want to enforce that better via a different context type. - // todo!() Move this to `FrameContext` to emphasize its individuality? - pub fn on_focus_changed( - &mut self, - listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static, - ) { - self.window - .next_frame - .focus_listeners - .push(Box::new(move |event, cx| { - listener(event, cx); - })); - } - /// Set an input handler, such as [ElementInputHandler], which interfaces with the /// platform to receive textual input with proper integration with concerns such /// as IME interactions. @@ -2226,7 +2229,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } - pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { + pub fn with_z_index(&mut self, z_index: u8, f: impl FnOnce(&mut Self) -> R) -> R { self.window.next_frame.z_index_stack.push(z_index); let result = f(self); self.window.next_frame.z_index_stack.pop(); @@ -2363,10 +2366,12 @@ impl<'a, V: 'static> ViewContext<'a, V> { } pub fn notify(&mut self) { - self.window_cx.notify(); - self.window_cx.app.push_effect(Effect::Notify { - emitter: self.view.model.entity_id, - }); + if !self.window.drawing { + self.window_cx.notify(); + self.window_cx.app.push_effect(Effect::Notify { + emitter: self.view.model.entity_id, + }); + } } pub fn observe_window_bounds( @@ -2409,7 +2414,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event.focused.as_ref().map(|focused| focused.id) == Some(focus_id) { + if event.previous_focus_path.last() != Some(&focus_id) + && event.current_focus_path.last() == Some(&focus_id) + { listener(view, cx) } }) @@ -2434,10 +2441,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event - .focused - .as_ref() - .map_or(false, |focused| focus_id.contains(focused.id, cx)) + if !event.previous_focus_path.contains(&focus_id) + && event.current_focus_path.contains(&focus_id) { listener(view, cx) } @@ -2463,7 +2468,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event.blurred.as_ref().map(|blurred| blurred.id) == Some(focus_id) { + if event.previous_focus_path.last() == Some(&focus_id) + && event.current_focus_path.last() != Some(&focus_id) + { listener(view, cx) } }) @@ -2504,10 +2511,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event - .blurred - .as_ref() - .map_or(false, |blurred| focus_id.contains(blurred.id, cx)) + if event.previous_focus_path.contains(&focus_id) + && !event.current_focus_path.contains(&focus_id) { listener(view, cx) } @@ -2901,12 +2906,12 @@ impl AnyWindowHandle { } } -#[cfg(any(test, feature = "test-support"))] -impl From> for StackingOrder { - fn from(small_vec: SmallVec<[u32; 16]>) -> Self { - StackingOrder(small_vec) - } -} +// #[cfg(any(test, feature = "test-support"))] +// impl From> for StackingOrder { +// fn from(small_vec: SmallVec<[u32; 16]>) -> Self { +// StackingOrder(small_vec) +// } +// } #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum ElementId { diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index adcd21cac6d35f2a00f24938184122d7c7d842c1..2d18d2a6c769396d699ee3059e48f2736a85370a 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -1377,33 +1377,28 @@ impl ProjectPanel { }) .unwrap_or(theme.status().info); + let file_name = details.filename.clone(); + let icon = details.icon.clone(); + let depth = details.depth; div() .id(entry_id.to_proto() as usize) - .on_drag({ - let details = details.clone(); - move |cx| { - let details = details.clone(); - cx.build_view(|_| DraggedProjectEntryView { - details, - width, - entry_id, - }) - } - }) - .drag_over::(|style| { - style.bg(cx.theme().colors().ghost_element_hover) + .on_drag(entry_id, move |entry_id, cx| { + cx.build_view(|_| DraggedProjectEntryView { + details: details.clone(), + width, + entry_id: *entry_id, + }) }) - .on_drop(cx.listener( - move |this, dragged_view: &View, cx| { - this.move_entry(dragged_view.read(cx).entry_id, entry_id, kind.is_file(), cx); - }, - )) + .drag_over::(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .on_drop(cx.listener(move |this, dragged_id: &ProjectEntryId, cx| { + this.move_entry(*dragged_id, entry_id, kind.is_file(), cx); + })) .child( ListItem::new(entry_id.to_proto() as usize) - .indent_level(details.depth) + .indent_level(depth) .indent_step_size(px(settings.indent_size)) .selected(is_selected) - .child(if let Some(icon) = &details.icon { + .child(if let Some(icon) = &icon { div().child(IconElement::from_path(icon.to_string())) } else { div() @@ -1414,7 +1409,7 @@ impl ProjectPanel { } else { div() .text_color(filename_text_color) - .child(Label::new(details.filename.clone())) + .child(Label::new(file_name)) } .ml_1(), ) @@ -1927,7 +1922,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "root1", cx); @@ -2280,7 +2280,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "root1", cx); @@ -2551,7 +2556,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "src/", cx); diff --git a/crates/recent_projects2/src/recent_projects.rs b/crates/recent_projects2/src/recent_projects.rs index dff6aa12ccc30f43766451d244619159c2a7c8bb..3ecf1180af535ea23190bd41ad2c8dd0e329810d 100644 --- a/crates/recent_projects2/src/recent_projects.rs +++ b/crates/recent_projects2/src/recent_projects.rs @@ -76,7 +76,10 @@ impl RecentProjects { let delegate = RecentProjectsDelegate::new(weak_workspace, workspace_locations, true); - RecentProjects::new(delegate, cx) + let modal = RecentProjects::new(delegate, cx); + cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent)) + .detach(); + modal }); } else { workspace.show_notification(0, cx, |cx| { diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index 45495e502700ee65c3015a9f6fd78bc22fe5053c..5db7aff73670161719188a8cff5051c4a4a46604 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -338,7 +338,9 @@ impl BufferSearchBar { pane.update(cx, |this, cx| { this.toolbar().update(cx, |this, cx| { if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, |this, cx| this.toggle(deploy, cx)); + search_bar.update(cx, |this, cx| { + this.deploy(deploy, cx); + }); return; } let view = cx.build_view(|cx| BufferSearchBar::new(cx)); @@ -1483,9 +1485,9 @@ mod tests { search_bar.select_all_matches(&SelectAllMatches, cx); }); assert!( - editor.update(cx, |this, cx| !this.is_focused(cx.window_context())), - "Should not switch focus to editor if SelectAllMatches does not find any matches" - ); + editor.update(cx, |this, cx| !this.is_focused(cx.window_context())), + "Should not switch focus to editor if SelectAllMatches does not find any matches" + ); search_bar.update(cx, |search_bar, cx| { let all_selections = editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)); @@ -1651,6 +1653,7 @@ mod tests { assert_eq!(search_bar.search_options, SearchOptions::NONE); }); } + #[gpui::test] async fn test_replace_simple(cx: &mut TestAppContext) { let (editor, search_bar, cx) = init_test(cx); diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index 09b23c74b0b6bd953c132c133052b837516a8b53..7bd60891ced8a78902d856a674c502c8c1546f4a 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -10,11 +10,13 @@ use editor::{ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, EditorEvent, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, }; +use editor::{EditorElement, EditorStyle}; use gpui::{ - actions, div, white, AnyElement, AnyView, AppContext, Context as _, Div, Element, EntityId, - EventEmitter, FocusableView, InteractiveElement, IntoElement, KeyContext, Model, ModelContext, - ParentElement, PromptLevel, Render, SharedString, Styled, Subscription, Task, View, - ViewContext, VisualContext, WeakModel, WeakView, WindowContext, + actions, div, AnyElement, AnyView, AppContext, Context as _, Div, Element, EntityId, + EventEmitter, FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, + KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled, + Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView, + WhiteSpace, WindowContext, }; use menu::Confirm; use project::{ @@ -23,6 +25,7 @@ use project::{ }; use semantic_index::{SemanticIndex, SemanticIndexStatus}; +use settings::Settings; use smol::stream::StreamExt; use std::{ any::{Any, TypeId}, @@ -32,6 +35,7 @@ use std::{ path::PathBuf, time::{Duration, Instant}, }; +use theme::ThemeSettings; use ui::{ h_stack, prelude::*, v_stack, Button, Icon, IconButton, IconElement, Label, LabelCommon, @@ -273,13 +277,15 @@ pub enum ViewEvent { impl EventEmitter for ProjectSearchView {} impl Render for ProjectSearchView { - type Element = Div; + type Element = AnyElement; + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { if self.has_matches() { div() .flex_1() .size_full() .child(self.results_editor.clone()) + .into_any() } else { let model = self.model.read(cx); let has_no_results = model.no_results.unwrap_or(false); @@ -352,21 +358,31 @@ impl Render for ProjectSearchView { .max_w_96() .child(Label::new(text).size(LabelSize::Small)) }); - v_stack().flex_1().size_full().justify_center().child( - h_stack() - .size_full() - .justify_center() - .child(h_stack().flex_1()) - .child(v_stack().child(major_text).children(minor_text)) - .child(h_stack().flex_1()), - ) + v_stack() + .track_focus(&self.query_editor.focus_handle(cx)) + .flex_1() + .size_full() + .justify_center() + .child( + h_stack() + .size_full() + .justify_center() + .child(h_stack().flex_1()) + .child(v_stack().child(major_text).children(minor_text)) + .child(h_stack().flex_1()), + ) + .into_any() } } } impl FocusableView for ProjectSearchView { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { - self.results_editor.focus_handle(cx) + if self.has_matches() { + self.results_editor.focus_handle(cx) + } else { + self.query_editor.focus_handle(cx) + } } } @@ -1425,6 +1441,36 @@ impl ProjectSearchBar { }; new_placeholder_text } + + fn render_text_input(&self, editor: &View, cx: &ViewContext) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if editor.read(cx).read_only() { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features, + font_size: rems(0.875).into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: relative(1.3).into(), + background_color: None, + underline: None, + white_space: WhiteSpace::Normal, + }; + + EditorElement::new( + &editor, + EditorStyle { + background: cx.theme().colors().editor_background, + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + } } impl Render for ProjectSearchBar { @@ -1445,85 +1491,79 @@ impl Render for ProjectSearchBar { } let search = search.read(cx); let semantic_is_available = SemanticIndex::enabled(cx); - let query_column = v_stack() - //.flex_1() - .child( - h_stack() - .min_w_80() - .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) - .on_action( - cx.listener(|this, action, cx| this.previous_history_query(action, cx)), - ) - .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) - .child(IconElement::new(Icon::MagnifyingGlass)) - .child(search.query_editor.clone()) - .child( - h_stack() - .child( - IconButton::new("project-search-filter-button", Icon::Filter) - .tooltip(|cx| { - Tooltip::for_action("Toggle filters", &ToggleFilters, cx) - }) - .on_click(cx.listener(|this, _, cx| { - this.toggle_filters(cx); - })) - .selected( - self.active_project_search - .as_ref() - .map(|search| search.read(cx).filters_enabled) - .unwrap_or_default(), - ), - ) - .when(search.current_mode != SearchMode::Semantic, |this| { - this.child( - IconButton::new( - "project-search-case-sensitive", - Icon::CaseSensitive, + let query_column = v_stack().child( + h_stack() + .min_w(rems(512. / 16.)) + .px_2() + .py_1() + .gap_2() + .bg(cx.theme().colors().editor_background) + .border_1() + .border_color(cx.theme().colors().border) + .rounded_lg() + .on_action(cx.listener(|this, action, cx| this.confirm(action, cx))) + .on_action(cx.listener(|this, action, cx| this.previous_history_query(action, cx))) + .on_action(cx.listener(|this, action, cx| this.next_history_query(action, cx))) + .child(IconElement::new(Icon::MagnifyingGlass)) + .child(self.render_text_input(&search.query_editor, cx)) + .child( + h_stack() + .child( + IconButton::new("project-search-filter-button", Icon::Filter) + .tooltip(|cx| { + Tooltip::for_action("Toggle filters", &ToggleFilters, cx) + }) + .on_click(cx.listener(|this, _, cx| { + this.toggle_filters(cx); + })) + .selected( + self.active_project_search + .as_ref() + .map(|search| search.read(cx).filters_enabled) + .unwrap_or_default(), + ), + ) + .when(search.current_mode != SearchMode::Semantic, |this| { + this.child( + IconButton::new( + "project-search-case-sensitive", + Icon::CaseSensitive, + ) + .tooltip(|cx| { + Tooltip::for_action( + "Toggle case sensitive", + &ToggleCaseSensitive, + cx, ) + }) + .selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx)) + .on_click(cx.listener( + |this, _, cx| { + this.toggle_search_option( + SearchOptions::CASE_SENSITIVE, + cx, + ); + }, + )), + ) + .child( + IconButton::new("project-search-whole-word", Icon::WholeWord) .tooltip(|cx| { Tooltip::for_action( - "Toggle case sensitive", - &ToggleCaseSensitive, + "Toggle whole word", + &ToggleWholeWord, cx, ) }) - .selected( - self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx), - ) - .on_click(cx.listener( - |this, _, cx| { - this.toggle_search_option( - SearchOptions::CASE_SENSITIVE, - cx, - ); - }, - )), - ) - .child( - IconButton::new("project-search-whole-word", Icon::WholeWord) - .tooltip(|cx| { - Tooltip::for_action( - "Toggle whole word", - &ToggleWholeWord, - cx, - ) - }) - .selected( - self.is_option_enabled(SearchOptions::WHOLE_WORD, cx), - ) - .on_click(cx.listener(|this, _, cx| { - this.toggle_search_option( - SearchOptions::WHOLE_WORD, - cx, - ); - })), - ) - }), - ) - .border_2() - .bg(white()) - .rounded_lg(), - ); + .selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx)) + .on_click(cx.listener(|this, _, cx| { + this.toggle_search_option(SearchOptions::WHOLE_WORD, cx); + })), + ) + }), + ), + ); + let mode_column = v_stack().items_start().justify_start().child( h_stack() .child( @@ -1579,12 +1619,16 @@ impl Render for ProjectSearchBar { ); let replace_column = if search.replace_enabled { h_stack() - .p_1() .flex_1() - .border_2() + .h_full() + .gap_2() + .px_2() + .py_1() + .border_1() + .border_color(cx.theme().colors().border) .rounded_lg() .child(IconElement::new(Icon::Replace).size(ui::IconSize::Small)) - .child(search.replacement_editor.clone()) + .child(self.render_text_input(&search.replacement_editor, cx)) } else { // Fill out the space if we don't have a replacement editor. h_stack().flex_1() @@ -1647,9 +1691,10 @@ impl Render for ProjectSearchBar { ]); v_stack() .key_context(key_context) + .flex_grow() .p_1() .m_2() - .justify_between() + .gap_2() .on_action(cx.listener(|this, _: &ToggleFilters, cx| { this.toggle_filters(cx); })) @@ -1703,6 +1748,7 @@ impl Render for ProjectSearchBar { }) .child( h_stack() + .justify_between() .child(query_column) .child(mode_column) .child(replace_column) @@ -1711,15 +1757,19 @@ impl Render for ProjectSearchBar { .when(search.filters_enabled, |this| { this.child( h_stack() - .mt_2() .flex_1() + .gap_2() .justify_between() .child( h_stack() .flex_1() + .h_full() + .px_2() + .py_1() .border_1() - .mr_2() - .child(search.included_files_editor.clone()) + .border_color(cx.theme().colors().border) + .rounded_lg() + .child(self.render_text_input(&search.included_files_editor, cx)) .when(search.current_mode != SearchMode::Semantic, |this| { this.child( SearchOptions::INCLUDE_IGNORED.as_button( @@ -1739,9 +1789,13 @@ impl Render for ProjectSearchBar { .child( h_stack() .flex_1() + .h_full() + .px_2() + .py_1() .border_1() - .ml_2() - .child(search.excluded_files_editor.clone()), + .border_color(cx.theme().colors().border) + .rounded_lg() + .child(self.render_text_input(&search.excluded_files_editor, cx)), ), ) }) diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 672bebe20e1ea59124833c00615c4da44cc65057..db79c9faca9052c383e8809b4ae064bf9a847609 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -3,23 +3,19 @@ name = "storybook2" version = "0.1.0" edition = "2021" publish = false -default-run = "storybook" [[bin]] name = "storybook" -path = "src/bin/storybook2.rs" - -[[bin]] -name = "print_storybook_script" -path = "src/bin/print_storybook_script.rs" +path = "src/storybook2.rs" [dependencies] anyhow.workspace = true # TODO: Remove after diagnosing stack overflow. backtrace-on-stack-overflow = "0.3.0" +chrono = "0.4" clap = { version = "4.4", features = ["derive", "string"] } +dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } editor = { package = "editor2", path = "../editor2" } -chrono = "0.4" fuzzy = { package = "fuzzy2", path = "../fuzzy2" } gpui = { package = "gpui2", path = "../gpui2" } indoc.workspace = true diff --git a/crates/storybook2/src/bin/print_storybook_script.rs b/crates/storybook2/src/bin/print_storybook_script.rs deleted file mode 100644 index d3409d6121779984a051383ce9ee97842f46625d..0000000000000000000000000000000000000000 --- a/crates/storybook2/src/bin/print_storybook_script.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::fs::File; -use std::io::Write; - -use strum::IntoEnumIterator; - -use storybook2::story_selector::ComponentStory; - -// TOOD: Ideally we actually create a more full featured CLI, -// but for the moment I just wanted a easier way to run the stories - -fn main() -> std::io::Result<()> { - let path = std::env::current_dir()?; - let out_file = path.join("script").join("storybook"); - - // the script output file - let mut file = File::create(out_file)?; - - // generate the list of components, in `snake_case` - let components = ComponentStory::iter() - .map(|c| c.to_string()) // Converts enum to string in `snake_case` - .collect::>(); - - // write the bash script - writeln!(file, "#!/bin/bash")?; - writeln!(file, "")?; - writeln!(file, "options=(")?; - for component in &components { - writeln!(file, " \"{}\"", component)?; - } - writeln!(file, ")")?; - writeln!(file, "")?; - - // Check if an argument is provided and if it matches a valid option - writeln!(file, "run_story() {{")?; - writeln!(file, " echo \"Running story: $1\"")?; - writeln!(file, " cargo run -p storybook2 -- \"$1\"")?; - writeln!(file, "}}")?; - writeln!(file, "")?; - - writeln!(file, "if [ \"$#\" -gt 0 ]; then")?; - writeln!(file, " story_arg=\"$1\"")?; - writeln!(file, " # Add prefix 'components/' if not present")?; - writeln!(file, " if [[ $story_arg != components/* ]]; then")?; - writeln!(file, " story_arg=\"components/$story_arg\"")?; - writeln!(file, " fi")?; - writeln!(file, " # Check if the provided story is a valid option")?; - writeln!(file, " for opt in \"${{options[@]}}\"; do")?; - writeln!( - file, - " if [[ \"components/$opt\" == \"$story_arg\" ]]; then" - )?; - writeln!(file, " run_story \"$story_arg\"")?; - writeln!(file, " exit")?; - writeln!(file, " fi")?; - writeln!(file, " done")?; - writeln!(file, " echo \"Invalid story name: $1\"")?; - writeln!(file, " exit 1")?; - writeln!(file, "fi")?; - writeln!(file, "")?; - - // Existing selection prompt - writeln!(file, "prompt=\"Please select a story:\"")?; - writeln!(file, "PS3=\"$prompt \"")?; - writeln!(file, "select story in \"${{options[@]}}\"; do")?; - writeln!(file, " if [[ -n $story ]]; then")?; - writeln!(file, " run_story \"components/$story\"")?; - writeln!(file, " break")?; - writeln!(file, " else")?; - writeln!(file, " echo \"Invalid option\"")?; - writeln!(file, " fi")?; - writeln!(file, "done")?; - - Ok(()) -} diff --git a/crates/storybook2/src/lib.rs b/crates/storybook2/src/lib.rs deleted file mode 100644 index f9c945f06ab73c0b314f78997cb9dafc94624118..0000000000000000000000000000000000000000 --- a/crates/storybook2/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod assets; - -pub mod stories; -pub mod story_selector; diff --git a/crates/storybook2/src/stories/z_index.rs b/crates/storybook2/src/stories/z_index.rs index c6a4b68cc369f4e0bf409f714e585c2bde6f47f4..9579b8c7fcf891c4fcda7ef691d37f583b2bd77a 100644 --- a/crates/storybook2/src/stories/z_index.rs +++ b/crates/storybook2/src/stories/z_index.rs @@ -78,7 +78,7 @@ impl Styles for Div {} #[derive(IntoElement)] struct ZIndexExample { - z_index: u32, + z_index: u8, } impl RenderOnce for ZIndexExample { @@ -170,7 +170,7 @@ impl RenderOnce for ZIndexExample { } impl ZIndexExample { - pub fn new(z_index: u32) -> Self { + pub fn new(z_index: u8) -> Self { Self { z_index } } } diff --git a/crates/storybook2/src/bin/storybook2.rs b/crates/storybook2/src/storybook2.rs similarity index 84% rename from crates/storybook2/src/bin/storybook2.rs rename to crates/storybook2/src/storybook2.rs index b77bbcb0666ac9f3ddc4930e72ad2c2fd5c8c6b5..13d9a75e2451d607820c9df3c845b285a0f6c40c 100644 --- a/crates/storybook2/src/bin/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -1,6 +1,11 @@ +mod assets; +mod stories; +mod story_selector; + use std::sync::Arc; use clap::Parser; +use dialoguer::FuzzySelect; use gpui::{ div, px, size, AnyView, AppContext, Bounds, Div, Render, ViewContext, VisualContext, WindowBounds, WindowOptions, @@ -8,13 +13,14 @@ use gpui::{ use log::LevelFilter; use settings2::{default_settings, Settings, SettingsStore}; use simplelog::SimpleLogger; +use strum::IntoEnumIterator; use theme2::{ThemeRegistry, ThemeSettings}; use ui::prelude::*; pub use indoc::indoc; use storybook2::assets::Assets; -pub use storybook2::story_selector::*; -// pub use crate::story_selector::{ComponentStory, StorySelector}; +use crate::assets::Assets; +use crate::story_selector::{ComponentStory, StorySelector}; // gpui::actions! { // storybook, @@ -41,7 +47,17 @@ fn main() { let args = Args::parse(); - let story_selector = args.story.clone(); + let story_selector = args.story.clone().unwrap_or_else(|| { + let stories = ComponentStory::iter().collect::>(); + + let selection = FuzzySelect::new() + .with_prompt("Choose a story to run:") + .items(&stories) + .interact() + .unwrap(); + + StorySelector::Component(stories[selection]) + }); let theme_name = args.theme.unwrap_or("One Dark".to_string()); let asset_source = Arc::new(Assets); @@ -56,7 +72,7 @@ fn main() { theme2::init(theme2::LoadThemes::All, cx); - let selector = story_selector.unwrap_or(StorySelector::KitchenSink); + let selector = story_selector; let theme_registry = cx.global::(); let mut theme_settings = ThemeSettings::get_global(cx).clone(); diff --git a/crates/terminal_view2/src/terminal_element.rs b/crates/terminal_view2/src/terminal_element.rs index 7358f2e1d735bd9170ab2815035d4232c3e4f933..e9335a4220312d33eeb1fcab4670661d3f037ba3 100644 --- a/crates/terminal_view2/src/terminal_element.rs +++ b/crates/terminal_view2/src/terminal_element.rs @@ -2,8 +2,8 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ black, div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, FontStyle, - FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, IntoElement, - LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, + FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, Interactivity, + IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, Pixels, PlatformInputHandler, Point, Rgba, ShapedLine, Size, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext, }; @@ -145,11 +145,11 @@ pub struct TerminalElement { focused: bool, cursor_visible: bool, can_navigate_to_selected_word: bool, - interactivity: gpui::Interactivity, + interactivity: Interactivity, } impl InteractiveElement for TerminalElement { - fn interactivity(&mut self) -> &mut gpui::Interactivity { + fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } } @@ -605,141 +605,156 @@ impl TerminalElement { } fn register_mouse_listeners( - self, + &mut self, origin: Point, mode: TermMode, bounds: Bounds, cx: &mut WindowContext, - ) -> Self { + ) { let focus = self.focus.clone(); - let connection = self.terminal.clone(); - - let mut this = self - .on_mouse_down(MouseButton::Left, { - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - cx.focus(&focus); - //todo!(context menu) - // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); - connection.update(cx, |terminal, cx| { - terminal.mouse_down(&e, origin); + let terminal = self.terminal.clone(); + + self.interactivity.on_mouse_down(MouseButton::Left, { + let terminal = terminal.clone(); + let focus = focus.clone(); + move |e, cx| { + cx.focus(&focus); + //todo!(context menu) + // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); + terminal.update(cx, |terminal, cx| { + terminal.mouse_down(&e, origin); + cx.notify(); + }) + } + }); + self.interactivity.on_mouse_move({ + let terminal = terminal.clone(); + let focus = focus.clone(); + move |e, cx| { + if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() { + terminal.update(cx, |terminal, cx| { + terminal.mouse_drag(e, origin, bounds); cx.notify(); }) } - }) - .on_mouse_move({ - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() { - connection.update(cx, |terminal, cx| { - terminal.mouse_drag(e, origin, bounds); - cx.notify(); - }) - } - } - }) - .on_mouse_up( - MouseButton::Left, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ) - .on_click({ - let connection = connection.clone(); - move |e, cx| { - if e.down.button == MouseButton::Right { - let mouse_mode = connection.update(cx, |terminal, _cx| { - terminal.mouse_mode(e.down.modifiers.shift) - }); - - if !mouse_mode { - //todo!(context menu) - // view.deploy_context_menu(e.position, cx); - } - } - } - }) - .on_mouse_move({ - let connection = connection.clone(); - let focus = focus.clone(); - move |e, cx| { - if focus.is_focused(cx) { - connection.update(cx, |terminal, cx| { - terminal.mouse_move(&e, origin); - cx.notify(); - }) + } + }); + self.interactivity.on_mouse_up( + MouseButton::Left, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ); + self.interactivity.on_click({ + let terminal = terminal.clone(); + move |e, cx| { + if e.down.button == MouseButton::Right { + let mouse_mode = terminal.update(cx, |terminal, _cx| { + terminal.mouse_mode(e.down.modifiers.shift) + }); + + if !mouse_mode { + //todo!(context menu) + // view.deploy_context_menu(e.position, cx); } } - }) - .on_scroll_wheel({ - let connection = connection.clone(); - move |e, cx| { - connection.update(cx, |terminal, cx| { - terminal.scroll_wheel(e, origin); + } + }); + + self.interactivity.on_mouse_move({ + let terminal = terminal.clone(); + let focus = focus.clone(); + move |e, cx| { + if focus.is_focused(cx) { + terminal.update(cx, |terminal, cx| { + terminal.mouse_move(&e, origin); cx.notify(); }) } - }); + } + }); + self.interactivity.on_scroll_wheel({ + let terminal = terminal.clone(); + move |e, cx| { + terminal.update(cx, |terminal, cx| { + terminal.scroll_wheel(e, origin); + cx.notify(); + }) + } + }); + + self.interactivity.on_drop::({ + let focus = focus.clone(); + let terminal = terminal.clone(); + move |external_paths, cx| { + cx.focus(&focus); + let mut new_text = external_paths + .paths() + .iter() + .map(|path| format!(" {path:?}")) + .join(""); + new_text.push(' '); + terminal.update(cx, |terminal, _| { + // todo!() long paths are not displayed properly albeit the text is there + terminal.paste(&new_text); + }); + } + }); // Mouse mode handlers: // All mouse modes need the extra click handlers if mode.intersects(TermMode::MOUSE_MODE) { - this = this - .on_mouse_down( - MouseButton::Right, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) - .on_mouse_down( - MouseButton::Middle, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) - .on_mouse_up( - MouseButton::Right, - TerminalElement::generic_button_handler( - connection.clone(), - origin, - focus.clone(), - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ) - .on_mouse_up( - MouseButton::Middle, - TerminalElement::generic_button_handler( - connection, - origin, - focus, - move |terminal, origin, e, cx| { - terminal.mouse_up(&e, origin, cx); - }, - ), - ) + self.interactivity.on_mouse_down( + MouseButton::Right, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ); + self.interactivity.on_mouse_down( + MouseButton::Middle, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, _cx| { + terminal.mouse_down(&e, origin); + }, + ), + ); + self.interactivity.on_mouse_up( + MouseButton::Right, + TerminalElement::generic_button_handler( + terminal.clone(), + origin, + focus.clone(), + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ); + self.interactivity.on_mouse_up( + MouseButton::Middle, + TerminalElement::generic_button_handler( + terminal, + origin, + focus, + move |terminal, origin, e, cx| { + terminal.mouse_up(&e, origin, cx); + }, + ), + ); } - - this } } @@ -764,7 +779,12 @@ impl Element for TerminalElement { (layout_id, interactive_state) } - fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext<'_>) { + fn paint( + &mut self, + bounds: Bounds, + state: &mut Self::State, + cx: &mut WindowContext<'_>, + ) { let mut layout = self.compute_layout(bounds, cx); let theme = cx.theme(); @@ -783,33 +803,19 @@ impl Element for TerminalElement { let terminal_focus_handle = self.focus.clone(); let terminal_handle = self.terminal.clone(); - let mut this: TerminalElement = self - .register_mouse_listeners(origin, layout.mode, bounds, cx) - .drag_over::(|style| { - // todo!() why does not it work? z-index of elements? - style.bg(cx.theme().colors().ghost_element_hover) - }) - .on_drop::(move |external_paths, cx| { - cx.focus(&terminal_focus_handle); - let mut new_text = external_paths - .read(cx) - .paths() - .iter() - .map(|path| format!(" {path:?}")) - .join(""); - new_text.push(' '); - terminal_handle.update(cx, |terminal, _| { - // todo!() long paths are not displayed properly albeit the text is there - terminal.paste(&new_text); - }); - }); + self.register_mouse_listeners(origin, layout.mode, bounds, cx); - let interactivity = mem::take(&mut this.interactivity); + // todo!(change this to work in terms of on_drag_move or some such) + // .drag_over::(|style| { + // // todo!() why does not it work? z-index of elements? + // style.bg(cx.theme().colors().ghost_element_hover) + // }) + let mut interactivity = mem::take(&mut self.interactivity); interactivity.paint(bounds, bounds.size, state, cx, |_, _, cx| { - cx.handle_input(&this.focus, terminal_input_handler); + cx.handle_input(&self.focus, terminal_input_handler); - this.register_key_listeners(cx); + self.register_key_listeners(cx); for rect in &layout.rects { rect.paint(origin, &layout, cx); @@ -840,7 +846,7 @@ impl Element for TerminalElement { } }); - if this.cursor_visible { + if self.cursor_visible { cx.with_z_index(3, |cx| { if let Some(cursor) = &layout.cursor { cursor.paint(origin, cx); @@ -848,7 +854,7 @@ impl Element for TerminalElement { }); } - if let Some(element) = layout.hyperlink_tooltip.take() { + if let Some(mut element) = layout.hyperlink_tooltip.take() { let width: AvailableSpace = bounds.size.width.into(); let height: AvailableSpace = bounds.size.height.into(); element.draw(origin, Size { width, height }, cx) diff --git a/crates/theme2/src/themes/andromeda.rs b/crates/theme2/src/themes/andromeda.rs index 33284cdbeee1bf548384ebc689861c9db0c69694..32bccf172a61ab7fcdaff19fcc4b9c973fc753bb 100644 --- a/crates/theme2/src/themes/andromeda.rs +++ b/crates/theme2/src/themes/andromeda.rs @@ -47,6 +47,7 @@ pub fn andromeda() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x3a3f4c77).into()), scrollbar_track_background: Some(rgba(0x23262eff).into()), scrollbar_track_border: Some(rgba(0x1b1d23ff).into()), + editor_foreground: Some(rgba(0xd5ced9ff).into()), editor_background: Some(rgba(0x23262eff).into()), editor_gutter_background: Some(rgba(0x23262eff).into()), editor_line_number: Some(rgba(0x746f77ff).into()), @@ -261,6 +262,7 @@ pub fn andromeda() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x3a3f4c77).into()), scrollbar_track_background: Some(rgba(0x262a33ff).into()), scrollbar_track_border: Some(rgba(0x1b1d23ff).into()), + editor_foreground: Some(rgba(0xd5ced9ff).into()), editor_background: Some(rgba(0x262a33ff).into()), editor_gutter_background: Some(rgba(0x262a33ff).into()), editor_line_number: Some(rgba(0x746f77ff).into()), diff --git a/crates/theme2/src/themes/ayu.rs b/crates/theme2/src/themes/ayu.rs index 0164e4786f88559bb4234d8acce95e463bdac57c..4c5d95123000183ace35e98f4a291762dbd3129e 100644 --- a/crates/theme2/src/themes/ayu.rs +++ b/crates/theme2/src/themes/ayu.rs @@ -46,6 +46,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x8a919966).into()), scrollbar_track_background: Some(rgba(0xf8f9faff).into()), scrollbar_track_border: Some(rgba(0x6b7d8f1f).into()), + editor_foreground: Some(rgba(0x5c6166ff).into()), editor_background: Some(rgba(0xf8f9faff).into()), editor_gutter_background: Some(rgba(0xf8f9faff).into()), editor_line_number: Some(rgba(0x8a919966).into()), @@ -352,6 +353,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x707a8c66).into()), scrollbar_track_background: Some(rgba(0x1f2430ff).into()), scrollbar_track_border: Some(rgba(0x171b24ff).into()), + editor_foreground: Some(rgba(0xcccac2ff).into()), editor_background: Some(rgba(0x1f2430ff).into()), editor_gutter_background: Some(rgba(0x1f2430ff).into()), editor_line_number: Some(rgba(0x8a919966).into()), @@ -658,6 +660,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x565b6666).into()), scrollbar_track_background: Some(rgba(0x0b0e14ff).into()), scrollbar_track_border: Some(rgba(0x1e232bff).into()), + editor_foreground: Some(rgba(0xbfbdb6ff).into()), editor_background: Some(rgba(0x0b0e14ff).into()), editor_gutter_background: Some(rgba(0x0b0e14ff).into()), editor_line_number: Some(rgba(0x6c738099).into()), diff --git a/crates/theme2/src/themes/dracula.rs b/crates/theme2/src/themes/dracula.rs index 5933ec46ffe60c0f5ff686b47312c8b283905faa..3fabb67ec2ce1a1a86d5a934731b2593ceb6426b 100644 --- a/crates/theme2/src/themes/dracula.rs +++ b/crates/theme2/src/themes/dracula.rs @@ -43,6 +43,7 @@ pub fn dracula() -> UserThemeFamily { tab_active_background: Some(rgba(0x282a36ff).into()), scrollbar_track_background: Some(rgba(0x282a36ff).into()), scrollbar_track_border: Some(rgba(0x191a21ff).into()), + editor_foreground: Some(rgba(0xf8f8f2ff).into()), editor_background: Some(rgba(0x282a36ff).into()), editor_gutter_background: Some(rgba(0x282a36ff).into()), editor_line_number: Some(rgba(0x6272a4ff).into()), diff --git a/crates/theme2/src/themes/gruvbox.rs b/crates/theme2/src/themes/gruvbox.rs index 4573f55ca1725532f1e5f5e03d52da7ef7670b41..2c9666553447d94a460fb122f4993a80dc5318a9 100644 --- a/crates/theme2/src/themes/gruvbox.rs +++ b/crates/theme2/src/themes/gruvbox.rs @@ -46,6 +46,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x50494599).into()), scrollbar_track_background: Some(rgba(0x1d2021ff).into()), scrollbar_track_border: Some(rgba(0x1d202100).into()), + editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x1d2021ff).into()), editor_gutter_background: Some(rgba(0x1d2021ff).into()), editor_line_number: Some(rgba(0x665c54ff).into()), @@ -338,6 +339,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x50494599).into()), scrollbar_track_background: Some(rgba(0x282828ff).into()), scrollbar_track_border: Some(rgba(0x28282800).into()), + editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x282828ff).into()), editor_gutter_background: Some(rgba(0x282828ff).into()), editor_line_number: Some(rgba(0x665c54ff).into()), @@ -630,6 +632,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x50494599).into()), scrollbar_track_background: Some(rgba(0x32302fff).into()), scrollbar_track_border: Some(rgba(0x32302f00).into()), + editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x32302fff).into()), editor_gutter_background: Some(rgba(0x32302fff).into()), editor_line_number: Some(rgba(0x665c54ff).into()), @@ -922,6 +925,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()), scrollbar_track_background: Some(rgba(0xf9f5d7ff).into()), scrollbar_track_border: Some(rgba(0xf9f5d700).into()), + editor_foreground: Some(rgba(0x3c3836ff).into()), editor_background: Some(rgba(0xf9f5d7ff).into()), editor_gutter_background: Some(rgba(0xf9f5d7ff).into()), editor_line_number: Some(rgba(0xbdae93ff).into()), @@ -1214,6 +1218,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()), scrollbar_track_background: Some(rgba(0xfbf1c7ff).into()), scrollbar_track_border: Some(rgba(0xfbf1c700).into()), + editor_foreground: Some(rgba(0x3c3836ff).into()), editor_background: Some(rgba(0xfbf1c7ff).into()), editor_gutter_background: Some(rgba(0xfbf1c7ff).into()), editor_line_number: Some(rgba(0xbdae93ff).into()), @@ -1506,6 +1511,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xd5c4a199).into()), scrollbar_track_background: Some(rgba(0xf2e5bcff).into()), scrollbar_track_border: Some(rgba(0xf2e5bc00).into()), + editor_foreground: Some(rgba(0x3c3836ff).into()), editor_background: Some(rgba(0xf2e5bcff).into()), editor_gutter_background: Some(rgba(0xf2e5bcff).into()), editor_line_number: Some(rgba(0xbdae93ff).into()), diff --git a/crates/theme2/src/themes/night_owl.rs b/crates/theme2/src/themes/night_owl.rs index 71c980e6315314d60d3d761c2bc66524ae732242..778dfabeb8e7c5f1bb6c5afa43c0930205157102 100644 --- a/crates/theme2/src/themes/night_owl.rs +++ b/crates/theme2/src/themes/night_owl.rs @@ -46,6 +46,7 @@ pub fn night_owl() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x084d8180).into()), scrollbar_thumb_border: Some(rgba(0x084d8180).into()), scrollbar_track_background: Some(rgba(0x011627ff).into()), + editor_foreground: Some(rgba(0xd6deebff).into()), editor_background: Some(rgba(0x011627ff).into()), editor_gutter_background: Some(rgba(0x011627ff).into()), editor_line_number: Some(rgba(0x4b6479ff).into()), @@ -305,6 +306,7 @@ pub fn night_owl() -> UserThemeFamily { tab_inactive_background: Some(rgba(0xf0f0f0ff).into()), tab_active_background: Some(rgba(0xf6f6f6ff).into()), scrollbar_track_background: Some(rgba(0xfbfbfbff).into()), + editor_foreground: Some(rgba(0x403f53ff).into()), editor_background: Some(rgba(0xfbfbfbff).into()), editor_gutter_background: Some(rgba(0xfbfbfbff).into()), editor_line_number: Some(rgba(0x90a7b2ff).into()), diff --git a/crates/theme2/src/themes/noctis.rs b/crates/theme2/src/themes/noctis.rs index 51b442a57e46de757b735c5a881be30bd02cdc9c..614553593d5ffa031d385dfbd801af10d9892bfc 100644 --- a/crates/theme2/src/themes/noctis.rs +++ b/crates/theme2/src/themes/noctis.rs @@ -47,6 +47,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x008ee633).into()), scrollbar_track_background: Some(rgba(0x07273bff).into()), scrollbar_track_border: Some(rgba(0x07273bff).into()), + editor_foreground: Some(rgba(0xbecfdaff).into()), editor_background: Some(rgba(0x07273bff).into()), editor_gutter_background: Some(rgba(0x07273bff).into()), editor_line_number: Some(rgba(0x4d6c80ff).into()), @@ -324,6 +325,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xeb609133).into()), scrollbar_track_background: Some(rgba(0x322a2dff).into()), scrollbar_track_border: Some(rgba(0x322a2dff).into()), + editor_foreground: Some(rgba(0xcbbec2ff).into()), editor_background: Some(rgba(0x322a2dff).into()), editor_gutter_background: Some(rgba(0x322a2dff).into()), editor_line_number: Some(rgba(0x715b63ff).into()), @@ -601,6 +603,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0xf4f6f6ff).into()), scrollbar_track_border: Some(rgba(0xf4f6f6ff).into()), + editor_foreground: Some(rgba(0x005661ff).into()), editor_background: Some(rgba(0xf4f6f6ff).into()), editor_gutter_background: Some(rgba(0xf4f6f6ff).into()), editor_line_number: Some(rgba(0xa0abacff).into()), @@ -878,6 +881,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0xf2f1f8ff).into()), scrollbar_track_border: Some(rgba(0xf2f1f8ff).into()), + editor_foreground: Some(rgba(0x0c006bff).into()), editor_background: Some(rgba(0xf2f1f8ff).into()), editor_gutter_background: Some(rgba(0xf2f1f8ff).into()), editor_line_number: Some(rgba(0x9d9ab1ff).into()), @@ -1155,6 +1159,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0xfef8ecff).into()), scrollbar_track_border: Some(rgba(0xfef8ecff).into()), + editor_foreground: Some(rgba(0x005661ff).into()), editor_background: Some(rgba(0xfef8ecff).into()), editor_gutter_background: Some(rgba(0xfef8ecff).into()), editor_line_number: Some(rgba(0xa0abacff).into()), @@ -1432,6 +1437,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x3f7fa633).into()), scrollbar_track_background: Some(rgba(0x1b2932ff).into()), scrollbar_track_border: Some(rgba(0x1b2932ff).into()), + editor_foreground: Some(rgba(0xc5cdd3ff).into()), editor_background: Some(rgba(0x1b2932ff).into()), editor_gutter_background: Some(rgba(0x1b2932ff).into()), editor_line_number: Some(rgba(0x5d6e79ff).into()), @@ -1709,6 +1715,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0x052529ff).into()), scrollbar_track_border: Some(rgba(0x052529ff).into()), + editor_foreground: Some(rgba(0xb2cacdff).into()), editor_background: Some(rgba(0x052529ff).into()), editor_gutter_background: Some(rgba(0x052529ff).into()), editor_line_number: Some(rgba(0x4e6b6eff).into()), @@ -1986,6 +1993,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0x031417ff).into()), scrollbar_track_border: Some(rgba(0x031417ff).into()), + editor_foreground: Some(rgba(0xb2cacdff).into()), editor_background: Some(rgba(0x031417ff).into()), editor_gutter_background: Some(rgba(0x031417ff).into()), editor_line_number: Some(rgba(0x4e6b6eff).into()), @@ -2263,6 +2271,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6a90955b).into()), scrollbar_track_background: Some(rgba(0x031417ff).into()), scrollbar_track_border: Some(rgba(0x031417ff).into()), + editor_foreground: Some(rgba(0xb2cacdff).into()), editor_background: Some(rgba(0x031417ff).into()), editor_gutter_background: Some(rgba(0x031417ff).into()), editor_line_number: Some(rgba(0x4e6b6eff).into()), @@ -2540,6 +2549,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x7060eb33).into()), scrollbar_track_background: Some(rgba(0x292640ff).into()), scrollbar_track_border: Some(rgba(0x292640ff).into()), + editor_foreground: Some(rgba(0xc5c2d6ff).into()), editor_background: Some(rgba(0x292640ff).into()), editor_gutter_background: Some(rgba(0x292640ff).into()), editor_line_number: Some(rgba(0x5c5973ff).into()), @@ -2817,6 +2827,7 @@ pub fn noctis() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0xa660eb33).into()), scrollbar_track_background: Some(rgba(0x30243dff).into()), scrollbar_track_border: Some(rgba(0x30243dff).into()), + editor_foreground: Some(rgba(0xccbfd9ff).into()), editor_background: Some(rgba(0x30243dff).into()), editor_gutter_background: Some(rgba(0x30243dff).into()), editor_line_number: Some(rgba(0x665973ff).into()), diff --git a/crates/theme2/src/themes/nord.rs b/crates/theme2/src/themes/nord.rs index 949926c6857d0b0b0d183328c8e837f0007c7afc..f3d03b38892628aa06f21b90eca69c169fca9921 100644 --- a/crates/theme2/src/themes/nord.rs +++ b/crates/theme2/src/themes/nord.rs @@ -46,6 +46,7 @@ pub fn nord() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x434c5e99).into()), scrollbar_track_background: Some(rgba(0x2e3440ff).into()), scrollbar_track_border: Some(rgba(0x3b4252ff).into()), + editor_foreground: Some(rgba(0xd8dee9ff).into()), editor_background: Some(rgba(0x2e3440ff).into()), editor_gutter_background: Some(rgba(0x2e3440ff).into()), editor_line_number: Some(rgba(0x4c566aff).into()), diff --git a/crates/theme2/src/themes/palenight.rs b/crates/theme2/src/themes/palenight.rs index ab47cc104643bc63fba6a85a778474b2fb4925c0..501a678577d69a7318b1fd880c81c7790f0b83e5 100644 --- a/crates/theme2/src/themes/palenight.rs +++ b/crates/theme2/src/themes/palenight.rs @@ -46,6 +46,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), + editor_foreground: Some(rgba(0xbfc7d5ff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), @@ -331,6 +332,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), + editor_foreground: Some(rgba(0xbfc7d5ff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), @@ -616,6 +618,7 @@ pub fn palenight() -> UserThemeFamily { scrollbar_thumb_hover_background: Some(rgba(0x694ca4cc).into()), scrollbar_thumb_border: Some(rgba(0x694ca466).into()), scrollbar_track_background: Some(rgba(0x292d3eff).into()), + editor_foreground: Some(rgba(0xbfc7d5ff).into()), editor_background: Some(rgba(0x292d3eff).into()), editor_gutter_background: Some(rgba(0x292d3eff).into()), editor_line_number: Some(rgba(0x4c5374ff).into()), diff --git a/crates/theme2/src/themes/rose_pine.rs b/crates/theme2/src/themes/rose_pine.rs index d9091df347dc891d628d601ccea2b8a7c965cf56..a09364405f02e9adb4d74a6b16f82f9dc386db9e 100644 --- a/crates/theme2/src/themes/rose_pine.rs +++ b/crates/theme2/src/themes/rose_pine.rs @@ -47,6 +47,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6e6a8633).into()), scrollbar_track_background: Some(rgba(0x191724ff).into()), scrollbar_track_border: Some(rgba(0x6e6a8666).into()), + editor_foreground: Some(rgba(0xe0def4ff).into()), editor_background: Some(rgba(0x191724ff).into()), editor_gutter_background: Some(rgba(0x191724ff).into()), editor_line_number: Some(rgba(0x908caaff).into()), @@ -306,6 +307,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x817c9c26).into()), scrollbar_track_background: Some(rgba(0x232136ff).into()), scrollbar_track_border: Some(rgba(0x817c9c4d).into()), + editor_foreground: Some(rgba(0xe0def4ff).into()), editor_background: Some(rgba(0x232136ff).into()), editor_gutter_background: Some(rgba(0x232136ff).into()), editor_line_number: Some(rgba(0x908caaff).into()), @@ -565,6 +567,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_border: Some(rgba(0x6e6a8614).into()), scrollbar_track_background: Some(rgba(0xfaf4edff).into()), scrollbar_track_border: Some(rgba(0x6e6a8626).into()), + editor_foreground: Some(rgba(0x575279ff).into()), editor_background: Some(rgba(0xfaf4edff).into()), editor_gutter_background: Some(rgba(0xfaf4edff).into()), editor_line_number: Some(rgba(0x797593ff).into()), diff --git a/crates/theme2/src/themes/solarized.rs b/crates/theme2/src/themes/solarized.rs index 25799bea280e0c4f00becefdfcda76b0b4b49ff3..9807a6cd3de000d79bd9b30fc21ffad5e9562906 100644 --- a/crates/theme2/src/themes/solarized.rs +++ b/crates/theme2/src/themes/solarized.rs @@ -42,6 +42,7 @@ pub fn solarized() -> UserThemeFamily { tab_inactive_background: Some(rgba(0x004052ff).into()), tab_active_background: Some(rgba(0x002b37ff).into()), scrollbar_track_background: Some(rgba(0x002b36ff).into()), + editor_foreground: Some(rgba(0xbbbbbbff).into()), editor_background: Some(rgba(0x002b36ff).into()), editor_gutter_background: Some(rgba(0x002b36ff).into()), editor_line_number: Some(rgba(0x566c74ff).into()), @@ -307,6 +308,7 @@ pub fn solarized() -> UserThemeFamily { tab_inactive_background: Some(rgba(0xd3cbb7ff).into()), tab_active_background: Some(rgba(0xfdf6e3ff).into()), scrollbar_track_background: Some(rgba(0xfdf6e3ff).into()), + editor_foreground: Some(rgba(0x333333ff).into()), editor_background: Some(rgba(0xfdf6e3ff).into()), editor_gutter_background: Some(rgba(0xfdf6e3ff).into()), editor_line_number: Some(rgba(0x9ca8a6ff).into()), diff --git a/crates/theme_importer/src/theme_printer.rs b/crates/theme_importer/src/theme_printer.rs index 78c8719105576f9efb7b6bca26dfcda3f07f5873..4726c90c6d1077da8f8f1c947bff49bac9bc9d57 100644 --- a/crates/theme_importer/src/theme_printer.rs +++ b/crates/theme_importer/src/theme_printer.rs @@ -201,6 +201,7 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> { self.0.scrollbar_track_background, ), ("scrollbar_track_border", self.0.scrollbar_track_border), + ("editor_foreground", self.0.editor_foreground), ("editor_background", self.0.editor_background), ("editor_gutter_background", self.0.editor_gutter_background), ( diff --git a/crates/theme_importer/src/vscode/converter.rs b/crates/theme_importer/src/vscode/converter.rs index c9a07a1f7f2a78cb09da7e5c09d21e5fab02cc8a..46d9c3d08608b0f3c060c86ae89cfe464bb8bb07 100644 --- a/crates/theme_importer/src/vscode/converter.rs +++ b/crates/theme_importer/src/vscode/converter.rs @@ -132,6 +132,11 @@ impl VsCodeThemeConverter { .as_ref() .traverse(|color| try_parse_color(&color))?; + let vscode_editor_foreground = vscode_colors + .editor_foreground + .as_ref() + .traverse(|color| try_parse_color(&color))?; + let vscode_editor_background = vscode_colors .editor_background .as_ref() @@ -142,6 +147,16 @@ impl VsCodeThemeConverter { .as_ref() .traverse(|color| try_parse_color(&color))?; + let vscode_token_colors_foreground = self + .theme + .token_colors + .iter() + .find(|token_color| token_color.scope.is_none()) + .and_then(|token_color| token_color.settings.foreground.as_ref()) + .traverse(|color| try_parse_color(&color)) + .ok() + .flatten(); + Ok(ThemeColorsRefinement { border: vscode_panel_border, border_variant: vscode_panel_border, @@ -197,16 +212,7 @@ impl VsCodeThemeConverter { .foreground .as_ref() .traverse(|color| try_parse_color(&color))? - .or_else(|| { - self.theme - .token_colors - .iter() - .find(|token_color| token_color.scope.is_none()) - .and_then(|token_color| token_color.settings.foreground.as_ref()) - .traverse(|color| try_parse_color(&color)) - .ok() - .flatten() - }), + .or(vscode_token_colors_foreground), text_muted: vscode_colors .tab_inactive_foreground .as_ref() @@ -226,6 +232,7 @@ impl VsCodeThemeConverter { .as_ref() .traverse(|color| try_parse_color(&color))? .or(vscode_editor_background), + editor_foreground: vscode_editor_foreground.or(vscode_token_colors_foreground), editor_background: vscode_editor_background, editor_gutter_background: vscode_editor_background, editor_line_number: vscode_colors diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index b68aa579c026d518f33e9eabc94824270c8104e7..6876630100e95d3ec23252e80787642263783d56 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -50,6 +50,7 @@ pub enum Icon { CopilotError, CopilotDisabled, Dash, + Disconnected, Envelope, ExternalLink, ExclamationTriangle, @@ -129,6 +130,7 @@ impl Icon { Icon::CopilotError => "icons/copilot_error.svg", Icon::CopilotDisabled => "icons/copilot_disabled.svg", Icon::Dash => "icons/dash.svg", + Icon::Disconnected => "icons/disconnected.svg", Icon::Envelope => "icons/feedback.svg", Icon::ExclamationTriangle => "icons/warning.svg", Icon::ExternalLink => "icons/external_link.svg", diff --git a/crates/ui2/src/components/popover_menu.rs b/crates/ui2/src/components/popover_menu.rs index 4b5144e7c7de468e823cd7e60d062083ea065386..0f2fa6d23f147ba846695d1177ca5189f9130b3e 100644 --- a/crates/ui2/src/components/popover_menu.rs +++ b/crates/ui2/src/components/popover_menu.rs @@ -182,12 +182,12 @@ impl Element for PopoverMenu { } fn paint( - self, + &mut self, _: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, ) { - if let Some(child) = element_state.child_element.take() { + if let Some(mut child) = element_state.child_element.take() { child.paint(cx); } @@ -195,7 +195,7 @@ impl Element for PopoverMenu { element_state.child_bounds = Some(cx.layout_bounds(child_layout_id)); } - if let Some(menu) = element_state.menu_element.take() { + if let Some(mut menu) = element_state.menu_element.take() { menu.paint(cx); if let Some(child_bounds) = element_state.child_bounds { diff --git a/crates/ui2/src/components/right_click_menu.rs b/crates/ui2/src/components/right_click_menu.rs index 19031b2be70f7953e21a1305dd8957912ddfb63a..a3a454d652fa4dcbea48a6c8114c7c83e390c555 100644 --- a/crates/ui2/src/components/right_click_menu.rs +++ b/crates/ui2/src/components/right_click_menu.rs @@ -112,21 +112,21 @@ impl Element for RightClickMenu { } fn paint( - self, + &mut self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext, ) { - if let Some(child) = element_state.child_element.take() { + if let Some(mut child) = element_state.child_element.take() { child.paint(cx); } - if let Some(menu) = element_state.menu_element.take() { + if let Some(mut menu) = element_state.menu_element.take() { menu.paint(cx); return; } - let Some(builder) = self.menu_builder else { + let Some(builder) = self.menu_builder.take() else { return; }; let menu = element_state.menu.clone(); diff --git a/crates/ui2/src/styles/elevation.rs b/crates/ui2/src/styles/elevation.rs index 88b2ff2e56aeaf5d2e212fca7218afa3940a4742..7b3835c2e54b3e60528ab212311d245dfd7223d3 100644 --- a/crates/ui2/src/styles/elevation.rs +++ b/crates/ui2/src/styles/elevation.rs @@ -20,14 +20,14 @@ pub enum ElevationIndex { } impl ElevationIndex { - pub fn z_index(self) -> u32 { + pub fn z_index(self) -> u8 { match self { ElevationIndex::Background => 0, - ElevationIndex::Surface => 100, - ElevationIndex::ElevatedSurface => 200, - ElevationIndex::Wash => 250, - ElevationIndex::ModalSurface => 300, - ElevationIndex::DraggedElement => 900, + ElevationIndex::Surface => 42, + ElevationIndex::ElevatedSurface => 84, + ElevationIndex::Wash => 126, + ElevationIndex::ModalSurface => 168, + ElevationIndex::DraggedElement => 210, } } diff --git a/crates/vcs_menu2/src/lib.rs b/crates/vcs_menu2/src/lib.rs index e867e04dcdb96229eb7513f891d60cc31ce1ec26..ca3b685aa60ad93f63cc2ba203fa52a184ff564b 100644 --- a/crates/vcs_menu2/src/lib.rs +++ b/crates/vcs_menu2/src/lib.rs @@ -65,8 +65,13 @@ impl ModalBranchList { ) -> Result<()> { // Modal branch picker has a longer trailoff than a popover one. let delegate = BranchListDelegate::new(workspace, cx.view().clone(), 70, cx)?; - workspace.toggle_modal(cx, |cx| ModalBranchList { - picker: cx.build_view(|cx| Picker::new(delegate, cx)), + workspace.toggle_modal(cx, |cx| { + let modal = ModalBranchList { + picker: cx.build_view(|cx| Picker::new(delegate, cx)), + }; + cx.subscribe(&modal.picker, |_, _, _, cx| cx.emit(DismissEvent)) + .detach(); + modal }); Ok(()) diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index f9e294763b3314cfb0b01930c0d4581c76be000e..f76bb82177084f2e638e31e0e88bad8a3552a182 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -41,7 +41,7 @@ pub trait Panel: FocusableView + EventEmitter { } pub trait PanelHandle: Send + Sync { - fn entity_id(&self) -> EntityId; + fn panel_id(&self) -> EntityId; fn persistent_name(&self) -> &'static str; fn position(&self, cx: &WindowContext) -> DockPosition; fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool; @@ -62,7 +62,7 @@ impl PanelHandle for View where T: Panel, { - fn entity_id(&self) -> EntityId { + fn panel_id(&self) -> EntityId { Entity::entity_id(self) } @@ -135,7 +135,7 @@ pub struct Dock { is_open: bool, active_panel_index: usize, focus_handle: FocusHandle, - focus_subscription: Subscription, + _focus_subscription: Subscription, } impl FocusableView for Dock { @@ -187,7 +187,6 @@ struct PanelEntry { pub struct PanelButtons { dock: View, - workspace: WeakView, } impl Dock { @@ -204,7 +203,7 @@ impl Dock { active_panel_index: 0, is_open: false, focus_handle, - focus_subscription, + _focus_subscription: focus_subscription, } } @@ -261,7 +260,7 @@ impl Dock { pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext) { for entry in &mut self.panel_entries { - if entry.panel.entity_id() == panel.entity_id() { + if entry.panel.panel_id() == panel.entity_id() { if zoomed != entry.panel.is_zoomed(cx) { entry.panel.set_zoomed(zoomed, cx); } @@ -309,7 +308,7 @@ impl Dock { let was_visible = this.is_open() && this.visible_panel().map_or(false, |active_panel| { - active_panel.entity_id() == Entity::entity_id(&panel) + active_panel.panel_id() == Entity::entity_id(&panel) }); this.remove_panel(&panel, cx); @@ -351,7 +350,7 @@ impl Dock { if let Some(ix) = this .panel_entries .iter() - .position(|entry| entry.panel.entity_id() == Entity::entity_id(&panel)) + .position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel)) { this.set_open(true, cx); this.activate_panel(ix, cx); @@ -361,7 +360,7 @@ impl Dock { PanelEvent::Close => { if this .visible_panel() - .map_or(false, |p| p.entity_id() == Entity::entity_id(&panel)) + .map_or(false, |p| p.panel_id() == Entity::entity_id(&panel)) { this.set_open(false, cx); } @@ -389,7 +388,7 @@ impl Dock { if let Some(panel_ix) = self .panel_entries .iter() - .position(|entry| entry.panel.entity_id() == Entity::entity_id(panel)) + .position(|entry| entry.panel.panel_id() == Entity::entity_id(panel)) { if panel_ix == self.active_panel_index { self.active_panel_index = 0; @@ -450,7 +449,7 @@ impl Dock { pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option { self.panel_entries .iter() - .find(|entry| entry.panel.entity_id() == panel.entity_id()) + .find(|entry| entry.panel.panel_id() == panel.panel_id()) .map(|entry| entry.panel.size(cx)) } @@ -487,13 +486,12 @@ impl Render for Dock { if let Some(entry) = self.visible_entry() { let size = entry.panel.size(cx); - let mut pre_resize_handle = None; - let mut post_resize_handle = None; let position = self.position; - let handler = div() + let mut handle = div() .id("resize-handle") - .bg(cx.theme().colors().border) - .on_drag(move |cx| cx.build_view(|_| DraggedDock(position))) + .on_drag(DraggedDock(position), |dock, cx| { + cx.build_view(|_| dock.clone()) + }) .on_click(cx.listener(|v, e: &ClickEvent, cx| { if e.down.button == MouseButton::Left && e.down.click_count == 2 { v.resize_active_panel(None, cx) @@ -505,16 +503,31 @@ impl Render for Dock { match self.position() { DockPosition::Left => { - post_resize_handle = - Some(handler.min_w(HANDLE_SIZE).h_full().cursor_col_resize()) + handle = handle + .absolute() + .right(px(0.)) + .top(px(0.)) + .h_full() + .w(HANDLE_SIZE) + .cursor_col_resize(); } DockPosition::Bottom => { - pre_resize_handle = - Some(handler.w_full().min_h(HANDLE_SIZE).cursor_row_resize()) + handle = handle + .absolute() + .top(px(0.)) + .left(px(0.)) + .w_full() + .h(HANDLE_SIZE) + .cursor_row_resize(); } DockPosition::Right => { - pre_resize_handle = - Some(handler.min_w(HANDLE_SIZE).h_full().cursor_col_resize()) + handle = handle + .absolute() + .top(px(0.)) + .left(px(0.)) + .w_full() + .h(HANDLE_SIZE) + .cursor_col_resize(); } } @@ -530,16 +543,15 @@ impl Render for Dock { DockPosition::Right => this.border_l(), DockPosition::Bottom => this.border_t(), }) - .children(pre_resize_handle) .child( div() .map(|this| match self.position().axis() { - Axis::Horizontal => this.min_w(px(size) - HANDLE_SIZE).h_full(), - Axis::Vertical => this.min_h(px(size) - HANDLE_SIZE).w_full(), + Axis::Horizontal => this.min_w(px(size)).h_full(), + Axis::Vertical => this.min_h(px(size)).w_full(), }) .child(entry.panel.to_any()), ) - .children(post_resize_handle) + .child(handle) } else { div() } @@ -547,166 +559,12 @@ impl Render for Dock { } impl PanelButtons { - pub fn new( - dock: View, - workspace: WeakView, - cx: &mut ViewContext, - ) -> Self { + pub fn new(dock: View, cx: &mut ViewContext) -> Self { cx.observe(&dock, |_, _, cx| cx.notify()).detach(); - Self { dock, workspace } + Self { dock } } } -// impl Render for PanelButtons { -// type Element = (); - -// fn render(&mut self, cx: &mut ViewContext) -> Self::Element { -// todo!("") -// } - -// fn ui_name() -> &'static str { -// "PanelButtons" -// } - -// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { -// let theme = &settings::get::(cx).theme; -// let tooltip_style = theme.tooltip.clone(); -// let theme = &theme.workspace.status_bar.panel_buttons; -// let button_style = theme.button.clone(); -// let dock = self.dock.read(cx); -// let active_ix = dock.active_panel_index; -// let is_open = dock.is_open; -// let dock_position = dock.position; -// let group_style = match dock_position { -// DockPosition::Left => theme.group_left, -// DockPosition::Bottom => theme.group_bottom, -// DockPosition::Right => theme.group_right, -// }; -// let menu_corner = match dock_position { -// DockPosition::Left => AnchorCorner::BottomLeft, -// DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight, -// }; - -// let panels = dock -// .panel_entries -// .iter() -// .map(|item| (item.panel.clone(), item.context_menu.clone())) -// .collect::>(); -// Flex::row() -// .with_children(panels.into_iter().enumerate().filter_map( -// |(panel_ix, (view, context_menu))| { -// let icon_path = view.icon_path(cx)?; -// let is_active = is_open && panel_ix == active_ix; -// let (tooltip, tooltip_action) = if is_active { -// ( -// format!("Close {} dock", dock_position.to_label()), -// Some(match dock_position { -// DockPosition::Left => crate::ToggleLeftDock.boxed_clone(), -// DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(), -// DockPosition::Right => crate::ToggleRightDock.boxed_clone(), -// }), -// ) -// } else { -// view.icon_tooltip(cx) -// }; -// Some( -// Stack::new() -// .with_child( -// MouseEventHandler::new::(panel_ix, cx, |state, cx| { -// let style = button_style.in_state(is_active); - -// let style = style.style_for(state); -// Flex::row() -// .with_child( -// Svg::new(icon_path) -// .with_color(style.icon_color) -// .constrained() -// .with_width(style.icon_size) -// .aligned(), -// ) -// .with_children(if let Some(label) = view.icon_label(cx) { -// Some( -// Label::new(label, style.label.text.clone()) -// .contained() -// .with_style(style.label.container) -// .aligned(), -// ) -// } else { -// None -// }) -// .constrained() -// .with_height(style.icon_size) -// .contained() -// .with_style(style.container) -// }) -// .with_cursor_style(CursorStyle::PointingHand) -// .on_click(MouseButton::Left, { -// let tooltip_action = -// tooltip_action.as_ref().map(|action| action.boxed_clone()); -// move |_, this, cx| { -// if let Some(tooltip_action) = &tooltip_action { -// let window = cx.window(); -// let view_id = this.workspace.id(); -// let tooltip_action = tooltip_action.boxed_clone(); -// cx.spawn(|_, mut cx| async move { -// window.dispatch_action( -// view_id, -// &*tooltip_action, -// &mut cx, -// ); -// }) -// .detach(); -// } -// } -// }) -// .on_click(MouseButton::Right, { -// let view = view.clone(); -// let menu = context_menu.clone(); -// move |_, _, cx| { -// const POSITIONS: [DockPosition; 3] = [ -// DockPosition::Left, -// DockPosition::Right, -// DockPosition::Bottom, -// ]; - -// menu.update(cx, |menu, cx| { -// let items = POSITIONS -// .into_iter() -// .filter(|position| { -// *position != dock_position -// && view.position_is_valid(*position, cx) -// }) -// .map(|position| { -// let view = view.clone(); -// ContextMenuItem::handler( -// format!("Dock {}", position.to_label()), -// move |cx| view.set_position(position, cx), -// ) -// }) -// .collect(); -// menu.show(Default::default(), menu_corner, items, cx); -// }) -// } -// }) -// .with_tooltip::( -// panel_ix, -// tooltip, -// tooltip_action, -// tooltip_style.clone(), -// cx, -// ), -// ) -// .with_child(ChildView::new(&context_menu, cx)), -// ) -// }, -// )) -// .contained() -// .with_style(group_style) -// .into_any() -// } -// } - -// here be kittens impl Render for PanelButtons { type Element = Div; diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index c0242ffa170aedfd00516a0ef031988bee16cdcd..4eadd4e7a3cbc08d33123c6f30f118bbf0c4479d 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -760,8 +760,9 @@ pub mod test { use super::{Item, ItemEvent}; use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ - AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView, - IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView, + AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, Focusable, + FocusableView, InteractiveElement, IntoElement, Model, Render, SharedString, Task, View, + ViewContext, VisualContext, WeakView, }; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; use std::{any::Any, cell::Cell, path::Path}; @@ -909,17 +910,17 @@ pub mod test { } impl Render for TestItem { - type Element = Div; + type Element = Focusable
; fn render(&mut self, _: &mut ViewContext) -> Self::Element { - gpui::div() + gpui::div().track_focus(&self.focus_handle) } } impl EventEmitter for TestItem {} impl FocusableView for TestItem { - fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { + fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle { self.focus_handle.clone() } } @@ -941,8 +942,8 @@ pub mod test { fn tab_content( &self, detail: Option, - selected: bool, - cx: &ui::prelude::WindowContext, + _selected: bool, + _cx: &ui::prelude::WindowContext, ) -> AnyElement { self.tab_detail.set(detail); gpui::div().into_any_element() diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index a428ba3e18352ec7750f13ecd19289b5682a75be..ff1de6548d7c7be9ff137f99ee0a79b54de63444 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -5,7 +5,7 @@ use gpui::{ use ui::{h_stack, v_stack}; pub trait ModalView: ManagedView { - fn on_before_dismiss(&mut self, cx: &mut ViewContext) -> bool { + fn on_before_dismiss(&mut self, _: &mut ViewContext) -> bool { true } } @@ -27,7 +27,7 @@ impl ModalViewHandle for View { pub struct ActiveModal { modal: Box, - subscription: Subscription, + _subscription: Subscription, previous_focus_handle: Option, focus_handle: FocusHandle, } @@ -63,7 +63,7 @@ impl ModalLayer { { self.active_modal = Some(ActiveModal { modal: Box::new(new_modal.clone()), - subscription: cx.subscribe(&new_modal, |this, modal, _: &DismissEvent, cx| { + _subscription: cx.subscribe(&new_modal, |this, _, _: &DismissEvent, cx| { this.hide_modal(cx); }), previous_focus_handle: cx.focused(), @@ -116,7 +116,7 @@ impl Render for ModalLayer { .size_full() .top_0() .left_0() - .z_index(400) + .z_index(169) .child( v_stack() .h(px(0.0)) diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index 63475c2aba64100155311136630f6e7d57537950..a692388a06afe1eb4f88eb063d37b1a4e4e333d1 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -104,12 +104,9 @@ impl Workspace { }) { let notification = build_notification(cx); - cx.subscribe( - ¬ification, - move |this, handle, event: &DismissEvent, cx| { - this.dismiss_notification_internal(type_id, id, cx); - }, - ) + cx.subscribe(¬ification, move |this, _, _: &DismissEvent, cx| { + this.dismiss_notification_internal(type_id, id, cx); + }) .detach(); self.notifications .push((type_id, id, Box::new(notification))); @@ -173,21 +170,15 @@ impl Workspace { pub mod simple_message_notification { use gpui::{ - div, AnyElement, AppContext, DismissEvent, Div, EventEmitter, InteractiveElement, - ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, TextStyle, - ViewContext, + div, DismissEvent, Div, EventEmitter, InteractiveElement, ParentElement, Render, + SharedString, StatefulInteractiveElement, Styled, ViewContext, }; use std::sync::Arc; use ui::prelude::*; use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt}; - enum NotificationMessage { - Text(SharedString), - Element(fn(TextStyle, &AppContext) -> AnyElement), - } - pub struct MessageNotification { - message: NotificationMessage, + message: SharedString, on_click: Option)>>, click_message: Option, } @@ -200,23 +191,12 @@ pub mod simple_message_notification { S: Into, { Self { - message: NotificationMessage::Text(message.into()), + message: message.into(), on_click: None, click_message: None, } } - // not needed I think (only for the "new panel" toast, which is outdated now) - // pub fn new_element( - // message: fn(TextStyle, &AppContext) -> AnyElement, - // ) -> MessageNotification { - // Self { - // message: NotificationMessage::Element(message), - // on_click: None, - // click_message: None, - // } - // } - pub fn with_click_message(mut self, message: S) -> Self where S: Into, @@ -248,18 +228,13 @@ pub mod simple_message_notification { .child( h_stack() .justify_between() - .child(div().max_w_80().child(match &self.message { - NotificationMessage::Text(text) => Label::new(text.clone()), - NotificationMessage::Element(element) => { - todo!() - } - })) + .child(div().max_w_80().child(Label::new(self.message.clone()))) .child( div() .id("cancel") .child(IconElement::new(Icon::Close)) .cursor_pointer() - .on_click(cx.listener(|this, event, cx| this.dismiss(cx))), + .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), ), ) .children(self.click_message.iter().map(|message| { diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index a55469fbadd096bc3a041b3ebb3cc12614be871d..11588212ef76e3891c57fc5a14778fd8e48a9e37 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -7,10 +7,11 @@ use crate::{ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyWeakView, AppContext, - AsyncWindowContext, DismissEvent, Div, EntityId, EventEmitter, FocusHandle, Focusable, - FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render, - ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, + actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AppContext, + AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, FocusHandle, + Focusable, FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, + Render, ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use parking_lot::Mutex; use project::{Project, ProjectEntryId, ProjectPath}; @@ -28,8 +29,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, - Indicator, Label, Tab, TabBar, TabPosition, Tooltip, + prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label, + Tab, TabBar, TabPosition, Tooltip, }; use ui::{v_stack, ContextMenu}; use util::{maybe, truncate_and_remove_front, ResultExt}; @@ -164,11 +165,6 @@ impl fmt::Debug for Event { } } -struct FocusedView { - view: AnyWeakView, - focus_handle: FocusHandle, -} - pub struct Pane { focus_handle: FocusHandle, items: Vec>, @@ -184,10 +180,11 @@ pub struct Pane { // tab_context_menu: ViewHandle, workspace: WeakView, project: Model, + drag_split_direction: Option, // can_drop: Rc, &WindowContext) -> bool>, can_split: bool, // render_tab_bar_buttons: Rc) -> AnyElement>, - subscriptions: Vec, + _subscriptions: Vec, tab_bar_scroll_handle: ScrollHandle, } @@ -231,6 +228,7 @@ pub struct NavigationEntry { pub timestamp: usize, } +#[derive(Clone)] struct DraggedTab { pub pane: View, pub ix: usize, @@ -365,6 +363,7 @@ impl Pane { new_item_menu: None, split_item_menu: None, tab_bar_scroll_handle: ScrollHandle::new(), + drag_split_direction: None, // tab_bar_context_menu: TabBarContextMenu { // kind: TabBarContextMenuKind::New, // handle: context_menu, @@ -431,14 +430,10 @@ impl Pane { // }) // .into_any() // }), - subscriptions, + _subscriptions: subscriptions, } } - pub(crate) fn workspace(&self) -> &WeakView { - &self.workspace - } - pub fn has_focus(&self, cx: &WindowContext) -> bool { // todo!(); // inline this manually self.focus_handle.contains_focused(cx) @@ -1467,21 +1462,6 @@ impl Pane { let label = item.tab_content(Some(detail), is_active, cx); let close_side = &ItemSettings::get_global(cx).close_position; - let (text_color, tab_bg, tab_hover_bg, tab_active_bg) = match ix == self.active_item_index { - false => ( - cx.theme().colors().text_muted, - cx.theme().colors().tab_inactive_background, - cx.theme().colors().ghost_element_hover, - cx.theme().colors().ghost_element_active, - ), - true => ( - cx.theme().colors().text, - cx.theme().colors().tab_active_background, - cx.theme().colors().element_hover, - cx.theme().colors().element_active, - ), - }; - let indicator = maybe!({ let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) { (true, _) => Color::Warning, @@ -1497,56 +1477,57 @@ impl Pane { let is_last_item = ix == self.items.len() - 1; let position_relative_to_active_item = ix.cmp(&self.active_item_index); - let tab = - Tab::new(ix) - .position(if is_first_item { - TabPosition::First - } else if is_last_item { - TabPosition::Last - } else { - TabPosition::Middle(position_relative_to_active_item) - }) - .close_side(match close_side { - ClosePosition::Left => ui::TabCloseSide::Start, - ClosePosition::Right => ui::TabCloseSide::End, - }) - .selected(is_active) - .on_click(cx.listener(move |pane: &mut Self, event, cx| { - pane.activate_item(ix, true, true, cx) - })) - .on_drag({ - let pane = cx.view().clone(); - move |cx| { - cx.build_view(|cx| DraggedTab { - pane: pane.clone(), - detail, - item_id, - is_active, - ix, - }) - } - }) - .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) - .on_drop( - cx.listener(move |this, dragged_tab: &View, cx| { - this.handle_tab_drop(dragged_tab, ix, cx) - }), - ) - .when_some(item.tab_tooltip_text(cx), |tab, text| { - tab.tooltip(move |cx| Tooltip::text(text.clone(), cx)) - }) - .start_slot::(indicator) - .end_slot( - IconButton::new("close tab", Icon::Close) - .icon_color(Color::Muted) - .size(ButtonSize::None) - .icon_size(IconSize::XSmall) - .on_click(cx.listener(move |pane, _, cx| { - pane.close_item_by_id(item_id, SaveIntent::Close, cx) - .detach_and_log_err(cx); - })), - ) - .child(label); + let tab = Tab::new(ix) + .position(if is_first_item { + TabPosition::First + } else if is_last_item { + TabPosition::Last + } else { + TabPosition::Middle(position_relative_to_active_item) + }) + .close_side(match close_side { + ClosePosition::Left => ui::TabCloseSide::Start, + ClosePosition::Right => ui::TabCloseSide::End, + }) + .selected(is_active) + .on_click( + cx.listener(move |pane: &mut Self, _, cx| pane.activate_item(ix, true, true, cx)), + ) + .on_drag( + DraggedTab { + pane: cx.view().clone(), + detail, + item_id, + is_active, + ix, + }, + |tab, cx| cx.build_view(|_| tab.clone()), + ) + .drag_over::(|tab| tab.bg(cx.theme().colors().tab_active_background)) + .drag_over::(|tab| tab.bg(gpui::red())) + .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.drag_split_direction = None; + this.handle_tab_drop(dragged_tab, ix, cx) + })) + .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.drag_split_direction = None; + this.handle_project_entry_drop(entry_id, cx) + })) + .when_some(item.tab_tooltip_text(cx), |tab, text| { + tab.tooltip(move |cx| Tooltip::text(text.clone(), cx)) + }) + .start_slot::(indicator) + .end_slot( + IconButton::new("close tab", Icon::Close) + .icon_color(Color::Muted) + .size(ButtonSize::None) + .icon_size(IconSize::XSmall) + .on_click(cx.listener(move |pane, _, cx| { + pane.close_item_by_id(item_id, SaveIntent::Close, cx) + .detach_and_log_err(cx); + })), + ) + .child(label); let single_entry_to_resolve = { let item_entries = self.items[ix].project_entry_ids(cx); @@ -1616,12 +1597,12 @@ impl Pane { IconButton::new("plus", Icon::Plus) .icon_size(IconSize::Small) .on_click(cx.listener(|this, _, cx| { - let menu = ContextMenu::build(cx, |menu, cx| { + let menu = ContextMenu::build(cx, |menu, _| { menu.action("New File", NewFile.boxed_clone()) .action("New Terminal", NewCenterTerminal.boxed_clone()) .action("New Search", NewSearch.boxed_clone()) }); - cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| { + cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| { this.focus(cx); this.new_item_menu = None; }) @@ -1640,13 +1621,13 @@ impl Pane { IconButton::new("split", Icon::Split) .icon_size(IconSize::Small) .on_click(cx.listener(|this, _, cx| { - let menu = ContextMenu::build(cx, |menu, cx| { + let menu = ContextMenu::build(cx, |menu, _| { menu.action("Split Right", SplitRight.boxed_clone()) .action("Split Left", SplitLeft.boxed_clone()) .action("Split Up", SplitUp.boxed_clone()) .action("Split Down", SplitDown.boxed_clone()) }); - cx.subscribe(&menu, |this, _, event: &DismissEvent, cx| { + cx.subscribe(&menu, |this, _, _: &DismissEvent, cx| { this.focus(cx); this.split_item_menu = None; }) @@ -1677,11 +1658,15 @@ impl Pane { .drag_over::(|bar| { bar.bg(cx.theme().colors().tab_active_background) }) - .on_drop( - cx.listener(move |this, dragged_tab: &View, cx| { - this.handle_tab_drop(dragged_tab, this.items.len(), cx) - }), - ), + .drag_over::(|bar| bar.bg(gpui::red())) + .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| { + this.drag_split_direction = None; + this.handle_tab_drop(dragged_tab, this.items.len(), cx) + })) + .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| { + this.drag_split_direction = None; + this.handle_project_entry_drop(entry_id, cx) + })), ) } @@ -1741,47 +1726,70 @@ impl Pane { self.zoomed } + fn handle_drag_move(&mut self, event: &DragMoveEvent, cx: &mut ViewContext) { + let edge_width = cx.rem_size() * 8; + let cursor = event.event.position; + let direction = if cursor.x < event.bounds.left() + edge_width { + Some(SplitDirection::Left) + } else if cursor.x > event.bounds.right() - edge_width { + Some(SplitDirection::Right) + } else if cursor.y < event.bounds.top() + edge_width { + Some(SplitDirection::Up) + } else if cursor.y > event.bounds.bottom() - edge_width { + Some(SplitDirection::Down) + } else { + None + }; + if direction != self.drag_split_direction { + self.drag_split_direction = direction; + cx.notify(); + } + } + fn handle_tab_drop( &mut self, - dragged_tab: &View, + dragged_tab: &DraggedTab, ix: usize, cx: &mut ViewContext<'_, Pane>, ) { - let dragged_tab = dragged_tab.read(cx); + let mut to_pane = cx.view().clone(); + let split_direction = self.drag_split_direction; let item_id = dragged_tab.item_id; let from_pane = dragged_tab.pane.clone(); - let to_pane = cx.view().clone(); self.workspace - .update(cx, |workspace, cx| { + .update(cx, |_, cx| { cx.defer(move |workspace, cx| { + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); + } workspace.move_item(from_pane, to_pane, item_id, ix, cx); }); }) .log_err(); } - fn handle_split_tab_drop( + fn handle_project_entry_drop( &mut self, - dragged_tab: &View, - split_direction: SplitDirection, + project_entry_id: &ProjectEntryId, cx: &mut ViewContext<'_, Pane>, ) { - let dragged_tab = dragged_tab.read(cx); - let item_id = dragged_tab.item_id; - let from_pane = dragged_tab.pane.clone(); - let to_pane = cx.view().clone(); + let mut to_pane = cx.view().clone(); + let split_direction = self.drag_split_direction; + let project_entry_id = *project_entry_id; self.workspace - .update(cx, |workspace, cx| { + .update(cx, |_, cx| { cx.defer(move |workspace, cx| { - let item = from_pane + if let Some(path) = workspace + .project() .read(cx) - .items() - .find(|item| item.item_id() == item_id) - .map(|item| item.boxed_clone()); - if let Some(item) = item { - if let Some(item) = item.clone_on_split(workspace.database_id(), cx) { - workspace.split_item(split_direction, item, cx); + .path_for_entry(project_entry_id, cx) + { + if let Some(split_direction) = split_direction { + to_pane = workspace.split_pane(to_pane, split_direction, cx); } + workspace + .open_path(path, Some(to_pane.downgrade()), true, cx) + .detach_and_log_err(cx); } }); }) @@ -1799,7 +1807,8 @@ impl Render for Pane { type Element = Focusable
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let this = cx.view().downgrade(); + let mut drag_target_color = cx.theme().colors().text; + drag_target_color.a = 0.5; v_stack() .key_context("Pane") @@ -1878,63 +1887,60 @@ impl Render for Pane { }), ) .child(self.render_tab_bar(cx)) - .child(self.toolbar.clone()) - .child(if let Some(item) = self.active_item() { - let mut drag_target_color = cx.theme().colors().text; - drag_target_color.a = 0.5; - + .child( + // main content div() - .flex() .flex_1() .relative() - .child(item.to_any()) + .group("") + .on_drag_move::(cx.listener(Self::handle_drag_move)) + .on_drag_move::(cx.listener(Self::handle_drag_move)) + .map(|div| { + if let Some(item) = self.active_item() { + div.flex_col() + .child(self.toolbar.clone()) + .child(item.to_any()) + } else { + div.flex() + .flex_row() + .items_center() + .size_full() + .justify_center() + .child( + Label::new("Open a file or project to get started.") + .color(Color::Muted), + ) + } + }) .child( + // drag target div() + .invisible() .absolute() - .full() - .z_index(1) - .drag_over::(|style| style.bg(drag_target_color)) - .on_drop(cx.listener( - move |this, dragged_tab: &View, cx| { - this.handle_tab_drop(dragged_tab, this.active_item_index(), cx) - }, - )), - ) - .children( - [ - (SplitDirection::Up, 2), - (SplitDirection::Down, 2), - (SplitDirection::Left, 3), - (SplitDirection::Right, 3), - ] - .into_iter() - .map(|(direction, z_index)| { - let div = div() - .absolute() - .z_index(z_index) - .invisible() - .bg(drag_target_color) - .drag_over::(|style| style.visible()) - .on_drop(cx.listener( - move |this, dragged_tab: &View, cx| { - this.handle_split_tab_drop(dragged_tab, direction, cx) - }, - )); - match direction { - SplitDirection::Up => div.top_0().left_0().right_0().h_32(), - SplitDirection::Down => div.left_0().bottom_0().right_0().h_32(), - SplitDirection::Left => div.top_0().left_0().bottom_0().w_32(), - SplitDirection::Right => div.top_0().bottom_0().right_0().w_32(), - } - }), - ) - } else { - h_stack() - .items_center() - .size_full() - .justify_center() - .child(Label::new("Open a file or project to get started.").color(Color::Muted)) - }) + .bg(drag_target_color) + .group_drag_over::("", |style| style.visible()) + .group_drag_over::("", |style| style.visible()) + .on_drop(cx.listener(move |this, dragged_tab, cx| { + this.handle_tab_drop(dragged_tab, this.active_item_index(), cx) + })) + .on_drop(cx.listener(move |this, entry_id, cx| { + this.handle_project_entry_drop(entry_id, cx) + })) + .map(|div| match self.drag_split_direction { + None => div.top_0().left_0().right_0().bottom_0(), + Some(SplitDirection::Up) => div.top_0().left_0().right_0().h_32(), + Some(SplitDirection::Down) => { + div.left_0().bottom_0().right_0().h_32() + } + Some(SplitDirection::Left) => { + div.top_0().left_0().bottom_0().w_32() + } + Some(SplitDirection::Right) => { + div.top_0().bottom_0().right_0().w_32() + } + }), + ), + ) .on_mouse_down( MouseButton::Navigate(NavigationDirection::Back), cx.listener(|pane, _, cx| { diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index 5f14df833d1dfd5df8b83edf52dd0f1fce8ad2eb..966e2c6341a5c9fa7808424a07ea0571d99a5bf6 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -16,7 +16,7 @@ const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4) const HORIZONTAL_MIN_SIZE: f32 = 80.; const VERTICAL_MIN_SIZE: f32 = 100.; -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub struct PaneGroup { pub(crate) root: Member, } @@ -121,7 +121,7 @@ impl PaneGroup { } } -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub(crate) enum Member { Axis(PaneAxis), Pane(View), @@ -426,12 +426,6 @@ pub(crate) struct PaneAxis { pub bounding_boxes: Arc>>>>, } -impl PartialEq for PaneAxis { - fn eq(&self, other: &Self) -> bool { - todo!() - } -} - impl PaneAxis { pub fn new(axis: Axis, members: Vec) -> Self { let flexes = Arc::new(Mutex::new(vec![1.; members.len()])); @@ -816,7 +810,7 @@ mod element { proposed_current_pixel_change -= current_pixel_change; } - // todo!(reserialize workspace) + // todo!(schedule serialize) // workspace.schedule_serialize(cx); cx.notify(); } @@ -851,7 +845,7 @@ mod element { cx.on_mouse_event({ let dragged_handle = dragged_handle.clone(); - move |e: &MouseDownEvent, phase, cx| { + move |e: &MouseDownEvent, phase, _cx| { if phase.bubble() && handle_bounds.contains(&e.position) { dragged_handle.replace(Some(ix)); } @@ -859,7 +853,7 @@ mod element { }); cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| { let dragged_handle = dragged_handle.borrow(); - if *dragged_handle == Some(ix) { + if phase.bubble() && *dragged_handle == Some(ix) { Self::compute_resize(&flexes, e, ix, axis, axis_bounds, cx) } }); @@ -896,7 +890,7 @@ mod element { } fn paint( - self, + &mut self, bounds: gpui::Bounds, state: &mut Self::State, cx: &mut ui::prelude::WindowContext, @@ -912,7 +906,7 @@ mod element { let mut bounding_boxes = self.bounding_boxes.lock(); bounding_boxes.clear(); - for (ix, child) in self.children.into_iter().enumerate() { + for (ix, child) in self.children.iter_mut().enumerate() { //todo!(active_pane_magnification) // If usign active pane magnification, need to switch to using // 1 for all non-active panes, and then the magnification for the @@ -949,7 +943,7 @@ mod element { cx.with_z_index(1, |cx| { cx.on_mouse_event({ let state = state.clone(); - move |e: &MouseUpEvent, phase, cx| { + move |_: &MouseUpEvent, phase, _cx| { if phase.bubble() { state.replace(None); } @@ -968,402 +962,4 @@ mod element { fn flex_values_in_bounds(flexes: &[f32]) -> bool { (flexes.iter().copied().sum::() - flexes.len() as f32).abs() < 0.001 } - // // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc}; - - // // use gpui::{ - // // geometry::{ - // // rect::Bounds, - // // vector::{vec2f, Vector2F}, - // // }, - // // json::{self, ToJson}, - // // platform::{CursorStyle, MouseButton}, - // // scene::MouseDrag, - // // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, BoundsExt, - // // SizeConstraint, Vector2FExt, ViewContext, - // // }; - - // use crate::{ - // pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE}, - // Workspace, WorkspaceSettings, - // }; - - // pub struct PaneAxisElement { - // axis: Axis, - // basis: usize, - // active_pane_ix: Option, - // flexes: Rc>>, - // children: Vec>, - // bounding_boxes: Rc>>>>, - // } - - // impl PaneAxisElement { - // pub fn new( - // axis: Axis, - // basis: usize, - // flexes: Rc>>, - // bounding_boxes: Rc>>>>, - // ) -> Self { - // Self { - // axis, - // basis, - // flexes, - // bounding_boxes, - // active_pane_ix: None, - // children: Default::default(), - // } - // } - - // pub fn set_active_pane(&mut self, active_pane_ix: Option) { - // self.active_pane_ix = active_pane_ix; - // } - - // fn layout_children( - // &mut self, - // active_pane_magnification: f32, - // constraint: SizeConstraint, - // remaining_space: &mut f32, - // remaining_flex: &mut f32, - // cross_axis_max: &mut f32, - // view: &mut Workspace, - // cx: &mut ViewContext, - // ) { - // let flexes = self.flexes.borrow(); - // let cross_axis = self.axis.invert(); - // for (ix, child) in self.children.iter_mut().enumerate() { - // let flex = if active_pane_magnification != 1. { - // if let Some(active_pane_ix) = self.active_pane_ix { - // if ix == active_pane_ix { - // active_pane_magnification - // } else { - // 1. - // } - // } else { - // 1. - // } - // } else { - // flexes[ix] - // }; - - // let child_size = if *remaining_flex == 0.0 { - // *remaining_space - // } else { - // let space_per_flex = *remaining_space / *remaining_flex; - // space_per_flex * flex - // }; - - // let child_constraint = match self.axis { - // Axis::Horizontal => SizeConstraint::new( - // vec2f(child_size, constraint.min.y()), - // vec2f(child_size, constraint.max.y()), - // ), - // Axis::Vertical => SizeConstraint::new( - // vec2f(constraint.min.x(), child_size), - // vec2f(constraint.max.x(), child_size), - // ), - // }; - // let child_size = child.layout(child_constraint, view, cx); - // *remaining_space -= child_size.along(self.axis); - // *remaining_flex -= flex; - // *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); - // } - // } - - // fn handle_resize( - // flexes: Rc>>, - // axis: Axis, - // preceding_ix: usize, - // child_start: Vector2F, - // drag_bounds: Bounds, - // ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext) { - // let size = move |ix, flexes: &[f32]| { - // drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32) - // }; - - // move |drag, workspace: &mut Workspace, cx| { - // if drag.end { - // // TODO: Clear cascading resize state - // return; - // } - // let min_size = match axis { - // Axis::Horizontal => HORIZONTAL_MIN_SIZE, - // Axis::Vertical => VERTICAL_MIN_SIZE, - // }; - // let mut flexes = flexes.borrow_mut(); - - // // Don't allow resizing to less than the minimum size, if elements are already too small - // if min_size - 1. > size(preceding_ix, flexes.as_slice()) { - // return; - // } - - // let mut proposed_current_pixel_change = (drag.position - child_start).along(axis) - // - size(preceding_ix, flexes.as_slice()); - - // let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| { - // let flex_change = pixel_dx / drag_bounds.length_along(axis); - // let current_target_flex = flexes[target_ix] + flex_change; - // let next_target_flex = - // flexes[(target_ix as isize + next) as usize] - flex_change; - // (current_target_flex, next_target_flex) - // }; - - // let mut successors = from_fn({ - // let forward = proposed_current_pixel_change > 0.; - // let mut ix_offset = 0; - // let len = flexes.len(); - // move || { - // let result = if forward { - // (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset) - // } else { - // (preceding_ix as isize - ix_offset as isize >= 0) - // .then(|| preceding_ix - ix_offset) - // }; - - // ix_offset += 1; - - // result - // } - // }); - - // while proposed_current_pixel_change.abs() > 0. { - // let Some(current_ix) = successors.next() else { - // break; - // }; - - // let next_target_size = f32::max( - // size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change, - // min_size, - // ); - - // let current_target_size = f32::max( - // size(current_ix, flexes.as_slice()) - // + size(current_ix + 1, flexes.as_slice()) - // - next_target_size, - // min_size, - // ); - - // let current_pixel_change = - // current_target_size - size(current_ix, flexes.as_slice()); - - // let (current_target_flex, next_target_flex) = - // flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice()); - - // flexes[current_ix] = current_target_flex; - // flexes[current_ix + 1] = next_target_flex; - - // proposed_current_pixel_change -= current_pixel_change; - // } - - // workspace.schedule_serialize(cx); - // cx.notify(); - // } - // } - // } - - // impl Extend> for PaneAxisElement { - // fn extend>>(&mut self, children: T) { - // self.children.extend(children); - // } - // } - - // impl Element for PaneAxisElement { - // type LayoutState = f32; - // type PaintState = (); - - // fn layout( - // &mut self, - // constraint: SizeConstraint, - // view: &mut Workspace, - // cx: &mut ViewContext, - // ) -> (Vector2F, Self::LayoutState) { - // debug_assert!(self.children.len() == self.flexes.borrow().len()); - - // let active_pane_magnification = - // settings::get::(cx).active_pane_magnification; - - // let mut remaining_flex = 0.; - - // if active_pane_magnification != 1. { - // let active_pane_flex = self - // .active_pane_ix - // .map(|_| active_pane_magnification) - // .unwrap_or(1.); - // remaining_flex += self.children.len() as f32 - 1. + active_pane_flex; - // } else { - // for flex in self.flexes.borrow().iter() { - // remaining_flex += flex; - // } - // } - - // let mut cross_axis_max: f32 = 0.0; - // let mut remaining_space = constraint.max_along(self.axis); - - // if remaining_space.is_infinite() { - // panic!("flex contains flexible children but has an infinite constraint along the flex axis"); - // } - - // self.layout_children( - // active_pane_magnification, - // constraint, - // &mut remaining_space, - // &mut remaining_flex, - // &mut cross_axis_max, - // view, - // cx, - // ); - - // let mut size = match self.axis { - // Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), - // Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), - // }; - - // if constraint.min.x().is_finite() { - // size.set_x(size.x().max(constraint.min.x())); - // } - // if constraint.min.y().is_finite() { - // size.set_y(size.y().max(constraint.min.y())); - // } - - // if size.x() > constraint.max.x() { - // size.set_x(constraint.max.x()); - // } - // if size.y() > constraint.max.y() { - // size.set_y(constraint.max.y()); - // } - - // (size, remaining_space) - // } - - // fn paint( - // &mut self, - // bounds: Bounds, - // visible_bounds: Bounds, - // remaining_space: &mut Self::LayoutState, - // view: &mut Workspace, - // cx: &mut ViewContext, - // ) -> Self::PaintState { - // let can_resize = settings::get::(cx).active_pane_magnification == 1.; - // let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default(); - - // let overflowing = *remaining_space < 0.; - // if overflowing { - // cx.scene().push_layer(Some(visible_bounds)); - // } - - // let mut child_origin = bounds.origin(); - - // let mut bounding_boxes = self.bounding_boxes.borrow_mut(); - // bounding_boxes.clear(); - - // let mut children_iter = self.children.iter_mut().enumerate().peekable(); - // while let Some((ix, child)) = children_iter.next() { - // let child_start = child_origin.clone(); - // child.paint(child_origin, visible_bounds, view, cx); - - // bounding_boxes.push(Some(Bounds::new(child_origin, child.size()))); - - // match self.axis { - // Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), - // Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), - // } - - // if can_resize && children_iter.peek().is_some() { - // cx.scene().push_stacking_context(None, None); - - // let handle_origin = match self.axis { - // Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0), - // Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.), - // }; - - // let handle_bounds = match self.axis { - // Axis::Horizontal => Bounds::new( - // handle_origin, - // vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()), - // ), - // Axis::Vertical => Bounds::new( - // handle_origin, - // vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE), - // ), - // }; - - // let style = match self.axis { - // Axis::Horizontal => CursorStyle::ResizeLeftRight, - // Axis::Vertical => CursorStyle::ResizeUpDown, - // }; - - // cx.scene().push_cursor_region(CursorRegion { - // bounds: handle_bounds, - // style, - // }); - - // enum ResizeHandle {} - // let mut mouse_region = MouseRegion::new::( - // cx.view_id(), - // self.basis + ix, - // handle_bounds, - // ); - // mouse_region = mouse_region - // .on_drag( - // MouseButton::Left, - // Self::handle_resize( - // self.flexes.clone(), - // self.axis, - // ix, - // child_start, - // visible_bounds.clone(), - // ), - // ) - // .on_click(MouseButton::Left, { - // let flexes = self.flexes.clone(); - // move |e, v: &mut Workspace, cx| { - // if e.click_count >= 2 { - // let mut borrow = flexes.borrow_mut(); - // *borrow = vec![1.; borrow.len()]; - // v.schedule_serialize(cx); - // cx.notify(); - // } - // } - // }); - // cx.scene().push_mouse_region(mouse_region); - - // cx.scene().pop_stacking_context(); - // } - // } - - // if overflowing { - // cx.scene().pop_layer(); - // } - // } - - // fn rect_for_text_range( - // &self, - // range_utf16: Range, - // _: Bounds, - // _: Bounds, - // _: &Self::LayoutState, - // _: &Self::PaintState, - // view: &Workspace, - // cx: &ViewContext, - // ) -> Option> { - // self.children - // .iter() - // .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) - // } - - // fn debug( - // &self, - // bounds: Bounds, - // _: &Self::LayoutState, - // _: &Self::PaintState, - // view: &Workspace, - // cx: &ViewContext, - // ) -> json::Value { - // serde_json::json!({ - // "type": "PaneAxis", - // "bounds": bounds.to_json(), - // "axis": self.axis.to_json(), - // "flexes": *self.flexes.borrow(), - // "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() - // }) - // } - // } } diff --git a/crates/workspace2/src/searchable.rs b/crates/workspace2/src/searchable.rs index eadd602c8458d65434be39af241b699205b5e327..59202cbbaf53e5e4f62d77be693e3b13916b555b 100644 --- a/crates/workspace2/src/searchable.rs +++ b/crates/workspace2/src/searchable.rs @@ -193,7 +193,7 @@ impl SearchableItemHandle for View { cx: &mut WindowContext, ) -> Task>> { let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); - cx.spawn(|cx| async { + cx.spawn(|_| async { let matches = matches.await; matches .into_iter() @@ -253,7 +253,7 @@ pub trait WeakSearchableItemHandle: WeakItemHandle { } impl WeakSearchableItemHandle for WeakView { - fn upgrade(&self, cx: &AppContext) -> Option> { + fn upgrade(&self, _cx: &AppContext) -> Option> { Some(Box::new(self.upgrade()?)) } diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index c0cbd127acaf64df8716a562b167293ec630f5f5..ba571d6e0ad3c70bc64139e90e64f9c59314bfc7 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -51,14 +51,14 @@ impl Render for StatusBar { } impl StatusBar { - fn render_left_tools(&self, cx: &mut ViewContext) -> impl IntoElement { + fn render_left_tools(&self, _: &mut ViewContext) -> impl IntoElement { h_stack() .items_center() .gap_2() .children(self.left_items.iter().map(|item| item.to_any())) } - fn render_right_tools(&self, cx: &mut ViewContext) -> impl IntoElement { + fn render_right_tools(&self, _: &mut ViewContext) -> impl IntoElement { h_stack() .items_center() .gap_2() diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index 7971df40ff87ba0ad05ec39d39dff1e96d42659e..a6c8f9d0bff03ec3edd42779b49bdf4382a15a25 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -55,6 +55,12 @@ pub struct Toolbar { } impl Toolbar { + fn has_any_visible_items(&self) -> bool { + self.items + .iter() + .any(|(_item, location)| *location != ToolbarItemLocation::Hidden) + } + fn left_items(&self) -> impl Iterator { self.items.iter().filter_map(|(item, location)| { if *location == ToolbarItemLocation::PrimaryLeft { @@ -90,6 +96,10 @@ impl Render for Toolbar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + if !self.has_any_visible_items() { + return div(); + } + let secondary_item = self.secondary_items().next().map(|item| item.to_any()); v_stack() @@ -99,8 +109,22 @@ impl Render for Toolbar { .child( h_stack() .justify_between() - .child(h_stack().children(self.left_items().map(|item| item.to_any()))) - .child(h_stack().children(self.right_items().map(|item| item.to_any()))), + .when(self.left_items().count() > 0, |this| { + this.child( + h_stack() + .flex_1() + .justify_start() + .children(self.left_items().map(|item| item.to_any())), + ) + }) + .when(self.right_items().count() > 0, |this| { + this.child( + h_stack() + .flex_1() + .justify_end() + .children(self.right_items().map(|item| item.to_any())), + ) + }), ) .children(secondary_item) } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index a07c5818a0d0ce7fca252882ca43f41a32732c02..a789809537d1bf5fdc530cf293d5f8b9d542c8b2 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -1,15 +1,11 @@ -#![allow(unused_variables, dead_code, unused_mut)] -// todo!() this is to make transition easier. - pub mod dock; pub mod item; +mod modal_layer; pub mod notifications; pub mod pane; pub mod pane_group; mod persistence; pub mod searchable; -// todo!() -mod modal_layer; pub mod shared_screen; mod status_bar; mod toolbar; @@ -236,14 +232,14 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.on_action({ let app_state = Arc::downgrade(&app_state); move |_: &Open, cx: &mut AppContext| { - let mut paths = cx.prompt_for_paths(PathPromptOptions { + let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, multiple: true, }); if let Some(app_state) = app_state.upgrade() { - cx.spawn(move |mut cx| async move { + cx.spawn(move |cx| async move { if let Some(paths) = paths.await.log_err().flatten() { cx.update(|cx| { open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx) @@ -458,7 +454,7 @@ pub struct Workspace { leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, - subscriptions: Vec, + _subscriptions: Vec, _apply_leader_updates: Task>, _observe_current_user: Task>, _schedule_serialize: Option>, @@ -530,6 +526,7 @@ impl Workspace { cx.notify() }) .detach(); + cx.on_blur_window(|this, cx| { let focus_handle = this.focus_handle(cx); cx.focus(&focus_handle); @@ -590,12 +587,9 @@ impl Workspace { let left_dock = cx.build_view(|cx| Dock::new(DockPosition::Left, cx)); let bottom_dock = cx.build_view(|cx| Dock::new(DockPosition::Bottom, cx)); let right_dock = cx.build_view(|cx| Dock::new(DockPosition::Right, cx)); - let left_dock_buttons = - cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx)); - let bottom_dock_buttons = - cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx)); - let right_dock_buttons = - cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx)); + let left_dock_buttons = cx.build_view(|cx| PanelButtons::new(left_dock.clone(), cx)); + let bottom_dock_buttons = cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), cx)); + let right_dock_buttons = cx.build_view(|cx| PanelButtons::new(right_dock.clone(), cx)); let status_bar = cx.build_view(|cx| { let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_dock_buttons, cx); @@ -604,8 +598,7 @@ impl Workspace { status_bar }); - let workspace_handle = cx.view().downgrade(); - let modal_layer = cx.build_view(|cx| ModalLayer::new()); + let modal_layer = cx.build_view(|_| ModalLayer::new()); // todo!() // cx.update_default_global::, _, _>(|drag_and_drop, _| { @@ -703,7 +696,7 @@ impl Workspace { _apply_leader_updates, _schedule_serialize: None, leader_updates_tx, - subscriptions, + _subscriptions: subscriptions, pane_history_timestamp, workspace_actions: Default::default(), // This data will be incorrect, but it will be overwritten by the time it needs to be used. @@ -763,7 +756,7 @@ impl Workspace { }; let window = if let Some(window) = requesting_window { - cx.update_window(window.into(), |old_workspace, cx| { + cx.update_window(window.into(), |_, cx| { cx.replace_root_view(|cx| { Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx) }); @@ -1185,8 +1178,7 @@ impl Workspace { } fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext) { - let save_all = self - .save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx) + self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx) .detach_and_log_err(cx); } @@ -1216,7 +1208,7 @@ impl Workspace { cx.spawn(|workspace, mut cx| async move { // Override save mode and display "Save all files" prompt if save_intent == SaveIntent::Close && dirty_items.len() > 1 { - let mut answer = workspace.update(&mut cx, |_, cx| { + let answer = workspace.update(&mut cx, |_, cx| { let prompt = Pane::file_names_for_prompt( &mut dirty_items.iter().map(|(_, handle)| handle), dirty_items.len(), @@ -1261,7 +1253,7 @@ impl Workspace { } pub fn open(&mut self, _: &Open, cx: &mut ViewContext) { - let mut paths = cx.prompt_for_paths(PathPromptOptions { + let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, multiple: true, @@ -1390,7 +1382,7 @@ impl Workspace { } fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext) { - let mut paths = cx.prompt_for_paths(PathPromptOptions { + let paths = cx.prompt_for_paths(PathPromptOptions { files: false, directories: true, multiple: true, @@ -1670,6 +1662,8 @@ impl Workspace { None } + // todo!("implement zoom") + #[allow(unused)] fn zoom_out(&mut self, cx: &mut ViewContext) { for pane in &self.panes { pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); @@ -2043,20 +2037,20 @@ impl Workspace { _ => bounding_box.center(), }; - let distance_to_next = 1.; //todo(pane dividers styling) + let distance_to_next = 8.; //todo(pane dividers styling) let target = match direction { SplitDirection::Left => { - Point::new(bounding_box.origin.x - distance_to_next.into(), center.y) + Point::new(bounding_box.left() - distance_to_next.into(), center.y) } SplitDirection::Right => { Point::new(bounding_box.right() + distance_to_next.into(), center.y) } SplitDirection::Up => { - Point::new(center.x, bounding_box.origin.y - distance_to_next.into()) + Point::new(center.x, bounding_box.top() - distance_to_next.into()) } SplitDirection::Down => { - Point::new(center.x, bounding_box.top() + distance_to_next.into()) + Point::new(center.x, bounding_box.bottom() + distance_to_next.into()) } }; self.center.pane_at_pixel_position(target) @@ -2574,7 +2568,7 @@ impl Workspace { // } // } - fn render_notifications(&self, cx: &ViewContext) -> Option
{ + fn render_notifications(&self, _cx: &ViewContext) -> Option
{ if self.notifications.is_empty() { None } else { @@ -3005,6 +2999,7 @@ impl Workspace { cx.notify(); } + #[allow(unused)] fn schedule_serialize(&mut self, cx: &mut ViewContext) { self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move { cx.background_executor() @@ -3143,12 +3138,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task>>>> { cx.spawn(|workspace, mut cx| async move { - let (project, old_center_pane) = workspace.update(&mut cx, |workspace, _| { - ( - workspace.project().clone(), - workspace.last_active_center_pane.clone(), - ) - })?; + let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?; let mut center_group = None; let mut center_items = None; @@ -3293,7 +3283,7 @@ impl Workspace { .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| { workspace.swap_pane_in_direction(action.0, cx) })) - .on_action(cx.listener(|this, e: &ToggleLeftDock, cx| { + .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| { this.toggle_dock(DockPosition::Left, cx); })) .on_action( @@ -3313,42 +3303,12 @@ impl Workspace { ) .on_action(cx.listener(Workspace::open)) .on_action(cx.listener(Workspace::close_window)) - - // cx.add_action(Workspace::activate_pane_at_index); - // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { - // workspace.reopen_closed_item(cx).detach(); - // }); - // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| { - // workspace - // .go_back(workspace.active_pane().downgrade(), cx) - // .detach(); - // }); - // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| { - // workspace - // .go_forward(workspace.active_pane().downgrade(), cx) - // .detach(); - // }); - - // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| { - // cx.spawn(|workspace, mut cx| async move { - // let err = install_cli::install_cli(&cx) - // .await - // .context("Failed to create CLI symlink"); - - // workspace.update(&mut cx, |workspace, cx| { - // if matches!(err, Err(_)) { - // err.notify_err(workspace, cx); - // } else { - // workspace.show_notification(1, cx, |cx| { - // cx.build_view(|_| { - // MessageNotification::new("Successfully installed the `zed` binary") - // }) - // }); - // } - // }) - // }) - // .detach(); - // }); + .on_action(cx.listener(Workspace::activate_pane_at_index)) + .on_action( + cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { + workspace.reopen_closed_item(cx).detach(); + }), + ) } #[cfg(any(test, feature = "test-support"))] @@ -3418,7 +3378,7 @@ impl Workspace { self } - fn add_workspace_actions_listeners(&self, mut div: Div, cx: &mut ViewContext) -> Div { + fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext) -> Div { let mut div = div .on_action(cx.listener(Self::close_inactive_items_and_panes)) .on_action(cx.listener(Self::close_all_items_and_panes)) @@ -3578,9 +3538,7 @@ impl FocusableView for Workspace { } } -struct WorkspaceBounds(Bounds); - -#[derive(Render)] +#[derive(Clone, Render)] struct DraggedDock(DockPosition); impl Render for Workspace { @@ -3628,7 +3586,7 @@ impl Render for Workspace { .border_b() .border_color(cx.theme().colors().border) .child( - canvas(cx.listener(|workspace, bounds, cx| { + canvas(cx.listener(|workspace, bounds, _| { workspace.bounds = *bounds; })) .absolute() @@ -3636,7 +3594,7 @@ impl Render for Workspace { ) .on_drag_move( cx.listener(|workspace, e: &DragMoveEvent, cx| { - match e.drag.read(cx).0 { + match e.drag(cx).0 { DockPosition::Left => { let size = workspace.bounds.left() + e.event.position.x; workspace.left_dock.update(cx, |left_dock, cx| { @@ -4056,7 +4014,7 @@ async fn join_channel_internal( active_call: &Model, cx: &mut AsyncAppContext, ) -> Result { - let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { + let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| { let Some(room) = active_call.room().map(|room| room.read(cx)) else { return (false, None); }; @@ -4421,7 +4379,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) { } cx.spawn(|mut cx| async move { - if let Some(mut prompt) = prompt { + if let Some(prompt) = prompt { let answer = prompt.await?; if answer != 0 { return Ok(()); @@ -4460,6 +4418,8 @@ fn parse_pixel_size_env_var(value: &str) -> Option> { #[cfg(test)] mod tests { + use std::{cell::RefCell, rc::Rc}; + use super::*; use crate::item::{ test::{TestItem, TestProjectItem}, @@ -4470,7 +4430,6 @@ mod tests { use project::{Project, ProjectEntryId}; use serde_json::json; use settings::SettingsStore; - use std::{cell::RefCell, rc::Rc}; #[gpui::test] async fn test_tab_disambiguation(cx: &mut TestAppContext) { @@ -4544,7 +4503,7 @@ mod tests { let project = Project::test(fs, ["root1".as_ref()], cx).await; let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); - let worktree_id = project.read_with(cx, |project, cx| { + let worktree_id = project.update(cx, |project, cx| { project.worktrees().next().unwrap().read(cx).id() }); @@ -4557,7 +4516,7 @@ mod tests { // Add an item to an empty pane workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx)); - project.read_with(cx, |project, cx| { + project.update(cx, |project, cx| { assert_eq!( project.active_entry(), project @@ -4570,7 +4529,7 @@ mod tests { // Add a second item to a non-empty pane workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx)); assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1")); - project.read_with(cx, |project, cx| { + project.update(cx, |project, cx| { assert_eq!( project.active_entry(), project @@ -4586,7 +4545,7 @@ mod tests { .await .unwrap(); assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1")); - project.read_with(cx, |project, cx| { + project.update(cx, |project, cx| { assert_eq!( project.active_entry(), project @@ -4970,14 +4929,14 @@ mod tests { item.is_dirty = true; cx.blur(); }); - cx.executor().run_until_parked(); + cx.run_until_parked(); item.update(cx, |item, _| assert_eq!(item.save_count, 5)); // Ensure autosave is prevented for deleted files also when closing the buffer. let _close_items = pane.update(cx, |pane, cx| { pane.close_items(cx, SaveIntent::Close, move |id| id == item_id) }); - cx.executor().run_until_parked(); + cx.run_until_parked(); assert!(cx.has_pending_prompt()); item.update(cx, |item, _| assert_eq!(item.save_count, 5)); } @@ -5036,363 +4995,362 @@ mod tests { }); } - // #[gpui::test] - // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); - - // let project = Project::test(fs, [], cx).await; - // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); - // let workspace = window.root(cx); - - // let panel = workspace.update(cx, |workspace, cx| { - // let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right)); - // workspace.add_panel(panel.clone(), cx); - - // workspace - // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - - // panel - // }); - - // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); - // pane.update(cx, |pane, cx| { - // let item = cx.build_view(|_| TestItem::new(cx)); - // pane.add_item(Box::new(item), true, true, None, cx); - // }); - - // // Transfer focus from center to panel - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Transfer focus from panel to center - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.has_focus(cx)); - // }); - - // // Close the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(!panel.has_focus(cx)); - // }); - - // // Open the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(!panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Focus and zoom panel - // panel.update(cx, |panel, cx| { - // cx.focus_self(); - // panel.set_zoomed(true, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Transfer focus to the center closes the dock - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(!panel.has_focus(cx)); - // }); - - // // Transferring focus back to the panel keeps it zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_panel_focus::(cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(panel.has_focus(cx)); - // }); - - // // Close the dock while it is zoomed - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(!workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_none()); - // assert!(!panel.has_focus(cx)); - // }); - - // // Opening the dock, when it's zoomed, retains focus - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(panel.is_zoomed(cx)); - // assert!(workspace.zoomed.is_some()); - // assert!(panel.has_focus(cx)); - // }); - - // // Unzoom and close the panel, zoom the active pane. - // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) - // }); - // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + // #[gpui::test] + // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { + // init_test(cx); + // let fs = FakeFs::new(cx.executor()); + + // let project = Project::test(fs, [], cx).await; + // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + + // let panel = workspace.update(cx, |workspace, cx| { + // let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); + // workspace.add_panel(panel.clone(), cx); + + // workspace + // .right_dock() + // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + // panel + // }); + + // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); + // pane.update(cx, |pane, cx| { + // let item = cx.build_view(|cx| TestItem::new(cx)); + // pane.add_item(Box::new(item), true, true, None, cx); + // }); + + // // Transfer focus from center to panel + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Transfer focus from panel to center + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Close the dock + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(!workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Open the dock + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(!panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Focus and zoom panel + // panel.update(cx, |panel, cx| { + // cx.focus_self(); + // panel.set_zoomed(true, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Transfer focus to the center closes the dock + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(!workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Transferring focus back to the panel keeps it zoomed + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_panel_focus::(cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Close the dock while it is zoomed + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(!workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(workspace.zoomed.is_none()); + // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Opening the dock, when it's zoomed, retains focus + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(panel.is_zoomed(cx)); + // assert!(workspace.zoomed.is_some()); + // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); + // }); + + // // Unzoom and close the panel, zoom the active pane. + // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + + // // Opening a dock unzooms the pane. + // workspace.update(cx, |workspace, cx| { + // workspace.toggle_dock(DockPosition::Right, cx) + // }); + // workspace.update(cx, |workspace, cx| { + // let pane = pane.read(cx); + // assert!(!pane.is_zoomed()); + // assert!(!pane.focus_handle(cx).is_focused(cx)); + // assert!(workspace.right_dock().read(cx).is_open()); + // assert!(workspace.zoomed.is_none()); + // }); + // } - // // Opening a dock unzooms the pane. - // workspace.update(cx, |workspace, cx| { - // workspace.toggle_dock(DockPosition::Right, cx) + // #[gpui::test] + // async fn test_panels(cx: &mut gpui::TestAppContext) { + // init_test(cx); + // let fs = FakeFs::new(cx.executor()); + + // let project = Project::test(fs, [], cx).await; + // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); + + // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { + // // Add panel_1 on the left, panel_2 on the right. + // let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx)); + // workspace.add_panel(panel_1.clone(), cx); + // workspace + // .left_dock() + // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); + // let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); + // workspace.add_panel(panel_2.clone(), cx); + // workspace + // .right_dock() + // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + // let left_dock = workspace.left_dock(); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id() + // ); + // assert_eq!( + // left_dock.read(cx).active_panel_size(cx).unwrap(), + // panel_1.size(cx) + // ); + + // left_dock.update(cx, |left_dock, cx| { + // left_dock.resize_active_panel(Some(1337.), cx) // }); - // workspace.update(cx, |workspace, cx| { - // let pane = pane.read(cx); - // assert!(!pane.is_zoomed()); - // assert!(!pane.has_focus()); - // assert!(workspace.right_dock().read(cx).is_open()); - // assert!(workspace.zoomed.is_none()); - // }); - // } - - // #[gpui::test] - // async fn test_panels(cx: &mut gpui::TestAppContext) { - // init_test(cx); - // let fs = FakeFs::new(cx.executor()); - - // let project = Project::test(fs, [], cx).await; - // let window = cx.add_window(|cx| Workspace::test_new(project, cx)); - // let workspace = window.root(cx); - - // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { - // // Add panel_1 on the left, panel_2 on the right. - // let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left)); - // workspace.add_panel(panel_1.clone(), cx); + // assert_eq!( // workspace - // .left_dock() - // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); - // let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right)); - // workspace.add_panel(panel_2.clone(), cx); + // .right_dock() + // .read(cx) + // .visible_panel() + // .unwrap() + // .panel_id(), + // panel_2.panel_id(), + // ); + + // (panel_1, panel_2) + // }); + + // // Move panel_1 to the right + // panel_1.update(cx, |panel_1, cx| { + // panel_1.set_position(DockPosition::Right, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. + // // Since it was the only panel on the left, the left dock should now be closed. + // assert!(!workspace.left_dock().read(cx).is_open()); + // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); + // let right_dock = workspace.right_dock(); + // assert_eq!( + // right_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id() + // ); + // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); + + // // Now we move panel_2 to the left + // panel_2.set_position(DockPosition::Left, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_2 was not visible on the right, we don't open the left dock. + // assert!(!workspace.left_dock().read(cx).is_open()); + // // And the right dock is unaffected in it's displaying of panel_1 + // assert!(workspace.right_dock().read(cx).is_open()); + // assert_eq!( // workspace // .right_dock() - // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); - - // let left_dock = workspace.left_dock(); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert_eq!( - // left_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx) - // ); - - // left_dock.update(cx, |left_dock, cx| { - // left_dock.resize_active_panel(Some(1337.), cx) - // }); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .id(), - // panel_2.id() - // ); - - // (panel_1, panel_2) - // }); - - // // Move panel_1 to the right - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Right, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. - // // Since it was the only panel on the left, the left dock should now be closed. - // assert!(!workspace.left_dock().read(cx).is_open()); - // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); - // let right_dock = workspace.right_dock(); - // assert_eq!( - // right_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); - - // // Now we move panel_2 to the left - // panel_2.set_position(DockPosition::Left, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_2 was not visible on the right, we don't open the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // And the right dock is unaffected in it's displaying of panel_1 - // assert!(workspace.right_dock().read(cx).is_open()); - // assert_eq!( - // workspace - // .right_dock() - // .read(cx) - // .visible_panel() - // .unwrap() - // .id(), - // panel_1.id() - // ); - // }); - - // // Move panel_1 back to the left - // panel_1.update(cx, |panel_1, cx| { - // panel_1.set_position(DockPosition::Left, cx) - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); - // // And right the dock should be closed as it no longer has any panels. - // assert!(!workspace.right_dock().read(cx).is_open()); - - // // Now we move panel_1 to the bottom - // panel_1.set_position(DockPosition::Bottom, cx); - // }); - - // workspace.update(cx, |workspace, cx| { - // // Since panel_1 was visible on the left, we close the left dock. - // assert!(!workspace.left_dock().read(cx).is_open()); - // // The bottom dock is sized based on the panel's default size, - // // since the panel orientation changed from vertical to horizontal. - // let bottom_dock = workspace.bottom_dock(); - // assert_eq!( - // bottom_dock.read(cx).active_panel_size(cx).unwrap(), - // panel_1.size(cx), - // ); - // // Close bottom dock and move panel_1 back to the left. - // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); - // panel_1.set_position(DockPosition::Left, cx); - // }); - - // // Emit activated event on panel 1 - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated)); - - // // Now the left dock is open and panel_1 is active and focused. - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // assert!(panel_1.is_focused(cx)); - // }); - - // // Emit closed event on panel 2, which is not active - // panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); - - // // Wo don't close the left dock, because panel_2 wasn't the active panel - // workspace.update(cx, |workspace, cx| { - // let left_dock = workspace.left_dock(); - // assert!(left_dock.read(cx).is_open()); - // assert_eq!( - // left_dock.read(cx).visible_panel().unwrap().id(), - // panel_1.id() - // ); - // }); - - // // Emitting a ZoomIn event shows the panel as zoomed. - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); - // }); - - // // Move panel to another dock while it is zoomed - // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // If focus is transferred to another view that's not a panel or another pane, we still show - // // the panel as zoomed. - // let focus_receiver = cx.build_view(|_| EmptyView); - // focus_receiver.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. - // workspace.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // If focus is transferred again to another view that's not a panel or a pane, we won't - // // show the panel as zoomed because it wasn't zoomed before. - // focus_receiver.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // When focus is transferred back to the panel, it is zoomed again. - // panel_1.update(cx, |_, cx| cx.focus_self()); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); - // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); - // }); - - // // Emitting a ZoomOut event unzooms the panel. - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); - // workspace.update(cx, |workspace, _| { - // assert_eq!(workspace.zoomed, None); - // assert_eq!(workspace.zoomed_position, None); - // }); - - // // Emit closed event on panel 1, which is active - // panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed)); - - // // Now the left dock is closed, because panel_1 was the active panel - // workspace.update(cx, |workspace, cx| { - // let right_dock = workspace.right_dock(); - // assert!(!right_dock.read(cx).is_open()); - // }); - // } + // .read(cx) + // .visible_panel() + // .unwrap() + // .panel_id(), + // panel_1.panel_id(), + // ); + // }); + + // // Move panel_1 back to the left + // panel_1.update(cx, |panel_1, cx| { + // panel_1.set_position(DockPosition::Left, cx) + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. + // let left_dock = workspace.left_dock(); + // assert!(left_dock.read(cx).is_open()); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id() + // ); + // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); + // // And right the dock should be closed as it no longer has any panels. + // assert!(!workspace.right_dock().read(cx).is_open()); + + // // Now we move panel_1 to the bottom + // panel_1.set_position(DockPosition::Bottom, cx); + // }); + + // workspace.update(cx, |workspace, cx| { + // // Since panel_1 was visible on the left, we close the left dock. + // assert!(!workspace.left_dock().read(cx).is_open()); + // // The bottom dock is sized based on the panel's default size, + // // since the panel orientation changed from vertical to horizontal. + // let bottom_dock = workspace.bottom_dock(); + // assert_eq!( + // bottom_dock.read(cx).active_panel_size(cx).unwrap(), + // panel_1.size(cx), + // ); + // // Close bottom dock and move panel_1 back to the left. + // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); + // panel_1.set_position(DockPosition::Left, cx); + // }); + + // // Emit activated event on panel 1 + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); + + // // Now the left dock is open and panel_1 is active and focused. + // workspace.update(cx, |workspace, cx| { + // let left_dock = workspace.left_dock(); + // assert!(left_dock.read(cx).is_open()); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id(), + // ); + // assert!(panel_1.focus_handle(cx).is_focused(cx)); + // }); + + // // Emit closed event on panel 2, which is not active + // panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + + // // Wo don't close the left dock, because panel_2 wasn't the active panel + // workspace.update(cx, |workspace, cx| { + // let left_dock = workspace.left_dock(); + // assert!(left_dock.read(cx).is_open()); + // assert_eq!( + // left_dock.read(cx).visible_panel().unwrap().panel_id(), + // panel_1.panel_id(), + // ); + // }); + + // // Emitting a ZoomIn event shows the panel as zoomed. + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); + // }); + + // // Move panel to another dock while it is zoomed + // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + // }); + + // // If focus is transferred to another view that's not a panel or another pane, we still show + // // the panel as zoomed. + // let other_focus_handle = cx.update(|cx| cx.focus_handle()); + // cx.update(|cx| cx.focus(&other_focus_handle)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + // }); + + // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. + // workspace.update(cx, |_, cx| cx.focus_self()); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, None); + // assert_eq!(workspace.zoomed_position, None); + // }); + + // // If focus is transferred again to another view that's not a panel or a pane, we won't + // // show the panel as zoomed because it wasn't zoomed before. + // cx.update(|cx| cx.focus(&other_focus_handle)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, None); + // assert_eq!(workspace.zoomed_position, None); + // }); + + // // When focus is transferred back to the panel, it is zoomed again. + // panel_1.update(cx, |_, cx| cx.focus_self()); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); + // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); + // }); + + // // Emitting a ZoomOut event unzooms the panel. + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); + // workspace.update(cx, |workspace, _| { + // assert_eq!(workspace.zoomed, None); + // assert_eq!(workspace.zoomed_position, None); + // }); + + // // Emit closed event on panel 1, which is active + // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); + + // // Now the left dock is closed, because panel_1 was the active panel + // workspace.update(cx, |workspace, cx| { + // let right_dock = workspace.right_dock(); + // assert!(!right_dock.read(cx).is_open()); + // }); + // } pub fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index d5fc0e8fdc9d3badeb4f89ad6d31dd6f5ace1979..27e32fff179819fb79f87af303a4f996bc295b26 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -41,7 +41,7 @@ use workspace::{ notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile, NewWindow, Workspace, WorkspaceSettings, }; -use zed_actions::{OpenBrowser, OpenZedURL, Quit}; +use zed_actions::{OpenBrowser, OpenSettings, OpenZedURL, Quit}; actions!( zed, @@ -59,7 +59,6 @@ actions!( OpenLicenses, OpenLocalSettings, OpenLog, - OpenSettings, OpenTelemetryLog, ResetBufferFontSize, ResetDatabase, diff --git a/crates/zed_actions2/src/lib.rs b/crates/zed_actions2/src/lib.rs index fa1a4a5ea961ce77ecd5d199a182c88c17166bbe..badf76a6e7b4bc84a81184b81fca33c413b61bfa 100644 --- a/crates/zed_actions2/src/lib.rs +++ b/crates/zed_actions2/src/lib.rs @@ -22,4 +22,4 @@ pub struct OpenZedURL { impl_actions!(zed, [OpenBrowser, OpenZedURL]); -actions!(zed, [Quit]); +actions!(zed, [OpenSettings, Quit]); diff --git a/debug.plist b/debug.plist new file mode 100644 index 0000000000000000000000000000000000000000..e09573c9d1c1499f8b1357d3a86bd90d306bded3 --- /dev/null +++ b/debug.plist @@ -0,0 +1,8 @@ + + + + + com.apple.security.get-task-allow + + + diff --git a/script/storybook b/script/storybook index 2978a3d8f46c162a0108f0ef29cb7931ae6afd21..7f5d5e8893c40950f9b49ee7cffafe099f88cb75 100755 --- a/script/storybook +++ b/script/storybook @@ -1,60 +1,7 @@ #!/bin/bash -options=( - "auto_height_editor" - "avatar" - "button" - "checkbox" - "context_menu" - "cursor" - "disclosure" - "focus" - "icon" - "icon_button" - "keybinding" - "label" - "list" - "list_header" - "list_item" - "overflow_scroll" - "scroll" - "tab" - "tab_bar" - "text" - "viewport_units" - "z_index" - "picker" -) - -run_story() { - echo "Running story: $1" - cargo run -p storybook2 -- "$1" -} - -if [ "$#" -gt 0 ]; then - story_arg="$1" - # Add prefix 'components/' if not present - if [[ $story_arg != components/* ]]; then - story_arg="components/$story_arg" - fi - # Check if the provided story is a valid option - for opt in "${options[@]}"; do - if [[ "components/$opt" == "$story_arg" ]]; then - run_story "$story_arg" - exit - fi - done - echo "Invalid story name: $1" - exit 1 +if [ -z "$1" ]; then + cargo run -p storybook2 +else + cargo run -p storybook2 -- "components/$1" fi - -prompt="Please select a story:" -PS3="$prompt " -select story in "${options[@]}"; do - if [[ -n $story ]]; then - run_story "components/$story" - break - else - echo "Invalid option" - fi -done