Detailed changes
@@ -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]]
@@ -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<dyn HttpClient>, 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<dyn HttpClient>, 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<Workspace>) {
+pub fn check(_: &Check, cx: &mut WindowContext) {
if let Some(updater) = AutoUpdater::get(cx) {
updater.update(cx, |updater, cx| updater.poll(cx));
} else {
@@ -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::<ReleaseChannel>().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<Self>) {
+ pub fn dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
cx.emit(DismissEvent);
}
}
@@ -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<Result<Credentials>> {
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::<String>();
+ 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)
})
}
@@ -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" }
@@ -2142,7 +2142,7 @@ impl CollabPanel {
}
fn render_signed_out(&mut self, cx: &mut ViewContext<Self>) -> 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::<DraggedChannelView>(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
@@ -11,14 +11,8 @@ use ui::{prelude::*, Avatar};
use util::{ResultExt as _, TryFutureExt};
use workspace::ModalView;
-pub fn init(cx: &mut AppContext) {
- //Picker::<ContactFinderDelegate>::init(cx);
- //cx.add_action(ContactFinder::dismiss)
-}
-
pub struct ContactFinder {
picker: View<Picker<ContactFinderDelegate>>,
- 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>) {
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>) {
- // self.has_focus = true;
- // if cx.is_self_focused() {
- // cx.focus(&self.picker)
- // }
- // }
-
- // fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
- // 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<ContactFinder>,
potential_contacts: Arc<[Arc<User>]>,
@@ -161,7 +128,6 @@ impl PickerDelegate for ContactFinderDelegate {
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
- //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)
@@ -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<Workspace>,
branch_popover: Option<View<BranchList>>,
project_popover: Option<recent_projects::RecentProjects>,
- //user_menu: ViewHandle<ContextMenu>,
_subscriptions: Vec<Subscription>,
}
@@ -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<Arc<ImageData>>,
- // cx: &mut ViewContext<Self>,
- // ) -> AnyElement<Self> {
- // 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::<ToggleUserMenu, _>(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::<ToggleUserMenu>(
- // 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<Self>) -> AnyElement<Self> {
- // let titlebar = &theme.titlebar;
- // MouseEventHandler::new::<SignIn, _>(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<Self>,
- // ) -> Option<AnyElement<Self>> {
- // 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::<ConnectionStatusButton, _>(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<Self>,
+ ) -> Option<AnyElement> {
+ 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<Self>) -> 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<Self>) -> 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)),
+ )
+ }
+ }
}
@@ -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)
});
@@ -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)),
),
)
@@ -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<Pixels>,
+ 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>) -> 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<gpui::Pixels>,
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);
});
});
})
@@ -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
@@ -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<Editor>,
email_address_editor: View<Editor>,
+ submission_state: Option<SubmissionState>,
+ dismiss_modal: bool,
character_count: i32,
- state: FeedbackModalState,
}
impl FocusableView for FeedbackModal {
@@ -72,7 +87,7 @@ impl EventEmitter<DismissEvent> for FeedbackModal {}
impl ModalView for FeedbackModal {
fn on_before_dismiss(&mut self, cx: &mut ViewContext<Self>) -> 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<Self>) {
- cx.emit(DismissEvent)
- }
-}
+ fn update_submission_state(&mut self, cx: &mut ViewContext<Self>) {
+ 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>) -> 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<Self>) {
+ cx.emit(DismissEvent)
+ }
+}
+
+impl Render for FeedbackModal {
+ type Element = Div;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> 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));
+// });
+// })?;
+// }
+// }
@@ -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::<Vec<_>>()
{
@@ -693,51 +691,6 @@ impl AppContext {
});
}
- fn apply_focus_changed_effect(
- &mut self,
- window_handle: AnyWindowHandle,
- focused: Option<FocusId>,
- ) {
- 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<AnyView> {
- self.active_drag.as_ref().map(|drag| drag.view.clone())
+ pub fn active_drag<T: 'static>(&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<dyn Any>,
},
- FocusChanged {
- window_handle: AnyWindowHandle,
- focused: Option<FocusId>,
- },
Refresh,
NotifyGlobalObservers {
global_type: TypeId,
@@ -1296,6 +1247,7 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
/// within the window or by dragging into the app from the underlying platform.
pub struct AnyDrag {
pub view: AnyView,
+ pub value: Box<dyn Any>,
pub cursor_offset: Point<Pixels>,
}
@@ -23,7 +23,7 @@ pub trait IntoElement: Sized {
self.into_element().into_any()
}
- fn draw<T, R>(
+ fn draw_and_update_state<T, R>(
self,
origin: Point<Pixels>,
available_space: Size<T>,
@@ -92,7 +92,7 @@ pub trait Element: 'static + IntoElement {
cx: &mut WindowContext,
) -> (LayoutId, Self::State);
- fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
fn into_any(self) -> AnyElement {
AnyElement::new(self)
@@ -150,8 +150,8 @@ impl<C: RenderOnce> Element for Component<C> {
}
}
- fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
- let element = state.rendered_element.take().unwrap();
+ fn paint(&mut self, bounds: Bounds<Pixels>, 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<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
@@ -465,8 +465,8 @@ impl Element for AnyElement {
(layout_id, ())
}
- fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
- self.paint(cx);
+ fn paint(&mut self, _: Bounds<Pixels>, _: &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<Pixels>, _state: &mut Self::State, _cx: &mut WindowContext) {}
+ fn paint(
+ &mut self,
+ _bounds: Bounds<Pixels>,
+ _state: &mut Self::State,
+ _cx: &mut WindowContext,
+ ) {
+ }
}
@@ -4,13 +4,13 @@ use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled
pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
Canvas {
- paint_callback: Box::new(callback),
+ paint_callback: Some(Box::new(callback)),
style: StyleRefinement::default(),
}
}
pub struct Canvas {
- paint_callback: Box<dyn FnOnce(&Bounds<Pixels>, &mut WindowContext)>,
+ paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &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<Pixels>, _: &mut (), cx: &mut WindowContext) {
- (self.paint_callback)(&bounds, cx)
+ fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
+ style.paint(bounds, cx, |cx| {
+ (self.paint_callback.take().unwrap())(&bounds, cx)
+ });
}
}
@@ -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<StyleRefinement>,
}
-pub struct DragMoveEvent<W: Render> {
+pub struct DragMoveEvent<T> {
pub event: MouseMoveEvent,
- pub drag: View<W>,
+ pub bounds: Bounds<Pixels>,
+ drag: PhantomData<T>,
+}
+
+impl<T: 'static> DragMoveEvent<T> {
+ pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T {
+ cx.active_drag
+ .as_ref()
+ .and_then(|drag| drag.value.downcast_ref::<T>())
+ .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<T>(
+ &mut self,
+ listener: impl Fn(&DragMoveEvent<T>, &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::<T>())
+ {
+ (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<A: Action>(
+ &mut self,
+ listener: impl Fn(&A, &mut WindowContext) + 'static,
+ ) {
+ self.action_listeners.push((
+ TypeId::of::<A>(),
+ Box::new(move |action, phase, cx| {
+ let action = action.downcast_ref().unwrap();
+ if phase == DispatchPhase::Capture {
+ (listener)(action, cx)
+ }
+ }),
+ ));
+ }
+
+ pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
+ self.action_listeners.push((
+ TypeId::of::<A>(),
+ 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<dyn Action>,
+ listener: impl Fn(&Box<dyn Action>, &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<T: 'static>(&mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) {
+ self.drop_listeners.push((
+ TypeId::of::<T>(),
+ 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<T, W>(
+ &mut self,
+ value: T,
+ constructor: impl Fn(&T, &mut WindowContext) -> View<W> + '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<W>(
+ fn on_drag_move<T: 'static>(
mut self,
- listener: impl Fn(&DragMoveEvent<W>, &mut WindowContext) + 'static,
+ listener: impl Fn(&DragMoveEvent<T>, &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::<W>().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::<A>(),
- 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<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
- self.interactivity().action_listeners.push((
- TypeId::of::<A>(),
- 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<dyn Action>,
listener: impl Fn(&Box<dyn Action>, &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<W: 'static>(
- mut self,
- listener: impl Fn(&View<W>, &mut WindowContext) + 'static,
- ) -> Self {
- self.interactivity().drop_listeners.push((
- TypeId::of::<W>(),
- Box::new(move |dragged_view, cx| {
- listener(&dragged_view.downcast().unwrap(), cx);
- }),
- ));
+ fn on_drop<T: 'static>(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<W>(mut self, constructor: impl Fn(&mut WindowContext) -> View<W> + 'static) -> Self
+ fn on_drag<T, W>(
+ mut self,
+ value: T,
+ constructor: impl Fn(&T, &mut WindowContext) -> View<W> + '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<FocusListener>;
-
-pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
-
pub type MouseDownListener =
Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
pub type MouseUpListener =
@@ -513,9 +648,9 @@ pub type ScrollWheelListener =
pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
-pub type DragListener = Box<dyn Fn(Point<Pixels>, &mut WindowContext) -> AnyDrag + 'static>;
+pub type DragListener = Box<dyn Fn(&dyn Any, &mut WindowContext) -> AnyView + 'static>;
-type DropListener = dyn Fn(AnyView, &mut WindowContext) + 'static;
+type DropListener = Box<dyn Fn(&dyn Any, &mut WindowContext) + 'static>;
pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
@@ -529,17 +664,20 @@ pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext
#[track_caller]
pub fn div() -> 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<Pixels>,
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<KeyDownListener>,
pub key_up_listeners: Vec<KeyUpListener>,
pub action_listeners: Vec<(TypeId, ActionListener)>,
- pub drop_listeners: Vec<(TypeId, Box<DropListener>)>,
+ pub drop_listeners: Vec<(TypeId, DropListener)>,
pub click_listeners: Vec<ClickListener>,
- pub drag_listener: Option<DragListener>,
+ pub drag_listener: Option<(Box<dyn Any>, DragListener)>,
pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
pub tooltip_builder: Option<TooltipBuilder>,
@@ -770,7 +907,7 @@ impl Interactivity {
}
pub fn paint(
- mut self,
+ &mut self,
bounds: Bounds<Pixels>,
content_size: Size<Pixels>,
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::<crate::DebugBelow>())
&& 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)
}
@@ -81,11 +81,12 @@ impl Element for Img {
}
fn paint(
- self,
+ &mut self,
bounds: Bounds<Pixels>,
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
@@ -257,7 +257,7 @@ impl Element for List {
}
fn paint(
- self,
+ &mut self,
bounds: crate::Bounds<crate::Pixels>,
_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;
@@ -81,7 +81,7 @@ impl Element for Overlay {
}
fn paint(
- self,
+ &mut self,
bounds: crate::Bounds<crate::Pixels>,
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);
}
})
@@ -36,8 +36,12 @@ impl Element for Svg {
})
}
- fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext)
- where
+ fn paint(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ element_state: &mut Self::State,
+ cx: &mut WindowContext,
+ ) where
Self: Sized,
{
self.interactivity
@@ -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<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, 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<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, 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<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, 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<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
- if let Some(click_listener) = self.click_listener {
+ fn paint(&mut self, bounds: Bounds<Pixels>, 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,
@@ -155,7 +155,7 @@ impl Element for UniformList {
}
fn paint(
- self,
+ &mut self,
bounds: Bounds<crate::Pixels>,
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(
@@ -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<FocusHandle>,
- pub focused: Option<FocusHandle>,
-}
-
#[cfg(test)]
mod test {
use crate::{
@@ -29,6 +29,7 @@ pub(crate) struct DispatchNode {
pub key_listeners: Vec<KeyListener>,
pub action_listeners: Vec<DispatchActionListener>,
pub context: Option<KeyContext>,
+ focus_id: Option<FocusId>,
parent: Option<DispatchNodeId>,
}
@@ -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]
}
@@ -303,6 +303,7 @@ impl MetalRenderer {
command_buffer.commit();
self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
+
command_buffer.wait_until_completed();
drawable.present();
}
@@ -110,7 +110,7 @@ pub struct Style {
/// The mouse cursor style shown when the mouse pointer is over an element.
pub mouse_cursor: Option<CursorStyle>,
- pub z_index: Option<u32>,
+ pub z_index: Option<u8>,
#[cfg(debug_assertions)]
pub debug: bool,
@@ -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
}
@@ -7,6 +7,7 @@ use crate::{
use anyhow::{Context, Result};
use std::{
any::TypeId,
+ fmt,
hash::{Hash, Hasher},
};
@@ -90,7 +91,7 @@ impl<V: Render> Element for View<V> {
(layout_id, Some(element))
}
- fn paint(self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
element.take().unwrap().paint(cx);
}
}
@@ -170,7 +171,7 @@ impl<V> Eq for WeakView<V> {}
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<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, _: Bounds<Pixels>, 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<V: 'static + Render> From<WeakView<V>> 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<T, E> Render for T
where
T: 'static + FnMut(&mut WindowContext) -> E,
@@ -324,7 +339,7 @@ mod any_view {
pub(crate) fn paint<V: 'static + Render>(
_view: &AnyView,
- element: AnyElement,
+ element: &mut AnyElement,
cx: &mut WindowContext,
) {
element.paint(cx);
@@ -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<dyn FnMut(&mut WindowContext) -> bool + 'static>;
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
-type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> 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<RwLock<SlotMap<FocusId, AtomicUsize>>>,
- 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<Pixels>,
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<Option<FocusId>>,
pub(crate) focus: Option<FocusId>,
+
+ #[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<GlobalElementId, ElementStateBox>,
- mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
+ focus: Option<FocusId>,
+ pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
+ mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
pub(crate) dispatch_tree: DispatchTree,
- pub(crate) focus_listeners: Vec<AnyFocusListener>,
pub(crate) scene_builder: SceneBuilder,
pub(crate) depth_map: Vec<(StackingOrder, Bounds<Pixels>)>,
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<Event: 'static>(
&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<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+ pub fn with_z_index<R>(&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<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+ pub fn with_z_index<R>(&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<SmallVec<[u32; 16]>> for StackingOrder {
- fn from(small_vec: SmallVec<[u32; 16]>) -> Self {
- StackingOrder(small_vec)
- }
-}
+// #[cfg(any(test, feature = "test-support"))]
+// impl From<SmallVec<[u32; 16]>> for StackingOrder {
+// fn from(small_vec: SmallVec<[u32; 16]>) -> Self {
+// StackingOrder(small_vec)
+// }
+// }
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum ElementId {
@@ -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::<DraggedProjectEntryView>(|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<DraggedProjectEntryView>, cx| {
- this.move_entry(dragged_view.read(cx).entry_id, entry_id, kind.is_file(), cx);
- },
- ))
+ .drag_over::<ProjectEntryId>(|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);
@@ -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| {
@@ -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::<BufferSearchBar>() {
- 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);
@@ -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<ViewEvent> for ProjectSearchView {}
impl Render for ProjectSearchView {
- type Element = Div;
+ type Element = AnyElement;
+
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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<Editor>, cx: &ViewContext<Self>) -> 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)),
),
)
})
@@ -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
@@ -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::<Vec<_>>();
-
- // 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(())
-}
@@ -1,4 +0,0 @@
-pub mod assets;
-
-pub mod stories;
-pub mod story_selector;
@@ -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 }
}
}
@@ -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::<Vec<_>>();
+
+ 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::<ThemeRegistry>();
let mut theme_settings = ThemeSettings::get_global(cx).clone();
@@ -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<Pixels>,
mode: TermMode,
bounds: Bounds<Pixels>,
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::<ExternalPaths>({
+ 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<Pixels>, state: &mut Self::State, cx: &mut WindowContext<'_>) {
+ fn paint(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ 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::<ExternalPaths>(|style| {
- // todo!() why does not it work? z-index of elements?
- style.bg(cx.theme().colors().ghost_element_hover)
- })
- .on_drop::<ExternalPaths>(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::<ExternalPaths>(|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)
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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),
(
@@ -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
@@ -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",
@@ -182,12 +182,12 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
}
fn paint(
- self,
+ &mut self,
_: Bounds<gpui::Pixels>,
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<M: ManagedView> Element for PopoverMenu<M> {
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 {
@@ -112,21 +112,21 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
}
fn paint(
- self,
+ &mut self,
bounds: Bounds<gpui::Pixels>,
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();
@@ -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,
}
}
@@ -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(())
@@ -41,7 +41,7 @@ pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
}
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<T> PanelHandle for View<T>
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<Dock>,
- workspace: WeakView<Workspace>,
}
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<Self>) {
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<f32> {
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<Dock>,
- workspace: WeakView<Workspace>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
+ pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> 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>) -> Self::Element {
-// todo!("")
-// }
-
-// fn ui_name() -> &'static str {
-// "PanelButtons"
-// }
-
-// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-// let theme = &settings::get::<ThemeSettings>(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::<Vec<_>>();
-// 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::<Self, _>(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::<Self>(
-// 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;
@@ -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<Div>;
fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
- gpui::div()
+ gpui::div().track_focus(&self.focus_handle)
}
}
impl EventEmitter<ItemEvent> 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<usize>,
- selected: bool,
- cx: &ui::prelude::WindowContext,
+ _selected: bool,
+ _cx: &ui::prelude::WindowContext,
) -> AnyElement {
self.tab_detail.set(detail);
gpui::div().into_any_element()
@@ -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<Self>) -> bool {
+ fn on_before_dismiss(&mut self, _: &mut ViewContext<Self>) -> bool {
true
}
}
@@ -27,7 +27,7 @@ impl<V: ModalView> ModalViewHandle for View<V> {
pub struct ActiveModal {
modal: Box<dyn ModalViewHandle>,
- subscription: Subscription,
+ _subscription: Subscription,
previous_focus_handle: Option<FocusHandle>,
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))
@@ -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<Arc<dyn Fn(&mut ViewContext<Self>)>>,
click_message: Option<SharedString>,
}
@@ -200,23 +191,12 @@ pub mod simple_message_notification {
S: Into<SharedString>,
{
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<S>(mut self, message: S) -> Self
where
S: Into<SharedString>,
@@ -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| {
@@ -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<Box<dyn ItemHandle>>,
@@ -184,10 +180,11 @@ pub struct Pane {
// tab_context_menu: ViewHandle<ContextMenu>,
workspace: WeakView<Workspace>,
project: Model<Project>,
+ drag_split_direction: Option<SplitDirection>,
// can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
can_split: bool,
// render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
- subscriptions: Vec<Subscription>,
+ _subscriptions: Vec<Subscription>,
tab_bar_scroll_handle: ScrollHandle,
}
@@ -231,6 +228,7 @@ pub struct NavigationEntry {
pub timestamp: usize,
}
+#[derive(Clone)]
struct DraggedTab {
pub pane: View<Pane>,
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<Workspace> {
- &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::<DraggedTab>(|tab| tab.bg(cx.theme().colors().tab_active_background))
- .on_drop(
- cx.listener(move |this, dragged_tab: &View<DraggedTab>, 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>(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::<DraggedTab>(|tab| tab.bg(cx.theme().colors().tab_active_background))
+ .drag_over::<ProjectEntryId>(|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>(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::<DraggedTab>(|bar| {
bar.bg(cx.theme().colors().tab_active_background)
})
- .on_drop(
- cx.listener(move |this, dragged_tab: &View<DraggedTab>, cx| {
- this.handle_tab_drop(dragged_tab, this.items.len(), cx)
- }),
- ),
+ .drag_over::<ProjectEntryId>(|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<T>(&mut self, event: &DragMoveEvent<T>, cx: &mut ViewContext<Self>) {
+ 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<DraggedTab>,
+ 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<DraggedTab>,
- 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<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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::<DraggedTab>(cx.listener(Self::handle_drag_move))
+ .on_drag_move::<ProjectEntryId>(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::<DraggedTab>(|style| style.bg(drag_target_color))
- .on_drop(cx.listener(
- move |this, dragged_tab: &View<DraggedTab>, 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::<DraggedTab>(|style| style.visible())
- .on_drop(cx.listener(
- move |this, dragged_tab: &View<DraggedTab>, 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::<DraggedTab>("", |style| style.visible())
+ .group_drag_over::<ProjectEntryId>("", |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| {
@@ -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<Pane>),
@@ -426,12 +426,6 @@ pub(crate) struct PaneAxis {
pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
}
-impl PartialEq for PaneAxis {
- fn eq(&self, other: &Self) -> bool {
- todo!()
- }
-}
-
impl PaneAxis {
pub fn new(axis: Axis, members: Vec<Member>) -> 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<ui::prelude::Pixels>,
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::<f32>() - flexes.len() as f32).abs() < 0.001
}
- // // use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
-
- // // use gpui::{
- // // geometry::{
- // // rect::Bounds<Pixels>,
- // // vector::{vec2f, Vector2F},
- // // },
- // // json::{self, ToJson},
- // // platform::{CursorStyle, MouseButton},
- // // scene::MouseDrag,
- // // AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, Bounds<Pixels>Ext,
- // // 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<usize>,
- // flexes: Rc<RefCell<Vec<f32>>>,
- // children: Vec<AnyElement<Workspace>>,
- // bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
- // }
-
- // impl PaneAxisElement {
- // pub fn new(
- // axis: Axis,
- // basis: usize,
- // flexes: Rc<RefCell<Vec<f32>>>,
- // bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
- // ) -> 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<usize>) {
- // 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<Workspace>,
- // ) {
- // 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<RefCell<Vec<f32>>>,
- // axis: Axis,
- // preceding_ix: usize,
- // child_start: Vector2F,
- // drag_bounds: Bounds<Pixels>,
- // ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
- // 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<AnyElement<Workspace>> for PaneAxisElement {
- // fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
- // self.children.extend(children);
- // }
- // }
-
- // impl Element<Workspace> for PaneAxisElement {
- // type LayoutState = f32;
- // type PaintState = ();
-
- // fn layout(
- // &mut self,
- // constraint: SizeConstraint,
- // view: &mut Workspace,
- // cx: &mut ViewContext<Workspace>,
- // ) -> (Vector2F, Self::LayoutState) {
- // debug_assert!(self.children.len() == self.flexes.borrow().len());
-
- // let active_pane_magnification =
- // settings::get::<WorkspaceSettings>(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<Pixels>,
- // visible_bounds: Bounds<Pixels>,
- // remaining_space: &mut Self::LayoutState,
- // view: &mut Workspace,
- // cx: &mut ViewContext<Workspace>,
- // ) -> Self::PaintState {
- // let can_resize = settings::get::<WorkspaceSettings>(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<Pixels>::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<Pixels>::new(
- // handle_origin,
- // vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
- // ),
- // Axis::Vertical => Bounds<Pixels>::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::<ResizeHandle>(
- // 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<usize>,
- // _: Bounds<Pixels>,
- // _: Bounds<Pixels>,
- // _: &Self::LayoutState,
- // _: &Self::PaintState,
- // view: &Workspace,
- // cx: &ViewContext<Workspace>,
- // ) -> Option<Bounds<Pixels>> {
- // self.children
- // .iter()
- // .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
- // }
-
- // fn debug(
- // &self,
- // bounds: Bounds<Pixels>,
- // _: &Self::LayoutState,
- // _: &Self::PaintState,
- // view: &Workspace,
- // cx: &ViewContext<Workspace>,
- // ) -> 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::<Vec<json::Value>>()
- // })
- // }
- // }
}
@@ -193,7 +193,7 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
cx: &mut WindowContext,
) -> Task<Vec<Box<dyn Any + Send>>> {
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<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
- fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+ fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.upgrade()?))
}
@@ -51,14 +51,14 @@ impl Render for StatusBar {
}
impl StatusBar {
- fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ fn render_left_tools(&self, _: &mut ViewContext<Self>) -> 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<Self>) -> impl IntoElement {
+ fn render_right_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
h_stack()
.items_center()
.gap_2()
@@ -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<Item = &dyn ToolbarItemViewHandle> {
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>) -> 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)
}
@@ -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<AppState>, 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<AppState>,
- subscriptions: Vec<Subscription>,
+ _subscriptions: Vec<Subscription>,
_apply_leader_updates: Task<Result<()>>,
_observe_current_user: Task<Result<()>>,
_schedule_serialize: Option<Task<()>>,
@@ -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::<DragAndDrop<Workspace>, _, _>(|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<Self>) {
- 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<Self>) {
- 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<Self>) {
- 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<Self>) {
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<Self>) -> Option<Div> {
+ fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
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>) {
self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
cx.background_executor()
@@ -3143,12 +3138,7 @@ impl Workspace {
cx: &mut ViewContext<Workspace>,
) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
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<Self>) -> Div {
+ fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> 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<Pixels>);
-
-#[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<DraggedDock>, 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<ActiveCall>,
cx: &mut AsyncAppContext,
) -> Result<bool> {
- 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<Size<GlobalPixels>> {
#[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::<TestPanel>(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::<TestPanel>(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::<TestPanel>(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::<TestPanel>(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::<TestPanel>(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::<TestPanel>(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::<TestPanel>(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::<TestPanel>(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| {
@@ -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,
@@ -22,4 +22,4 @@ pub struct OpenZedURL {
impl_actions!(zed, [OpenBrowser, OpenZedURL]);
-actions!(zed, [Quit]);
+actions!(zed, [OpenSettings, Quit]);
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>com.apple.security.get-task-allow</key>
+ <true/>
+ </dict>
+</plist>
@@ -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