Continue redesign for the v1 stable release #5
325
Cargo.lock
generated
325
Cargo.lock
generated
@@ -291,9 +291,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.37"
|
version = "0.4.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40"
|
checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compression-codecs",
|
"compression-codecs",
|
||||||
"compression-core",
|
"compression-core",
|
||||||
@@ -501,10 +501,8 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "async-wsocket"
|
name = "async-wsocket"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/shadowylab/async-wsocket?rev=0fed6c9c6aec7393ee0e9cf3933d76914ab427d3#0fed6c9c6aec7393ee0e9cf3933d76914ab427d3"
|
||||||
checksum = "9a7d8c7d34a225ba919dd9ba44d4b9106d20142da545e086be8ae21d1897e043"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@@ -514,6 +512,7 @@ dependencies = [
|
|||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1264,7 +1263,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -1709,7 +1708,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1914,7 +1913,7 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml 0.9.11+spec-1.1.0",
|
"toml 0.9.12+spec-1.1.0",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
@@ -2008,7 +2007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2535,6 +2534,19 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
"wasip3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gif"
|
name = "gif"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@@ -2627,7 +2639,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2729,7 +2741,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2740,7 +2752,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2975,7 +2987,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -3000,7 +3012,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -3197,6 +3209,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -3497,6 +3515,12 @@ dependencies = [
|
|||||||
"leak",
|
"leak",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128fmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lebe"
|
name = "lebe"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -3505,15 +3529,15 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.180"
|
version = "0.2.181"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
version = "0.4.10"
|
version = "0.4.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
|
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -3756,7 +3780,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen",
|
"bindgen",
|
||||||
@@ -3770,9 +3794,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.6"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
@@ -3996,7 +4020,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.44.1"
|
version = "0.44.1"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7"
|
source = "git+https://github.com/rust-nostr/nostr#aeee536bdec863afffffe4819150230e20b8ad6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -4021,7 +4045,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7"
|
source = "git+https://github.com/rust-nostr/nostr#aeee536bdec863afffffe4819150230e20b8ad6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -4034,7 +4058,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7"
|
source = "git+https://github.com/rust-nostr/nostr#aeee536bdec863afffffe4819150230e20b8ad6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"btreecap",
|
"btreecap",
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
@@ -4046,7 +4070,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-gossip"
|
name = "nostr-gossip"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7"
|
source = "git+https://github.com/rust-nostr/nostr#aeee536bdec863afffffe4819150230e20b8ad6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nostr",
|
"nostr",
|
||||||
]
|
]
|
||||||
@@ -4054,7 +4078,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-gossip-memory"
|
name = "nostr-gossip-memory"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7"
|
source = "git+https://github.com/rust-nostr/nostr#aeee536bdec863afffffe4819150230e20b8ad6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -4066,7 +4090,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7"
|
source = "git+https://github.com/rust-nostr/nostr#aeee536bdec863afffffe4819150230e20b8ad6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"flume",
|
"flume",
|
||||||
@@ -4080,7 +4104,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.44.1"
|
version = "0.44.1"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7"
|
source = "git+https://github.com/rust-nostr/nostr#aeee536bdec863afffffe4819150230e20b8ad6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -4098,9 +4122,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081"
|
checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
@@ -4111,7 +4135,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4614,7 +4638,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4910,9 +4934,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psm"
|
name = "psm"
|
||||||
version = "0.1.29"
|
version = "0.1.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d"
|
checksum = "3852766467df634d74f0b2d7819bf8dc483a0eb2e3b0f50f756f9cfe8b0d18d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ar_archive_writer",
|
"ar_archive_writer",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -5021,7 +5045,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5274,7 +5298,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
]
|
]
|
||||||
@@ -5373,7 +5397,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5428,7 +5452,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rope"
|
name = "rope"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -5541,7 +5565,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.11.0",
|
"linux-raw-sys 0.11.0",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5656,9 +5680,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "salsa20"
|
name = "salsa20"
|
||||||
@@ -5690,7 +5714,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "scheduler"
|
name = "scheduler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-task",
|
"async-task",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
@@ -6176,9 +6200,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stacker"
|
name = "stacker"
|
||||||
version = "0.1.22"
|
version = "0.1.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59"
|
checksum = "08d74a23609d509411d10e2176dc2a4346e3b4aea2e7b1869f19fdedbc71c013"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -6305,7 +6329,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -6316,15 +6340,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval"
|
name = "sval"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "502b8906c4736190684646827fbab1e954357dfe541013bbd7994d033d53a1ca"
|
checksum = "c1aaf178a50bbdd86043fce9bf0a5867007d9b382db89d1c96ccae4601ff1ff9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval_buffer"
|
name = "sval_buffer"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4b854348b15b6c441bdd27ce9053569b016a0723eab2d015b1fd8e6abe4f708"
|
checksum = "f89273e48f03807ebf51c4d81c52f28d35ffa18a593edf97e041b52de143df89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sval",
|
"sval",
|
||||||
"sval_ref",
|
"sval_ref",
|
||||||
@@ -6332,18 +6356,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval_dynamic"
|
name = "sval_dynamic"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0bd9e8b74410ddad37c6962587c5f9801a2caadba9e11f3f916ee3f31ae4a1f"
|
checksum = "0430f4e18e7eba21a49d10d25a8dec3ce0e044af40b162347e99a8e3c3ced864"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sval",
|
"sval",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval_fmt"
|
name = "sval_fmt"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fe17b8deb33a9441280b4266c2d257e166bafbaea6e66b4b34ca139c91766d9"
|
checksum = "835f51b9d7331b9d7fc48fc716c02306fa88c4a076b1573531910c91a525882d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -6352,9 +6376,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval_json"
|
name = "sval_json"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "854addb048a5bafb1f496c98e0ab5b9b581c3843f03ca07c034ae110d3b7c623"
|
checksum = "13cbfe3ef406ee2366e7e8ab3678426362085fa9eaedf28cb878a967159dced3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -6363,9 +6387,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval_nested"
|
name = "sval_nested"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96cf068f482108ff44ae8013477cb047a1665d5f1a635ad7cf79582c1845dce9"
|
checksum = "8b20358af4af787c34321a86618c3cae12eabdd0e9df22cd9dd2c6834214c518"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sval",
|
"sval",
|
||||||
"sval_buffer",
|
"sval_buffer",
|
||||||
@@ -6374,18 +6398,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval_ref"
|
name = "sval_ref"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed02126365ffe5ab8faa0abd9be54fbe68d03d607cd623725b0a71541f8aaa6f"
|
checksum = "fb5e500f8eb2efa84f75e7090f7fc43f621b9f8b6cde571c635b3855f97b332a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sval",
|
"sval",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sval_serde"
|
name = "sval_serde"
|
||||||
version = "2.16.0"
|
version = "2.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a263383c6aa2076c4ef6011d3bae1b356edf6ea2613e3d8e8ebaa7b57dd707d5"
|
checksum = "ca2032ae39b11dcc6c18d5fbc50a661ea191cac96484c59ccf49b002261ca2c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"sval",
|
"sval",
|
||||||
@@ -6557,15 +6581,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.24.0"
|
version = "3.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand 2.3.0",
|
"fastrand 2.3.0",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.4.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix 1.1.3",
|
"rustix 1.1.3",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6812,9 +6836,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-tungstenite"
|
name = "tokio-tungstenite"
|
||||||
version = "0.26.2"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
@@ -6853,9 +6877,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.9.11+spec-1.1.0"
|
version = "0.9.12+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
|
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -6912,9 +6936,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_parser"
|
name = "toml_parser"
|
||||||
version = "1.0.6+spec-1.1.0"
|
version = "1.0.7+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
|
checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
@@ -7051,9 +7075,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.26.2"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
@@ -7140,9 +7164,9 @@ checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.22"
|
version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
@@ -7189,6 +7213,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "universal-hash"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -7266,7 +7296,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -7304,7 +7334,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7449,6 +7479,15 @@ dependencies = [
|
|||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip3"
|
||||||
|
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasite"
|
name = "wasite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -7514,6 +7553,28 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||||
|
dependencies = [
|
||||||
|
"leb128fmt",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-streams"
|
name = "wasm-streams"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -7527,6 +7588,18 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-backend"
|
name = "wayland-backend"
|
||||||
version = "0.3.12"
|
version = "0.3.12"
|
||||||
@@ -7647,9 +7720,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webbrowser"
|
name = "webbrowser"
|
||||||
version = "1.0.6"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97"
|
checksum = "3f00bb839c1cf1e3036066614cbdcd035ecf215206691ea646aa3c60a24f68f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation 0.10.0",
|
"core-foundation 0.10.0",
|
||||||
"jni",
|
"jni",
|
||||||
@@ -7748,7 +7821,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8349,6 +8422,88 @@ name = "wit-bindgen"
|
|||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rust-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck 0.5.0",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck 0.5.0",
|
||||||
|
"indexmap",
|
||||||
|
"prettyplease",
|
||||||
|
"syn 2.0.114",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-component",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.114",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-bindgen-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wasmparser",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
@@ -8780,7 +8935,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "zlog"
|
name = "zlog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -8790,14 +8945,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
|
checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing"
|
name = "ztracing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -8808,7 +8963,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing_macro"
|
name = "ztracing_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734"
|
source = "git+https://github.com/zed-industries/zed#6cd7586c161b579ba7c1ecad7dea1b95ca3dd239"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use ui::Root;
|
|||||||
use crate::actions::Quit;
|
use crate::actions::Quit;
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
mod command_bar;
|
|
||||||
mod dialogs;
|
mod dialogs;
|
||||||
mod panels;
|
mod panels;
|
||||||
mod sidebar;
|
mod sidebar;
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Set up relay list")
|
.label("Set up relay list")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.no_center()
|
.justify_start()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
relay_list::init(window, cx),
|
relay_list::init(window, cx),
|
||||||
@@ -172,7 +172,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Set up messaging relays")
|
.label("Set up messaging relays")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.no_center()
|
.justify_start()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
messaging_relays::init(window, cx),
|
messaging_relays::init(window, cx),
|
||||||
@@ -211,7 +211,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Connect account via Nostr Connect")
|
.label("Connect account via Nostr Connect")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.no_center()
|
.justify_start()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
connect::init(window, cx),
|
connect::init(window, cx),
|
||||||
@@ -227,7 +227,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Import a secret key or bunker")
|
.label("Import a secret key or bunker")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.no_center()
|
.justify_start()
|
||||||
.on_click(move |_ev, window, cx| {
|
.on_click(move |_ev, window, cx| {
|
||||||
Workspace::add_panel(
|
Workspace::add_panel(
|
||||||
import::init(window, cx),
|
import::init(window, cx),
|
||||||
@@ -264,7 +264,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Backup account")
|
.label("Backup account")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.no_center(),
|
.justify_start(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("profile")
|
Button::new("profile")
|
||||||
@@ -272,7 +272,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Update profile")
|
.label("Update profile")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.no_center()
|
.justify_start()
|
||||||
.on_click(cx.listener(move |this, _ev, window, cx| {
|
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||||
this.add_profile_panel(window, cx)
|
this.add_profile_panel(window, cx)
|
||||||
})),
|
})),
|
||||||
@@ -283,7 +283,7 @@ impl Render for GreeterPanel {
|
|||||||
.label("Invite friends")
|
.label("Invite friends")
|
||||||
.ghost()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.no_center(),
|
.justify_start(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -438,9 +438,9 @@ impl Render for CommandBar {
|
|||||||
.w_full()
|
.w_full()
|
||||||
.child(
|
.child(
|
||||||
TextInput::new(&self.find_input)
|
TextInput::new(&self.find_input)
|
||||||
.appearance(true)
|
.appearance(false)
|
||||||
.bordered(false)
|
.bordered(false)
|
||||||
.xsmall()
|
.small()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.when(!self.find_input.read(cx).loading, |this| {
|
.when(!self.find_input.read(cx).loading, |this| {
|
||||||
this.suffix(
|
this.suffix(
|
||||||
@@ -1,23 +1,38 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use chat::{ChatEvent, ChatRegistry, RoomKind};
|
use anyhow::Error;
|
||||||
use common::RenderedTimestamp;
|
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
||||||
|
use common::{DebouncedDelay, RenderedTimestamp};
|
||||||
use dock::panel::{Panel, PanelEvent};
|
use dock::panel::{Panel, PanelEvent};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
deferred, div, uniform_list, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
|
div, rems, uniform_list, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
|
||||||
Focusable, IntoElement, ParentElement, Render, RetainAllImageCache, SharedString, Styled,
|
Focusable, InteractiveElement, IntoElement, ParentElement, Render, RetainAllImageCache,
|
||||||
Subscription, Window,
|
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Window,
|
||||||
};
|
};
|
||||||
use list_item::RoomListItem;
|
use list_item::RoomListItem;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use person::PersonRegistry;
|
||||||
|
use settings::AppSettings;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use state::{NostrRegistry, FIND_DELAY};
|
||||||
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
use theme::{ActiveTheme, TABBAR_HEIGHT};
|
||||||
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
|
use ui::divider::Divider;
|
||||||
use ui::indicator::Indicator;
|
use ui::indicator::Indicator;
|
||||||
use ui::{h_flex, v_flex, IconName, Selectable, Sizable, StyledExt};
|
use ui::input::{InputEvent, InputState, TextInput};
|
||||||
|
use ui::notification::Notification;
|
||||||
|
use ui::{
|
||||||
|
h_flex, v_flex, Disableable, Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension,
|
||||||
|
};
|
||||||
|
|
||||||
mod list_item;
|
mod list_item;
|
||||||
|
|
||||||
|
const INPUT_PLACEHOLDER: &str = "Find or start a conversation";
|
||||||
|
|
||||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
||||||
cx.new(|cx| Sidebar::new(window, cx))
|
cx.new(|cx| Sidebar::new(window, cx))
|
||||||
}
|
}
|
||||||
@@ -30,12 +45,42 @@ pub struct Sidebar {
|
|||||||
/// Image cache
|
/// Image cache
|
||||||
image_cache: Entity<RetainAllImageCache>,
|
image_cache: Entity<RetainAllImageCache>,
|
||||||
|
|
||||||
|
/// Find input state
|
||||||
|
find_input: Entity<InputState>,
|
||||||
|
|
||||||
|
/// Debounced delay for find input
|
||||||
|
find_debouncer: DebouncedDelay<Self>,
|
||||||
|
|
||||||
|
/// Whether a search is in progress
|
||||||
|
finding: bool,
|
||||||
|
|
||||||
|
/// Whether the find input is focused
|
||||||
|
find_focused: bool,
|
||||||
|
|
||||||
|
/// Find results
|
||||||
|
find_results: Entity<Option<Vec<PublicKey>>>,
|
||||||
|
|
||||||
|
/// Async find operation
|
||||||
|
find_task: Option<Task<Result<(), Error>>>,
|
||||||
|
|
||||||
|
/// Whether there are search results
|
||||||
|
has_search: bool,
|
||||||
|
|
||||||
/// Whether there are new chat requests
|
/// Whether there are new chat requests
|
||||||
new_requests: bool,
|
new_requests: bool,
|
||||||
|
|
||||||
|
/// Selected public keys
|
||||||
|
selected_pkeys: Entity<HashSet<PublicKey>>,
|
||||||
|
|
||||||
/// Chatroom filter
|
/// Chatroom filter
|
||||||
filter: Entity<RoomKind>,
|
filter: Entity<RoomKind>,
|
||||||
|
|
||||||
|
/// User's contacts
|
||||||
|
contact_list: Entity<Option<Vec<PublicKey>>>,
|
||||||
|
|
||||||
|
/// Async tasks
|
||||||
|
tasks: SmallVec<[Task<()>; 1]>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
}
|
}
|
||||||
@@ -44,9 +89,49 @@ impl Sidebar {
|
|||||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let filter = cx.new(|_| RoomKind::Ongoing);
|
let filter = cx.new(|_| RoomKind::Ongoing);
|
||||||
|
let contact_list = cx.new(|_| None);
|
||||||
|
let selected_pkeys = cx.new(|_| HashSet::new());
|
||||||
|
let find_results = cx.new(|_| None);
|
||||||
|
let find_input = cx.new(|cx| {
|
||||||
|
InputState::new(window, cx)
|
||||||
|
.placeholder(INPUT_PLACEHOLDER)
|
||||||
|
.clean_on_escape()
|
||||||
|
});
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
|
subscriptions.push(
|
||||||
|
// Subscribe to find input events
|
||||||
|
cx.subscribe_in(&find_input, window, |this, state, event, window, cx| {
|
||||||
|
let delay = Duration::from_millis(FIND_DELAY);
|
||||||
|
|
||||||
|
match event {
|
||||||
|
InputEvent::PressEnter { .. } => {
|
||||||
|
this.search(window, cx);
|
||||||
|
}
|
||||||
|
InputEvent::Change => {
|
||||||
|
if state.read(cx).value().is_empty() {
|
||||||
|
// Clear results when input is empty
|
||||||
|
this.reset(window, cx);
|
||||||
|
} else {
|
||||||
|
// Run debounced search
|
||||||
|
this.find_debouncer
|
||||||
|
.fire_new(delay, window, cx, |this, window, cx| {
|
||||||
|
this.debounced_search(window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputEvent::Focus => {
|
||||||
|
this.set_input_focus(cx);
|
||||||
|
this.get_contact_list(window, cx);
|
||||||
|
}
|
||||||
|
InputEvent::Blur => {
|
||||||
|
this.set_input_focus(cx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
// Subscribe for registry new events
|
// Subscribe for registry new events
|
||||||
cx.subscribe_in(&chat, window, move |this, _s, event, _window, cx| {
|
cx.subscribe_in(&chat, window, move |this, _s, event, _window, cx| {
|
||||||
@@ -61,12 +146,193 @@ impl Sidebar {
|
|||||||
name: "Sidebar".into(),
|
name: "Sidebar".into(),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
image_cache: RetainAllImageCache::new(cx),
|
image_cache: RetainAllImageCache::new(cx),
|
||||||
|
find_input,
|
||||||
|
find_debouncer: DebouncedDelay::new(),
|
||||||
|
find_results,
|
||||||
|
find_task: None,
|
||||||
|
find_focused: false,
|
||||||
|
finding: false,
|
||||||
|
has_search: false,
|
||||||
new_requests: false,
|
new_requests: false,
|
||||||
|
contact_list,
|
||||||
|
selected_pkeys,
|
||||||
filter,
|
filter,
|
||||||
|
tasks: smallvec![],
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the contact list.
|
||||||
|
fn get_contact_list(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let task = nostr.read(cx).get_contact_list(cx);
|
||||||
|
|
||||||
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
|
match task.await {
|
||||||
|
Ok(contacts) => {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.set_contact_list(contacts, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
cx.update(|window, cx| {
|
||||||
|
window.push_notification(Notification::error(e.to_string()), cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the contact list with new contacts.
|
||||||
|
fn set_contact_list<I>(&mut self, contacts: I, cx: &mut Context<Self>)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = PublicKey>,
|
||||||
|
{
|
||||||
|
self.contact_list.update(cx, |this, cx| {
|
||||||
|
*this = Some(contacts.into_iter().collect());
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trigger the debounced search
|
||||||
|
fn debounced_search(&self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
this.search(window, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search
|
||||||
|
fn search(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
// Return if a search is already in progress
|
||||||
|
if self.finding {
|
||||||
|
if self.find_task.is_none() {
|
||||||
|
window.push_notification("There is another search in progress", cx);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Cancel the ongoing search request
|
||||||
|
self.find_task = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get query
|
||||||
|
let query = self.find_input.read(cx).value();
|
||||||
|
|
||||||
|
// Return if the query is empty
|
||||||
|
if query.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block the input until the search completes
|
||||||
|
self.set_finding(true, window, cx);
|
||||||
|
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let find_users = nostr.read(cx).search(&query, cx);
|
||||||
|
|
||||||
|
// Run task in the main thread
|
||||||
|
self.find_task = Some(cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let rooms = find_users.await?;
|
||||||
|
// Update the UI with the search results
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
this.set_results(rooms, cx);
|
||||||
|
this.set_finding(false, window, cx);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_results(&mut self, results: Vec<PublicKey>, cx: &mut Context<Self>) {
|
||||||
|
self.find_results.update(cx, |this, cx| {
|
||||||
|
*this = Some(results);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_finding(&mut self, status: bool, _window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
// Disable the input to prevent duplicate requests
|
||||||
|
self.find_input.update(cx, |this, cx| {
|
||||||
|
this.set_disabled(status, cx);
|
||||||
|
this.set_loading(status, cx);
|
||||||
|
});
|
||||||
|
// Set the search status
|
||||||
|
self.finding = status;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_input_focus(&mut self, cx: &mut Context<Self>) {
|
||||||
|
self.find_focused = !self.find_focused;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
// Clear all search results
|
||||||
|
self.find_results.update(cx, |this, cx| {
|
||||||
|
*this = None;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset the search status
|
||||||
|
self.set_finding(false, window, cx);
|
||||||
|
|
||||||
|
// Cancel the current search task
|
||||||
|
self.find_task = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Select a public key in the sidebar.
|
||||||
|
fn select(&mut self, pkey: PublicKey, cx: &mut Context<Self>) {
|
||||||
|
self.selected_pkeys.update(cx, |this, cx| {
|
||||||
|
if this.contains(&pkey) {
|
||||||
|
this.remove(&pkey);
|
||||||
|
} else {
|
||||||
|
this.insert(pkey);
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a public key is selected in the sidebar.
|
||||||
|
fn is_selected(&self, pkey: PublicKey, cx: &App) -> bool {
|
||||||
|
self.selected_pkeys.read(cx).contains(&pkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all selected public keys in the sidebar.
|
||||||
|
fn selected(&self, cx: &Context<Self>) -> HashSet<PublicKey> {
|
||||||
|
self.selected_pkeys.read(cx).clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new room
|
||||||
|
fn create_room(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let chat = ChatRegistry::global(cx);
|
||||||
|
let async_chat = chat.downgrade();
|
||||||
|
|
||||||
|
let nostr = NostrRegistry::global(cx);
|
||||||
|
let signer_pkey = nostr.read(cx).signer_pkey(cx);
|
||||||
|
|
||||||
|
// Get all selected public keys
|
||||||
|
let receivers = self.selected(cx);
|
||||||
|
|
||||||
|
let task: Task<Result<(), Error>> = cx.spawn_in(window, async move |_this, cx| {
|
||||||
|
let public_key = signer_pkey.await?;
|
||||||
|
|
||||||
|
async_chat.update_in(cx, |this, window, cx| {
|
||||||
|
let room = cx.new(|_| Room::new(public_key, receivers));
|
||||||
|
this.emit_room(room.downgrade(), cx);
|
||||||
|
|
||||||
|
window.close_modal(cx);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
task.detach();
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the active filter.
|
/// Get the active filter.
|
||||||
fn current_filter(&self, kind: &RoomKind, cx: &Context<Self>) -> bool {
|
fn current_filter(&self, kind: &RoomKind, cx: &Context<Self>) -> bool {
|
||||||
self.filter.read(cx) == kind
|
self.filter.read(cx) == kind
|
||||||
@@ -111,6 +377,67 @@ impl Sidebar {
|
|||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_contacts(&self, range: Range<usize>, cx: &Context<Self>) -> Vec<impl IntoElement> {
|
||||||
|
let persons = PersonRegistry::global(cx);
|
||||||
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
|
|
||||||
|
let Some(contacts) = self.contact_list.read(cx) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
contacts
|
||||||
|
.get(range.clone())
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ix, item)| {
|
||||||
|
let profile = persons.read(cx).get(item, cx);
|
||||||
|
let pkey = item.to_owned();
|
||||||
|
let id = range.start + ix;
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.id(id)
|
||||||
|
.h_8()
|
||||||
|
.w_full()
|
||||||
|
.px_1()
|
||||||
|
.gap_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.when(!hide_avatar, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.size_6()
|
||||||
|
.rounded_full()
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(Avatar::new(profile.avatar()).size(rems(1.5))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.flex_1()
|
||||||
|
.justify_between()
|
||||||
|
.line_clamp(1)
|
||||||
|
.text_ellipsis()
|
||||||
|
.truncate()
|
||||||
|
.text_sm()
|
||||||
|
.child(profile.name())
|
||||||
|
.when(self.is_selected(pkey, cx), |this| {
|
||||||
|
this.child(
|
||||||
|
Icon::new(IconName::CheckCircle)
|
||||||
|
.small()
|
||||||
|
.text_color(cx.theme().icon_accent),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.hover(|this| this.bg(cx.theme().elevated_surface_background))
|
||||||
|
.on_click(cx.listener(move |this, _ev, _window, cx| {
|
||||||
|
this.select(pkey, cx);
|
||||||
|
}))
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Panel for Sidebar {
|
impl Panel for Sidebar {
|
||||||
@@ -133,89 +460,124 @@ impl Render for Sidebar {
|
|||||||
let loading = chat.read(cx).loading();
|
let loading = chat.read(cx).loading();
|
||||||
let total_rooms = chat.read(cx).count(self.filter.read(cx), cx);
|
let total_rooms = chat.read(cx).count(self.filter.read(cx), cx);
|
||||||
|
|
||||||
|
// Whether the find panel should be shown
|
||||||
|
let show_find_panel = self.has_search || self.find_focused;
|
||||||
|
|
||||||
|
// Set button label based on total selected users
|
||||||
|
let button_label = if self.selected_pkeys.read(cx).len() > 1 {
|
||||||
|
"Create Group DM"
|
||||||
|
} else {
|
||||||
|
"Create DM"
|
||||||
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.image_cache(self.image_cache.clone())
|
.image_cache(self.image_cache.clone())
|
||||||
.size_full()
|
.size_full()
|
||||||
.relative()
|
.relative()
|
||||||
.gap_2()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.h(TABBAR_HEIGHT)
|
.h(TABBAR_HEIGHT)
|
||||||
.w_full()
|
|
||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
TextInput::new(&self.find_input)
|
||||||
.flex_1()
|
.appearance(false)
|
||||||
.h_full()
|
.bordered(false)
|
||||||
.gap_2()
|
.small()
|
||||||
.p_2()
|
.text_xs()
|
||||||
.justify_center()
|
.when(!self.find_input.read(cx).loading, |this| {
|
||||||
.child(
|
this.suffix(
|
||||||
Button::new("all")
|
Button::new("find-icon")
|
||||||
.map(|this| {
|
.icon(IconName::Search)
|
||||||
if self.current_filter(&RoomKind::Ongoing, cx) {
|
.tooltip("Press Enter to search")
|
||||||
this.icon(IconName::InboxFill)
|
.transparent()
|
||||||
} else {
|
.small(),
|
||||||
this.icon(IconName::Inbox)
|
)
|
||||||
}
|
}),
|
||||||
})
|
|
||||||
.label("Inbox")
|
|
||||||
.tooltip("All ongoing conversations")
|
|
||||||
.xsmall()
|
|
||||||
.bold()
|
|
||||||
.ghost()
|
|
||||||
.flex_1()
|
|
||||||
.rounded_none()
|
|
||||||
.selected(self.current_filter(&RoomKind::Ongoing, cx))
|
|
||||||
.on_click(cx.listener(|this, _, _, cx| {
|
|
||||||
this.set_filter(RoomKind::Ongoing, cx);
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("requests")
|
|
||||||
.map(|this| {
|
|
||||||
if self.current_filter(&RoomKind::Request, cx) {
|
|
||||||
this.icon(IconName::FistbumpFill)
|
|
||||||
} else {
|
|
||||||
this.icon(IconName::Fistbump)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.label("Requests")
|
|
||||||
.tooltip("Incoming new conversations")
|
|
||||||
.xsmall()
|
|
||||||
.bold()
|
|
||||||
.ghost()
|
|
||||||
.flex_1()
|
|
||||||
.rounded_none()
|
|
||||||
.selected(!self.current_filter(&RoomKind::Ongoing, cx))
|
|
||||||
.when(self.new_requests, |this| {
|
|
||||||
this.child(
|
|
||||||
div().size_1().rounded_full().bg(cx.theme().cursor),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|this, _, _, cx| {
|
|
||||||
this.set_filter(RoomKind::default(), cx);
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.h_full()
|
|
||||||
.px_2()
|
|
||||||
.border_l_1()
|
|
||||||
.border_color(cx.theme().border)
|
|
||||||
.child(
|
|
||||||
Button::new("option")
|
|
||||||
.icon(IconName::Ellipsis)
|
|
||||||
.small()
|
|
||||||
.ghost(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.when(!loading && total_rooms == 0, |this| {
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.h(TABBAR_HEIGHT)
|
||||||
|
.justify_center()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().border)
|
||||||
|
.when(show_find_panel, |this| {
|
||||||
|
this.child(
|
||||||
|
Button::new("search-results")
|
||||||
|
.icon(IconName::Search)
|
||||||
|
.label("Search")
|
||||||
|
.tooltip("All search results")
|
||||||
|
.small()
|
||||||
|
.underline()
|
||||||
|
.ghost()
|
||||||
|
.font_semibold()
|
||||||
|
.rounded_none()
|
||||||
|
.h_full()
|
||||||
|
.flex_1()
|
||||||
|
.selected(true),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
Button::new("all")
|
||||||
|
.map(|this| {
|
||||||
|
if self.current_filter(&RoomKind::Ongoing, cx) {
|
||||||
|
this.icon(IconName::InboxFill)
|
||||||
|
} else {
|
||||||
|
this.icon(IconName::Inbox)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.when(!show_find_panel, |this| this.label("Inbox"))
|
||||||
|
.tooltip("All ongoing conversations")
|
||||||
|
.small()
|
||||||
|
.underline()
|
||||||
|
.ghost()
|
||||||
|
.font_semibold()
|
||||||
|
.rounded_none()
|
||||||
|
.h_full()
|
||||||
|
.flex_1()
|
||||||
|
.disabled(show_find_panel)
|
||||||
|
.selected(
|
||||||
|
!show_find_panel && self.current_filter(&RoomKind::Ongoing, cx),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(|this, _ev, _window, cx| {
|
||||||
|
this.set_filter(RoomKind::Ongoing, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(Divider::vertical())
|
||||||
|
.child(
|
||||||
|
Button::new("requests")
|
||||||
|
.map(|this| {
|
||||||
|
if self.current_filter(&RoomKind::Request, cx) {
|
||||||
|
this.icon(IconName::FistbumpFill)
|
||||||
|
} else {
|
||||||
|
this.icon(IconName::Fistbump)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.when(!show_find_panel, |this| this.label("Requests"))
|
||||||
|
.tooltip("Incoming new conversations")
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.underline()
|
||||||
|
.font_semibold()
|
||||||
|
.rounded_none()
|
||||||
|
.h_full()
|
||||||
|
.flex_1()
|
||||||
|
.disabled(show_find_panel)
|
||||||
|
.selected(
|
||||||
|
!show_find_panel && !self.current_filter(&RoomKind::Ongoing, cx),
|
||||||
|
)
|
||||||
|
.when(self.new_requests, |this| {
|
||||||
|
this.child(div().size_1().rounded_full().bg(cx.theme().cursor))
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(|this, _ev, _window, cx| {
|
||||||
|
this.set_filter(RoomKind::default(), cx);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.when(!show_find_panel && !loading && total_rooms == 0, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
div().px_2p5().child(deferred(
|
div().mt_2().px_2().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.p_3()
|
.p_3()
|
||||||
.h_24()
|
.h_24()
|
||||||
@@ -238,47 +600,131 @@ impl Render for Sidebar {
|
|||||||
"Start a conversation with someone to get started.",
|
"Start a conversation with someone to get started.",
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.h_full()
|
||||||
.px_1p5()
|
.px_1p5()
|
||||||
.w_full()
|
.mt_2()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.overflow_y_hidden()
|
.overflow_y_hidden()
|
||||||
.child(
|
.when(show_find_panel, |this| {
|
||||||
uniform_list(
|
this.gap_2()
|
||||||
"rooms",
|
.when_some(self.find_results.read(cx).as_ref(), |this, results| {
|
||||||
total_rooms,
|
this.child(
|
||||||
cx.processor(|this, range, _window, cx| {
|
v_flex()
|
||||||
this.render_list_items(range, cx)
|
.gap_2()
|
||||||
}),
|
.flex_1()
|
||||||
)
|
.child(
|
||||||
.h_full(),
|
div()
|
||||||
)
|
.text_xs()
|
||||||
.when(loading, |this| {
|
.font_semibold()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from("Results")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
uniform_list(
|
||||||
|
"rooms",
|
||||||
|
results.len(),
|
||||||
|
cx.processor(|this, range, _window, cx| {
|
||||||
|
this.render_list_items(range, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.flex_1()
|
||||||
|
.h_full(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(self.contact_list.read(cx).as_ref(), |this, contacts| {
|
||||||
|
this.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_2()
|
||||||
|
.flex_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(SharedString::from("Suggestions")),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
uniform_list(
|
||||||
|
"contacts",
|
||||||
|
contacts.len(),
|
||||||
|
cx.processor(move |this, range, _window, cx| {
|
||||||
|
this.render_contacts(range, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.flex_1()
|
||||||
|
.h_full(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.when(!show_find_panel, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
div().absolute().top_2().left_0().w_full().px_8().child(
|
uniform_list(
|
||||||
h_flex()
|
"rooms",
|
||||||
.gap_2()
|
total_rooms,
|
||||||
.w_full()
|
cx.processor(|this, range, _window, cx| {
|
||||||
.h_9()
|
this.render_list_items(range, cx)
|
||||||
.justify_center()
|
}),
|
||||||
.bg(cx.theme().background.opacity(0.85))
|
)
|
||||||
.border_color(cx.theme().border_disabled)
|
.flex_1()
|
||||||
.border_1()
|
.h_full(),
|
||||||
.when(cx.theme().shadow, |this| this.shadow_sm())
|
|
||||||
.rounded_full()
|
|
||||||
.text_xs()
|
|
||||||
.font_semibold()
|
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.child(Indicator::new().small().color(cx.theme().icon_accent))
|
|
||||||
.child(SharedString::from("Getting messages...")),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.when(!self.selected_pkeys.read(cx).is_empty(), |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.bottom_0()
|
||||||
|
.left_0()
|
||||||
|
.h_9()
|
||||||
|
.w_full()
|
||||||
|
.px_2()
|
||||||
|
.child(
|
||||||
|
Button::new("create")
|
||||||
|
.label(button_label)
|
||||||
|
.primary()
|
||||||
|
.small()
|
||||||
|
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||||
|
this.create_room(window, cx);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(loading, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.bottom_2()
|
||||||
|
.left_0()
|
||||||
|
.h_9()
|
||||||
|
.w_full()
|
||||||
|
.px_8()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.w_full()
|
||||||
|
.h_9()
|
||||||
|
.justify_center()
|
||||||
|
.bg(cx.theme().background.opacity(0.85))
|
||||||
|
.border_color(cx.theme().border_disabled)
|
||||||
|
.border_1()
|
||||||
|
.when(cx.theme().shadow, |this| this.shadow_sm())
|
||||||
|
.rounded_full()
|
||||||
|
.text_xs()
|
||||||
|
.font_semibold()
|
||||||
|
.text_color(cx.theme().text_muted)
|
||||||
|
.child(Indicator::new().small().color(cx.theme().icon_accent))
|
||||||
|
.child(SharedString::from("Getting messages...")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ use nostr_sdk::prelude::*;
|
|||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use state::NostrRegistry;
|
use state::NostrRegistry;
|
||||||
use theme::{ActiveTheme, SIDEBAR_WIDTH, TITLEBAR_HEIGHT};
|
use theme::{SIDEBAR_WIDTH, TITLEBAR_HEIGHT};
|
||||||
use titlebar::TitleBar;
|
use titlebar::TitleBar;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::{h_flex, v_flex, Icon, IconName, Root, Sizable, WindowExtension};
|
use ui::button::{Button, ButtonVariants};
|
||||||
|
use ui::popup_menu::PopupMenuExt;
|
||||||
|
use ui::{h_flex, v_flex, Root, Sizable, WindowExtension};
|
||||||
|
|
||||||
use crate::command_bar::CommandBar;
|
|
||||||
use crate::panels::greeter;
|
use crate::panels::greeter;
|
||||||
use crate::sidebar;
|
use crate::sidebar;
|
||||||
|
|
||||||
@@ -33,9 +34,6 @@ pub struct Workspace {
|
|||||||
/// App's Dock Area
|
/// App's Dock Area
|
||||||
dock: Entity<DockArea>,
|
dock: Entity<DockArea>,
|
||||||
|
|
||||||
/// App's Command Bar
|
|
||||||
command_bar: Entity<CommandBar>,
|
|
||||||
|
|
||||||
/// Current User
|
/// Current User
|
||||||
current_user: Entity<Option<PublicKey>>,
|
current_user: Entity<Option<PublicKey>>,
|
||||||
|
|
||||||
@@ -45,22 +43,16 @@ pub struct Workspace {
|
|||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
let titlebar = cx.new(|_| TitleBar::new());
|
||||||
|
let dock =
|
||||||
|
cx.new(|cx| DockArea::new(window, cx).panel_style(dock::panel::PanelStyle::TabBar));
|
||||||
|
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let current_user = cx.new(|_| None);
|
let current_user = cx.new(|_| None);
|
||||||
|
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let nip65_state = nostr.read(cx).nip65_state();
|
let nip65_state = nostr.read(cx).nip65_state();
|
||||||
|
|
||||||
// Titlebar
|
|
||||||
let titlebar = cx.new(|_| TitleBar::new());
|
|
||||||
|
|
||||||
// Command bar
|
|
||||||
let command_bar = cx.new(|cx| CommandBar::new(window, cx));
|
|
||||||
|
|
||||||
// Dock
|
|
||||||
let dock =
|
|
||||||
cx.new(|cx| DockArea::new(window, cx).panel_style(dock::panel::PanelStyle::TabBar));
|
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
subscriptions.push(
|
subscriptions.push(
|
||||||
@@ -124,7 +116,6 @@ impl Workspace {
|
|||||||
Self {
|
Self {
|
||||||
titlebar,
|
titlebar,
|
||||||
dock,
|
dock,
|
||||||
command_bar,
|
|
||||||
current_user,
|
current_user,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
@@ -213,7 +204,8 @@ impl Workspace {
|
|||||||
fn titlebar_left(&mut self, _window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
|
fn titlebar_left(&mut self, _window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.h(TITLEBAR_HEIGHT)
|
.h(TITLEBAR_HEIGHT)
|
||||||
.flex_1()
|
.w(SIDEBAR_WIDTH)
|
||||||
|
.flex_shrink_0()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.when_some(self.current_user.read(cx).as_ref(), |this, public_key| {
|
.when_some(self.current_user.read(cx).as_ref(), |this, public_key| {
|
||||||
@@ -221,24 +213,30 @@ impl Workspace {
|
|||||||
let profile = persons.read(cx).get(public_key, cx);
|
let profile = persons.read(cx).get(public_key, cx);
|
||||||
|
|
||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
Button::new("current-user")
|
||||||
.gap_0p5()
|
|
||||||
.child(Avatar::new(profile.avatar()).size(rems(1.25)))
|
.child(Avatar::new(profile.avatar()).size(rems(1.25)))
|
||||||
.child(
|
.small()
|
||||||
Icon::new(IconName::ChevronDown)
|
.caret()
|
||||||
.small()
|
.compact()
|
||||||
.text_color(cx.theme().text_muted),
|
.transparent()
|
||||||
),
|
.popup_menu(move |this, _window, _cx| {
|
||||||
|
this.label(profile.name())
|
||||||
|
.separator()
|
||||||
|
.menu("Profile", Box::new(ClosePanel))
|
||||||
|
.menu("Backup", Box::new(ClosePanel))
|
||||||
|
.menu("Themes", Box::new(ClosePanel))
|
||||||
|
.menu("Settings", Box::new(ClosePanel))
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_center(&mut self, _window: &mut Window, _cx: &Context<Self>) -> impl IntoElement {
|
fn titlebar_center(&mut self, _window: &mut Window, _cx: &Context<Self>) -> impl IntoElement {
|
||||||
h_flex().flex_1().w_full().child(self.command_bar.clone())
|
h_flex().h(TITLEBAR_HEIGHT).flex_1()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_right(&mut self, _window: &mut Window, _cx: &Context<Self>) -> impl IntoElement {
|
fn titlebar_right(&mut self, _window: &mut Window, _cx: &Context<Self>) -> impl IntoElement {
|
||||||
h_flex().flex_1()
|
h_flex().h(TITLEBAR_HEIGHT).w(SIDEBAR_WIDTH).flex_shrink_0()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ impl StackPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if self or parent only have last panel.
|
/// Return true if self or parent only have last panel.
|
||||||
pub(super) fn is_last_panel(&self, cx: &App) -> bool {
|
pub fn is_last_panel(&self, cx: &App) -> bool {
|
||||||
if self.panels.len() > 1 {
|
if self.panels.len() > 1 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -79,12 +79,12 @@ impl StackPanel {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn panels_len(&self) -> usize {
|
pub fn panels_len(&self) -> usize {
|
||||||
self.panels.len()
|
self.panels.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the index of the panel.
|
/// Return the index of the panel.
|
||||||
pub(crate) fn index_of_panel(&self, panel: Arc<dyn PanelView>) -> Option<usize> {
|
pub fn index_of_panel(&self, panel: Arc<dyn PanelView>) -> Option<usize> {
|
||||||
self.panels.iter().position(|p| p == &panel)
|
self.panels.iter().position(|p| p == &panel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,11 +253,12 @@ impl StackPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
|
|
||||||
self.remove_self_if_empty(window, cx);
|
self.remove_self_if_empty(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the old panel with the new panel at same index.
|
/// Replace the old panel with the new panel at same index.
|
||||||
pub(super) fn replace_panel(
|
pub fn replace_panel(
|
||||||
&mut self,
|
&mut self,
|
||||||
old_panel: Arc<dyn PanelView>,
|
old_panel: Arc<dyn PanelView>,
|
||||||
new_panel: Entity<StackPanel>,
|
new_panel: Entity<StackPanel>,
|
||||||
@@ -266,16 +267,15 @@ impl StackPanel {
|
|||||||
) {
|
) {
|
||||||
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
||||||
self.panels[ix] = Arc::new(new_panel.clone());
|
self.panels[ix] = Arc::new(new_panel.clone());
|
||||||
let panel_state = ResizablePanelState::default();
|
|
||||||
self.state.update(cx, |state, cx| {
|
self.state.update(cx, |state, cx| {
|
||||||
state.replace_panel(ix, panel_state, cx);
|
state.replace_panel(ix, ResizablePanelState::default(), cx);
|
||||||
});
|
});
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If children is empty, remove self from parent view.
|
/// If children is empty, remove self from parent view.
|
||||||
pub(crate) fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.is_root() {
|
if self.is_root() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -296,11 +296,7 @@ impl StackPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find the first top left in the stack.
|
/// Find the first top left in the stack.
|
||||||
pub(super) fn left_top_tab_panel(
|
pub fn left_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option<Entity<TabPanel>> {
|
||||||
&self,
|
|
||||||
check_parent: bool,
|
|
||||||
cx: &App,
|
|
||||||
) -> Option<Entity<TabPanel>> {
|
|
||||||
if check_parent {
|
if check_parent {
|
||||||
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
|
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
|
||||||
if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) {
|
if let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx) {
|
||||||
@@ -324,11 +320,7 @@ impl StackPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find the first top right in the stack.
|
/// Find the first top right in the stack.
|
||||||
pub(super) fn right_top_tab_panel(
|
pub fn right_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option<Entity<TabPanel>> {
|
||||||
&self,
|
|
||||||
check_parent: bool,
|
|
||||||
cx: &App,
|
|
||||||
) -> Option<Entity<TabPanel>> {
|
|
||||||
if check_parent {
|
if check_parent {
|
||||||
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
|
if let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade()) {
|
||||||
if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) {
|
if let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx) {
|
||||||
@@ -357,7 +349,7 @@ impl StackPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all panels from the stack.
|
/// Remove all panels from the stack.
|
||||||
pub(super) fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
pub fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.panels.clear();
|
self.panels.clear();
|
||||||
self.state.update(cx, |state, cx| {
|
self.state.update(cx, |state, cx| {
|
||||||
state.clear();
|
state.clear();
|
||||||
@@ -366,7 +358,7 @@ impl StackPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Change the axis of the stack panel.
|
/// Change the axis of the stack panel.
|
||||||
pub(super) fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {
|
pub fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.axis = axis;
|
self.axis = axis;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,7 +241,6 @@ impl AppSettings {
|
|||||||
|
|
||||||
// Save event to the local database only
|
// Save event to the local database only
|
||||||
client.database().save_event(&event).await?;
|
client.database().save_event(&event).await?;
|
||||||
log::info!("Settings saved successfully");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -576,17 +576,15 @@ impl NostrRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get contact list for the current user
|
/// Get contact list for the current user
|
||||||
pub fn get_contact_list(&self, cx: &App) -> Task<Result<Vec<PublicKey>, Error>> {
|
pub fn get_contact_list(&self, cx: &App) -> Task<Result<HashSet<PublicKey>, Error>> {
|
||||||
let client = self.client();
|
let client = self.client();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let signer = client.signer().context("Signer not found")?;
|
let signer = client.signer().context("Signer not found")?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
let contacts = client.database().contacts_public_keys(public_key).await?;
|
let contacts = client.database().contacts_public_keys(public_key).await?;
|
||||||
let results = contacts.into_iter().collect();
|
|
||||||
|
|
||||||
Ok(results)
|
Ok(contacts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use theme::ActiveTheme;
|
|||||||
|
|
||||||
use crate::indicator::Indicator;
|
use crate::indicator::Indicator;
|
||||||
use crate::tooltip::Tooltip;
|
use crate::tooltip::Tooltip;
|
||||||
use crate::{h_flex, Disableable, Icon, Selectable, Sizable, Size, StyledExt};
|
use crate::{h_flex, Disableable, Icon, IconName, Selectable, Sizable, Size, StyledExt};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct ButtonCustomVariant {
|
pub struct ButtonCustomVariant {
|
||||||
@@ -20,50 +20,6 @@ pub struct ButtonCustomVariant {
|
|||||||
active: Hsla,
|
active: Hsla,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ButtonVariants: Sized {
|
|
||||||
fn with_variant(self, variant: ButtonVariant) -> Self;
|
|
||||||
|
|
||||||
/// With the primary style for the Button.
|
|
||||||
fn primary(self) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Primary)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With the secondary style for the Button.
|
|
||||||
fn secondary(self) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Secondary)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With the danger style for the Button.
|
|
||||||
fn danger(self) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Danger)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With the warning style for the Button.
|
|
||||||
fn warning(self) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With the ghost style for the Button.
|
|
||||||
fn ghost(self) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Ghost { alt: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With the ghost style for the Button.
|
|
||||||
fn ghost_alt(self) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Ghost { alt: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With the transparent style for the Button.
|
|
||||||
fn transparent(self) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Transparent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With the custom style for the Button.
|
|
||||||
fn custom(self, style: ButtonCustomVariant) -> Self {
|
|
||||||
self.with_variant(ButtonVariant::Custom(style))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ButtonCustomVariant {
|
impl ButtonCustomVariant {
|
||||||
pub fn new(_window: &Window, cx: &App) -> Self {
|
pub fn new(_window: &Window, cx: &App) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -110,6 +66,50 @@ pub enum ButtonVariant {
|
|||||||
Custom(ButtonCustomVariant),
|
Custom(ButtonCustomVariant),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ButtonVariants: Sized {
|
||||||
|
fn with_variant(self, variant: ButtonVariant) -> Self;
|
||||||
|
|
||||||
|
/// With the primary style for the Button.
|
||||||
|
fn primary(self) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the secondary style for the Button.
|
||||||
|
fn secondary(self) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the danger style for the Button.
|
||||||
|
fn danger(self) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Danger)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the warning style for the Button.
|
||||||
|
fn warning(self) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the ghost style for the Button.
|
||||||
|
fn ghost(self) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Ghost { alt: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the ghost style for the Button.
|
||||||
|
fn ghost_alt(self) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Ghost { alt: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the transparent style for the Button.
|
||||||
|
fn transparent(self) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Transparent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// With the custom style for the Button.
|
||||||
|
fn custom(self, style: ButtonCustomVariant) -> Self {
|
||||||
|
self.with_variant(ButtonVariant::Custom(style))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A Button element.
|
/// A Button element.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
@@ -124,17 +124,15 @@ pub struct Button {
|
|||||||
children: Vec<AnyElement>,
|
children: Vec<AnyElement>,
|
||||||
|
|
||||||
variant: ButtonVariant,
|
variant: ButtonVariant,
|
||||||
center: bool,
|
|
||||||
rounded: bool,
|
|
||||||
size: Size,
|
size: Size,
|
||||||
|
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
reverse: bool,
|
|
||||||
bold: bool,
|
|
||||||
cta: bool,
|
|
||||||
|
|
||||||
loading: bool,
|
loading: bool,
|
||||||
loading_icon: Option<Icon>,
|
|
||||||
|
rounded: bool,
|
||||||
|
compact: bool,
|
||||||
|
underline: bool,
|
||||||
|
caret: bool,
|
||||||
|
|
||||||
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||||
on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
|
on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
|
||||||
@@ -161,21 +159,19 @@ impl Button {
|
|||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
icon: None,
|
icon: None,
|
||||||
label: None,
|
label: None,
|
||||||
|
variant: ButtonVariant::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
variant: ButtonVariant::default(),
|
underline: false,
|
||||||
|
compact: false,
|
||||||
|
caret: false,
|
||||||
rounded: false,
|
rounded: false,
|
||||||
size: Size::Medium,
|
size: Size::Medium,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
on_hover: None,
|
on_hover: None,
|
||||||
loading: false,
|
loading: false,
|
||||||
reverse: false,
|
|
||||||
center: true,
|
|
||||||
bold: false,
|
|
||||||
cta: false,
|
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
loading_icon: None,
|
|
||||||
tab_index: 0,
|
tab_index: 0,
|
||||||
tab_stop: true,
|
tab_stop: true,
|
||||||
}
|
}
|
||||||
@@ -211,33 +207,21 @@ impl Button {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set reverse the position between icon and label.
|
/// Set true to make the button compact (no padding).
|
||||||
pub fn reverse(mut self) -> Self {
|
pub fn compact(mut self) -> Self {
|
||||||
self.reverse = true;
|
self.compact = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set bold the button (label will be use the semi-bold font).
|
/// Set true to show the caret indicator.
|
||||||
pub fn bold(mut self) -> Self {
|
pub fn caret(mut self) -> Self {
|
||||||
self.bold = true;
|
self.caret = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disable centering the button's content.
|
/// Set true to show the underline indicator.
|
||||||
pub fn no_center(mut self) -> Self {
|
pub fn underline(mut self) -> Self {
|
||||||
self.center = false;
|
self.underline = true;
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the cta style of the button.
|
|
||||||
pub fn cta(mut self) -> Self {
|
|
||||||
self.cta = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the loading icon of the button.
|
|
||||||
pub fn loading_icon(mut self, icon: impl Into<Icon>) -> Self {
|
|
||||||
self.loading_icon = Some(icon.into());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +330,7 @@ impl RenderOnce for Button {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let focus_handle = window
|
let focus_handle = window
|
||||||
.use_keyed_state(self.id.clone(), cx, |_, cx| cx.focus_handle())
|
.use_keyed_state(self.id.clone(), cx, |_window, cx| cx.focus_handle())
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
@@ -358,10 +342,11 @@ impl RenderOnce for Button {
|
|||||||
.tab_stop(self.tab_stop),
|
.tab_stop(self.tab_stop),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
.relative()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.when(self.center, |this| this.justify_center())
|
.justify_center()
|
||||||
.cursor_default()
|
.cursor_default()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.refine_style(&self.style)
|
.refine_style(&self.style)
|
||||||
@@ -369,39 +354,15 @@ impl RenderOnce for Button {
|
|||||||
false => this.rounded(cx.theme().radius),
|
false => this.rounded(cx.theme().radius),
|
||||||
true => this.rounded_full(),
|
true => this.rounded_full(),
|
||||||
})
|
})
|
||||||
.map(|this| {
|
.when(!self.compact, |this| {
|
||||||
if self.label.is_none() && self.children.is_empty() {
|
if self.label.is_none() && self.children.is_empty() {
|
||||||
// Icon Button
|
// Icon Button
|
||||||
match self.size {
|
match self.size {
|
||||||
Size::Size(px) => this.size(px),
|
Size::Size(px) => this.size(px),
|
||||||
Size::XSmall => {
|
Size::XSmall => this.size_5(),
|
||||||
if self.cta {
|
Size::Small => this.size_6(),
|
||||||
this.w_10().h_5()
|
Size::Medium => this.size_7(),
|
||||||
} else {
|
_ => this.size_9(),
|
||||||
this.size_5()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Size::Small => {
|
|
||||||
if self.cta {
|
|
||||||
this.w_12().h_6()
|
|
||||||
} else {
|
|
||||||
this.size_6()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Size::Medium => {
|
|
||||||
if self.cta {
|
|
||||||
this.w_12().h_7()
|
|
||||||
} else {
|
|
||||||
this.size_7()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if self.cta {
|
|
||||||
this.w_16().h_9()
|
|
||||||
} else {
|
|
||||||
this.size_9()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal Button
|
// Normal Button
|
||||||
@@ -410,8 +371,6 @@ impl RenderOnce for Button {
|
|||||||
Size::XSmall => {
|
Size::XSmall => {
|
||||||
if self.icon.is_some() {
|
if self.icon.is_some() {
|
||||||
this.h_6().pl_2().pr_2p5()
|
this.h_6().pl_2().pr_2p5()
|
||||||
} else if self.cta {
|
|
||||||
this.h_6().px_4()
|
|
||||||
} else {
|
} else {
|
||||||
this.h_6().px_2()
|
this.h_6().px_2()
|
||||||
}
|
}
|
||||||
@@ -419,8 +378,6 @@ impl RenderOnce for Button {
|
|||||||
Size::Small => {
|
Size::Small => {
|
||||||
if self.icon.is_some() {
|
if self.icon.is_some() {
|
||||||
this.h_7().pl_2().pr_2p5()
|
this.h_7().pl_2().pr_2p5()
|
||||||
} else if self.cta {
|
|
||||||
this.h_7().px_4()
|
|
||||||
} else {
|
} else {
|
||||||
this.h_7().px_2()
|
this.h_7().px_2()
|
||||||
}
|
}
|
||||||
@@ -442,13 +399,27 @@ impl RenderOnce for Button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on_mouse_down(gpui::MouseButton::Left, |_, window, _| {
|
.refine_style(&self.style)
|
||||||
|
.on_mouse_down(gpui::MouseButton::Left, move |_, window, cx| {
|
||||||
|
// Stop handle any click event when disabled.
|
||||||
|
// To avoid handle dropdown menu open when button is disabled.
|
||||||
|
if self.disabled {
|
||||||
|
cx.stop_propagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Avoid focus on mouse down.
|
// Avoid focus on mouse down.
|
||||||
window.prevent_default();
|
window.prevent_default();
|
||||||
})
|
})
|
||||||
.when_some(self.on_click.filter(|_| clickable), |this, on_click| {
|
.when_some(self.on_click, |this, on_click| {
|
||||||
this.on_click(move |event, window, cx| {
|
this.on_click(move |event, window, cx| {
|
||||||
(on_click)(event, window, cx);
|
// Stop handle any click event when disabled.
|
||||||
|
// To avoid handle dropdown menu open when button is disabled.
|
||||||
|
if !clickable {
|
||||||
|
cx.stop_propagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
on_click(event, window, cx);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.when_some(self.on_hover.filter(|_| hoverable), |this, on_hover| {
|
.when_some(self.on_hover.filter(|_| hoverable), |this, on_hover| {
|
||||||
@@ -459,7 +430,6 @@ impl RenderOnce for Button {
|
|||||||
.child({
|
.child({
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("label")
|
.id("label")
|
||||||
.when(self.reverse, |this| this.flex_row_reverse())
|
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.map(|this| match self.size {
|
.map(|this| match self.size {
|
||||||
Size::XSmall => this.text_xs().gap_1(),
|
Size::XSmall => this.text_xs().gap_1(),
|
||||||
@@ -471,22 +441,18 @@ impl RenderOnce for Button {
|
|||||||
this.child(icon.with_size(icon_size))
|
this.child(icon.with_size(icon_size))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.when(self.loading, |this| {
|
.when(self.loading, |this| this.child(Indicator::new()))
|
||||||
this.child(
|
|
||||||
Indicator::new()
|
|
||||||
.when_some(self.loading_icon, |this, icon| this.icon(icon)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when_some(self.label, |this, label| {
|
.when_some(self.label, |this, label| {
|
||||||
this.child(
|
this.child(div().flex_none().line_height(relative(1.)).child(label))
|
||||||
div()
|
|
||||||
.flex_none()
|
|
||||||
.line_height(relative(1.))
|
|
||||||
.child(label)
|
|
||||||
.when(self.bold, |this| this.font_semibold()),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.children(self.children)
|
.children(self.children)
|
||||||
|
.when(self.caret, |this| {
|
||||||
|
this.justify_between().gap_0p5().child(
|
||||||
|
Icon::new(IconName::ChevronDown)
|
||||||
|
.small()
|
||||||
|
.text_color(cx.theme().text_muted),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.text_color(normal_style.fg)
|
.text_color(normal_style.fg)
|
||||||
.when(!self.disabled && !self.selected, |this| {
|
.when(!self.disabled && !self.selected, |this| {
|
||||||
@@ -504,6 +470,17 @@ impl RenderOnce for Button {
|
|||||||
let selected_style = style.selected(cx);
|
let selected_style = style.selected(cx);
|
||||||
this.bg(selected_style.bg).text_color(selected_style.fg)
|
this.bg(selected_style.bg).text_color(selected_style.fg)
|
||||||
})
|
})
|
||||||
|
.when(self.selected && self.underline, |this| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.bottom_0()
|
||||||
|
.left_0()
|
||||||
|
.h_px()
|
||||||
|
.w_full()
|
||||||
|
.bg(cx.theme().element_background),
|
||||||
|
)
|
||||||
|
})
|
||||||
.when(self.disabled, |this| {
|
.when(self.disabled, |this| {
|
||||||
let disabled_style = style.disabled(cx);
|
let disabled_style = style.disabled(cx);
|
||||||
this.cursor_not_allowed()
|
this.cursor_not_allowed()
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ impl RenderOnce for Divider {
|
|||||||
.absolute()
|
.absolute()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.map(|this| match self.axis {
|
.map(|this| match self.axis {
|
||||||
Axis::Vertical => this.w(px(2.)).h_full(),
|
Axis::Vertical => this.w(px(1.)).h_full(),
|
||||||
Axis::Horizontal => this.h(px(2.)).w_full(),
|
Axis::Horizontal => this.h(px(1.)).w_full(),
|
||||||
})
|
})
|
||||||
.bg(self.color.unwrap_or(cx.theme().border_variant)),
|
.bg(self.color.unwrap_or(cx.theme().border_variant)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ pub trait PopupMenuExt: Styled + Selectable + InteractiveElement + IntoElement +
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PopupMenuExt for Button {}
|
impl PopupMenuExt for Button {}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
@@ -1074,7 +1075,9 @@ impl PopupMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FluentBuilder for PopupMenu {}
|
impl FluentBuilder for PopupMenu {}
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for PopupMenu {}
|
impl EventEmitter<DismissEvent> for PopupMenu {}
|
||||||
|
|
||||||
impl Focusable for PopupMenu {
|
impl Focusable for PopupMenu {
|
||||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||||
self.focus_handle.clone()
|
self.focus_handle.clone()
|
||||||
|
|||||||
@@ -1,776 +0,0 @@
|
|||||||
use std::ops::Deref;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
|
||||||
use gpui::{
|
|
||||||
actions, anchored, canvas, div, px, rems, Action, AnyElement, App, AppContext, AsKeystroke,
|
|
||||||
Bounds, Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable,
|
|
||||||
InteractiveElement, IntoElement, KeyBinding, Keystroke, ParentElement, Pixels, Render,
|
|
||||||
ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Subscription, WeakEntity,
|
|
||||||
Window,
|
|
||||||
};
|
|
||||||
use theme::ActiveTheme;
|
|
||||||
|
|
||||||
use crate::button::Button;
|
|
||||||
use crate::list::ListItem;
|
|
||||||
use crate::popover::Popover;
|
|
||||||
use crate::scroll::{Scrollbar, ScrollbarState};
|
|
||||||
use crate::{h_flex, v_flex, Icon, IconName, Selectable, Sizable as _, StyledExt};
|
|
||||||
|
|
||||||
actions!(
|
|
||||||
menu,
|
|
||||||
[
|
|
||||||
/// Trigger confirm action when user presses enter button
|
|
||||||
Confirm,
|
|
||||||
/// Trigger dismiss action when user presses escape button
|
|
||||||
Dismiss,
|
|
||||||
/// Select the next item when user presses up button
|
|
||||||
SelectNext,
|
|
||||||
/// Select the previous item when user preses down button
|
|
||||||
SelectPrev
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const ITEM_HEIGHT: Pixels = px(26.);
|
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
|
||||||
let context = Some("PopupMenu");
|
|
||||||
|
|
||||||
cx.bind_keys([
|
|
||||||
KeyBinding::new("enter", Confirm, context),
|
|
||||||
KeyBinding::new("escape", Dismiss, context),
|
|
||||||
KeyBinding::new("up", SelectPrev, context),
|
|
||||||
KeyBinding::new("down", SelectNext, context),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait PopupMenuExt: Styled + Selectable + InteractiveElement + IntoElement + 'static {
|
|
||||||
/// Create a popup menu with the given items, anchored to the TopLeft corner
|
|
||||||
fn popup_menu(
|
|
||||||
self,
|
|
||||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
|
||||||
) -> Popover<PopupMenu> {
|
|
||||||
self.popup_menu_with_anchor(Corner::TopLeft, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a popup menu with the given items, anchored to the given corner
|
|
||||||
fn popup_menu_with_anchor(
|
|
||||||
mut self,
|
|
||||||
anchor: impl Into<Corner>,
|
|
||||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
|
||||||
) -> Popover<PopupMenu> {
|
|
||||||
let style = self.style().clone();
|
|
||||||
let id = self.interactivity().element_id.clone();
|
|
||||||
|
|
||||||
Popover::new(SharedString::from(format!("popup-menu:{id:?}")))
|
|
||||||
.no_style()
|
|
||||||
.trigger(self)
|
|
||||||
.trigger_style(style)
|
|
||||||
.anchor(anchor.into())
|
|
||||||
.content(move |window, cx| {
|
|
||||||
PopupMenu::build(window, cx, |menu, window, cx| f(menu, window, cx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PopupMenuExt for Button {}
|
|
||||||
|
|
||||||
enum PopupMenuItem {
|
|
||||||
Title(SharedString),
|
|
||||||
Separator,
|
|
||||||
Item {
|
|
||||||
icon: Option<Icon>,
|
|
||||||
label: SharedString,
|
|
||||||
action: Option<Box<dyn Action>>,
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
handler: Rc<dyn Fn(&mut Window, &mut App)>,
|
|
||||||
},
|
|
||||||
ElementItem {
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
handler: Rc<dyn Fn(&mut Window, &mut App)>,
|
|
||||||
},
|
|
||||||
Submenu {
|
|
||||||
icon: Option<Icon>,
|
|
||||||
label: SharedString,
|
|
||||||
menu: Entity<PopupMenu>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PopupMenuItem {
|
|
||||||
fn is_clickable(&self) -> bool {
|
|
||||||
!matches!(self, PopupMenuItem::Separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_separator(&self) -> bool {
|
|
||||||
matches!(self, PopupMenuItem::Separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_icon(&self) -> bool {
|
|
||||||
matches!(self, PopupMenuItem::Item { icon: Some(_), .. })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PopupMenu {
|
|
||||||
/// The parent menu of this menu, if this is a submenu
|
|
||||||
parent_menu: Option<WeakEntity<Self>>,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
menu_items: Vec<PopupMenuItem>,
|
|
||||||
has_icon: bool,
|
|
||||||
selected_index: Option<usize>,
|
|
||||||
min_width: Pixels,
|
|
||||||
max_width: Pixels,
|
|
||||||
hovered_menu_ix: Option<usize>,
|
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
|
|
||||||
scrollable: bool,
|
|
||||||
scroll_handle: ScrollHandle,
|
|
||||||
scroll_state: ScrollbarState,
|
|
||||||
|
|
||||||
action_focus_handle: Option<FocusHandle>,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
subscriptions: Vec<Subscription>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PopupMenu {
|
|
||||||
pub fn build(
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
f: impl FnOnce(Self, &mut Window, &mut Context<PopupMenu>) -> Self,
|
|
||||||
) -> Entity<Self> {
|
|
||||||
cx.new(|cx| {
|
|
||||||
let focus_handle = cx.focus_handle();
|
|
||||||
let subscriptions =
|
|
||||||
vec![
|
|
||||||
cx.on_blur(&focus_handle, window, |this: &mut PopupMenu, window, cx| {
|
|
||||||
this.dismiss(&Dismiss, window, cx)
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
let menu = Self {
|
|
||||||
focus_handle,
|
|
||||||
action_focus_handle: None,
|
|
||||||
parent_menu: None,
|
|
||||||
menu_items: Vec::new(),
|
|
||||||
selected_index: None,
|
|
||||||
min_width: px(120.),
|
|
||||||
max_width: px(500.),
|
|
||||||
has_icon: false,
|
|
||||||
hovered_menu_ix: None,
|
|
||||||
bounds: Bounds::default(),
|
|
||||||
scrollable: false,
|
|
||||||
scroll_handle: ScrollHandle::default(),
|
|
||||||
scroll_state: ScrollbarState::default(),
|
|
||||||
subscriptions,
|
|
||||||
};
|
|
||||||
|
|
||||||
f(menu, window, cx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bind the focus handle of the menu, when clicked, it will focus back to this handle and then dispatch the action
|
|
||||||
pub fn track_focus(mut self, focus_handle: &FocusHandle) -> Self {
|
|
||||||
self.action_focus_handle = Some(focus_handle.clone());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set min width of the popup menu, default is 120px
|
|
||||||
pub fn min_w(mut self, width: impl Into<Pixels>) -> Self {
|
|
||||||
self.min_width = width.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set max width of the popup menu, default is 500px
|
|
||||||
pub fn max_w(mut self, width: impl Into<Pixels>) -> Self {
|
|
||||||
self.max_width = width.into();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the menu to be scrollable to show vertical scrollbar.
|
|
||||||
///
|
|
||||||
/// NOTE: If this is true, the sub-menus will cannot be support.
|
|
||||||
pub fn scrollable(mut self) -> Self {
|
|
||||||
self.scrollable = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add Menu Item
|
|
||||||
pub fn menu(mut self, label: impl Into<SharedString>, action: Box<dyn Action>) -> Self {
|
|
||||||
self.add_menu_item(label, None, action);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add Menu to open link
|
|
||||||
pub fn link(mut self, label: impl Into<SharedString>, href: impl Into<String>) -> Self {
|
|
||||||
let href = href.into();
|
|
||||||
self.menu_items.push(PopupMenuItem::Item {
|
|
||||||
icon: None,
|
|
||||||
label: label.into(),
|
|
||||||
action: None,
|
|
||||||
handler: Rc::new(move |_window, cx| cx.open_url(&href)),
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add Menu to open link
|
|
||||||
pub fn link_with_icon(
|
|
||||||
mut self,
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
icon: impl Into<Icon>,
|
|
||||||
href: impl Into<String>,
|
|
||||||
) -> Self {
|
|
||||||
let href = href.into();
|
|
||||||
self.menu_items.push(PopupMenuItem::Item {
|
|
||||||
icon: Some(icon.into()),
|
|
||||||
label: label.into(),
|
|
||||||
action: None,
|
|
||||||
handler: Rc::new(move |_window, cx| cx.open_url(&href)),
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add Menu Item with Icon
|
|
||||||
pub fn menu_with_icon(
|
|
||||||
mut self,
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
icon: impl Into<Icon>,
|
|
||||||
action: Box<dyn Action>,
|
|
||||||
) -> Self {
|
|
||||||
self.add_menu_item(label, Some(icon.into()), action);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add Menu Item with check icon
|
|
||||||
pub fn menu_with_check(
|
|
||||||
mut self,
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
checked: bool,
|
|
||||||
action: Box<dyn Action>,
|
|
||||||
) -> Self {
|
|
||||||
if checked {
|
|
||||||
self.add_menu_item(label, Some(IconName::Check.into()), action);
|
|
||||||
} else {
|
|
||||||
self.add_menu_item(label, None, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add Menu Item with custom element render.
|
|
||||||
pub fn menu_with_element<F, E>(mut self, builder: F, action: Box<dyn Action>) -> Self
|
|
||||||
where
|
|
||||||
F: Fn(&mut Window, &mut App) -> E + 'static,
|
|
||||||
E: IntoElement,
|
|
||||||
{
|
|
||||||
self.menu_items.push(PopupMenuItem::ElementItem {
|
|
||||||
render: Box::new(move |window, cx| builder(window, cx).into_any_element()),
|
|
||||||
handler: self.wrap_handler(action),
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
fn wrap_handler(&self, action: Box<dyn Action>) -> Rc<dyn Fn(&mut Window, &mut App)> {
|
|
||||||
let action_focus_handle = self.action_focus_handle.clone();
|
|
||||||
|
|
||||||
Rc::new(move |window, cx| {
|
|
||||||
window.activate_window();
|
|
||||||
|
|
||||||
// Focus back to the user expected focus handle
|
|
||||||
// Then the actions listened on that focus handle can be received
|
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// TabPanel
|
|
||||||
// |- PopupMenu
|
|
||||||
// |- PanelContent (actions are listened here)
|
|
||||||
//
|
|
||||||
// The `PopupMenu` and `PanelContent` are at the same level in the TabPanel
|
|
||||||
// If the actions are listened on the `PanelContent`,
|
|
||||||
// it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`.
|
|
||||||
if let Some(handle) = action_focus_handle.as_ref() {
|
|
||||||
window.focus(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.dispatch_action(action.boxed_clone(), cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_menu_item(
|
|
||||||
&mut self,
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
icon: Option<Icon>,
|
|
||||||
action: Box<dyn Action>,
|
|
||||||
) -> &mut Self {
|
|
||||||
if icon.is_some() {
|
|
||||||
self.has_icon = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.menu_items.push(PopupMenuItem::Item {
|
|
||||||
icon,
|
|
||||||
label: label.into(),
|
|
||||||
action: Some(action.boxed_clone()),
|
|
||||||
handler: self.wrap_handler(action),
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a title menu item
|
|
||||||
pub fn title(mut self, label: impl Into<SharedString>) -> Self {
|
|
||||||
if self.menu_items.is_empty() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(PopupMenuItem::Title(_)) = self.menu_items.last() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.menu_items.push(PopupMenuItem::Title(label.into()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a separator Menu Item
|
|
||||||
pub fn separator(mut self) -> Self {
|
|
||||||
if self.menu_items.is_empty() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(PopupMenuItem::Separator) = self.menu_items.last() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.menu_items.push(PopupMenuItem::Separator);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submenu(
|
|
||||||
self,
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.submenu_with_icon(None, label, window, cx, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a Submenu item with icon
|
|
||||||
pub fn submenu_with_icon(
|
|
||||||
mut self,
|
|
||||||
icon: Option<Icon>,
|
|
||||||
label: impl Into<SharedString>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
|
||||||
) -> Self {
|
|
||||||
let submenu = PopupMenu::build(window, cx, f);
|
|
||||||
let parent_menu = cx.entity().downgrade();
|
|
||||||
|
|
||||||
submenu.update(cx, |view, _| {
|
|
||||||
view.parent_menu = Some(parent_menu);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.menu_items.push(PopupMenuItem::Submenu {
|
|
||||||
icon,
|
|
||||||
label: label.into(),
|
|
||||||
menu: submenu,
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn active_submenu(&self) -> Option<Entity<PopupMenu>> {
|
|
||||||
if let Some(ix) = self.hovered_menu_ix {
|
|
||||||
if let Some(item) = self.menu_items.get(ix) {
|
|
||||||
return match item {
|
|
||||||
PopupMenuItem::Submenu { menu, .. } => Some(menu.clone()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.menu_items.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clickable_menu_items(&self) -> impl Iterator<Item = (usize, &PopupMenuItem)> {
|
|
||||||
self.menu_items
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, item)| item.is_clickable())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_click(&mut self, ix: usize, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
cx.stop_propagation();
|
|
||||||
window.prevent_default();
|
|
||||||
self.selected_index = Some(ix);
|
|
||||||
self.confirm(&Confirm, window, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
if let Some(index) = self.selected_index {
|
|
||||||
let item = self.menu_items.get(index);
|
|
||||||
match item {
|
|
||||||
Some(PopupMenuItem::Item { handler, .. }) => {
|
|
||||||
handler(window, cx);
|
|
||||||
self.dismiss(&Dismiss, window, cx)
|
|
||||||
}
|
|
||||||
Some(PopupMenuItem::ElementItem { handler, .. }) => {
|
|
||||||
handler(window, cx);
|
|
||||||
self.dismiss(&Dismiss, window, cx)
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_next(&mut self, _: &SelectNext, _window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let count = self.clickable_menu_items().count();
|
|
||||||
if count > 0 {
|
|
||||||
let last_ix = count.saturating_sub(1);
|
|
||||||
let ix = self
|
|
||||||
.selected_index
|
|
||||||
.map(|index| if index == last_ix { 0 } else { index + 1 })
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
self.selected_index = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_prev(&mut self, _: &SelectPrev, _window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
let count = self.clickable_menu_items().count();
|
|
||||||
if count > 0 {
|
|
||||||
let last_ix = count.saturating_sub(1);
|
|
||||||
|
|
||||||
let ix = self
|
|
||||||
.selected_index
|
|
||||||
.map(|index| {
|
|
||||||
if index == last_ix {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
index.saturating_sub(1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(last_ix);
|
|
||||||
self.selected_index = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: fix this
|
|
||||||
#[allow(clippy::only_used_in_recursion)]
|
|
||||||
fn dismiss(&mut self, _: &Dismiss, window: &mut Window, cx: &mut Context<Self>) {
|
|
||||||
if self.active_submenu().is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.emit(DismissEvent);
|
|
||||||
|
|
||||||
// Dismiss parent menu, when this menu is dismissed
|
|
||||||
if let Some(parent_menu) = self.parent_menu.clone().and_then(|menu| menu.upgrade()) {
|
|
||||||
parent_menu.update(cx, |view, cx| {
|
|
||||||
view.hovered_menu_ix = None;
|
|
||||||
view.dismiss(&Dismiss, window, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_keybinding(
|
|
||||||
action: Option<Box<dyn Action>>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> Option<impl IntoElement> {
|
|
||||||
if let Some(action) = action {
|
|
||||||
if let Some(keybinding) = window.bindings_for_action(action.deref()).first() {
|
|
||||||
let el = div().text_color(cx.theme().text_muted).children(
|
|
||||||
keybinding
|
|
||||||
.keystrokes()
|
|
||||||
.iter()
|
|
||||||
.map(|key| key_shortcut(key.as_keystroke().clone())),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Some(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_icon(
|
|
||||||
has_icon: bool,
|
|
||||||
icon: Option<Icon>,
|
|
||||||
_window: &Window,
|
|
||||||
_cx: &Context<Self>,
|
|
||||||
) -> Option<impl IntoElement> {
|
|
||||||
let icon_placeholder = if has_icon { Some(Icon::empty()) } else { None };
|
|
||||||
|
|
||||||
if !has_icon {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let icon = h_flex()
|
|
||||||
.w_3p5()
|
|
||||||
.h_3p5()
|
|
||||||
.items_center()
|
|
||||||
.justify_center()
|
|
||||||
.text_sm()
|
|
||||||
.map(|this| {
|
|
||||||
if let Some(icon) = icon {
|
|
||||||
this.child(icon.clone().small())
|
|
||||||
} else {
|
|
||||||
this.children(icon_placeholder.clone())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FluentBuilder for PopupMenu {}
|
|
||||||
|
|
||||||
impl EventEmitter<DismissEvent> for PopupMenu {}
|
|
||||||
|
|
||||||
impl Focusable for PopupMenu {
|
|
||||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
|
||||||
self.focus_handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for PopupMenu {
|
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
|
||||||
let view = cx.entity().clone();
|
|
||||||
let has_icon = self.menu_items.iter().any(|item| item.has_icon());
|
|
||||||
let items_count = self.menu_items.len();
|
|
||||||
let max_width = self.max_width;
|
|
||||||
let bounds = self.bounds;
|
|
||||||
|
|
||||||
let window_haft_height = window.window_bounds().get_bounds().size.height * 0.5;
|
|
||||||
let max_height = window_haft_height.min(px(450.));
|
|
||||||
|
|
||||||
v_flex()
|
|
||||||
.id("popup-menu")
|
|
||||||
.key_context("PopupMenu")
|
|
||||||
.track_focus(&self.focus_handle)
|
|
||||||
.on_action(cx.listener(Self::select_next))
|
|
||||||
.on_action(cx.listener(Self::select_prev))
|
|
||||||
.on_action(cx.listener(Self::confirm))
|
|
||||||
.on_action(cx.listener(Self::dismiss))
|
|
||||||
.on_mouse_down_out(cx.listener(|this, _, window, cx| this.dismiss(&Dismiss, window, cx)))
|
|
||||||
.popover_style(cx)
|
|
||||||
.relative()
|
|
||||||
.p_1()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.id("popup-menu-items")
|
|
||||||
.when(self.scrollable, |this| {
|
|
||||||
this.max_h(max_height)
|
|
||||||
.overflow_y_scroll()
|
|
||||||
.track_scroll(&self.scroll_handle)
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_y_0p5()
|
|
||||||
.min_w(self.min_width)
|
|
||||||
.max_w(self.max_width)
|
|
||||||
.min_w(rems(8.))
|
|
||||||
.child({
|
|
||||||
canvas(
|
|
||||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
|
||||||
|_, _, _, _| {},
|
|
||||||
)
|
|
||||||
.absolute()
|
|
||||||
.size_full()
|
|
||||||
})
|
|
||||||
.children(
|
|
||||||
self.menu_items
|
|
||||||
.iter_mut()
|
|
||||||
.enumerate()
|
|
||||||
// Skip last separator
|
|
||||||
.filter(|(ix, item)| !(*ix == items_count - 1 && item.is_separator()))
|
|
||||||
.map(|(ix, item)| {
|
|
||||||
let this = ListItem::new(("menu-item", ix))
|
|
||||||
.relative()
|
|
||||||
.items_center()
|
|
||||||
.py_0()
|
|
||||||
.px_2()
|
|
||||||
.rounded_md()
|
|
||||||
.text_sm()
|
|
||||||
.on_mouse_enter(cx.listener(move |this, _, _window, cx| {
|
|
||||||
this.hovered_menu_ix = Some(ix);
|
|
||||||
cx.notify();
|
|
||||||
}));
|
|
||||||
|
|
||||||
match item {
|
|
||||||
PopupMenuItem::Title(label) => {
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.text_xs()
|
|
||||||
.font_semibold()
|
|
||||||
.text_color(cx.theme().text_muted)
|
|
||||||
.child(label.clone())
|
|
||||||
)
|
|
||||||
},
|
|
||||||
PopupMenuItem::Separator => this.h_auto().p_0().disabled(true).child(
|
|
||||||
div()
|
|
||||||
.rounded_none()
|
|
||||||
.h(px(1.))
|
|
||||||
.mx_neg_1()
|
|
||||||
.my_0p5()
|
|
||||||
.bg(cx.theme().border_disabled),
|
|
||||||
),
|
|
||||||
PopupMenuItem::ElementItem { render, .. } => this
|
|
||||||
.on_click(
|
|
||||||
cx.listener(move |this, _, window, cx| {
|
|
||||||
this.on_click(ix, window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.min_h(ITEM_HEIGHT)
|
|
||||||
.items_center()
|
|
||||||
.gap_x_1()
|
|
||||||
.children(Self::render_icon(has_icon, None, window, cx))
|
|
||||||
.child((render)(window, cx)),
|
|
||||||
),
|
|
||||||
PopupMenuItem::Item {
|
|
||||||
icon, label, action, ..
|
|
||||||
} => {
|
|
||||||
let action = action.as_ref().map(|action| action.boxed_clone());
|
|
||||||
let key = Self::render_keybinding(action, window, cx);
|
|
||||||
|
|
||||||
this.on_click(
|
|
||||||
cx.listener(move |this, _, window, cx| {
|
|
||||||
this.on_click(ix, window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.h(ITEM_HEIGHT)
|
|
||||||
.items_center()
|
|
||||||
.gap_x_1p5()
|
|
||||||
.children(Self::render_icon(has_icon, icon.clone(), window, cx))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.flex_1()
|
|
||||||
.gap_2()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.child(label.clone())
|
|
||||||
.children(key),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
PopupMenuItem::Submenu { icon, label, menu } => this
|
|
||||||
.when(self.hovered_menu_ix == Some(ix), |this| this.selected(true))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.items_start()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.size_full()
|
|
||||||
.items_center()
|
|
||||||
.gap_x_1p5()
|
|
||||||
.children(Self::render_icon(
|
|
||||||
has_icon,
|
|
||||||
icon.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
))
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.flex_1()
|
|
||||||
.gap_2()
|
|
||||||
.items_center()
|
|
||||||
.justify_between()
|
|
||||||
.child(label.clone())
|
|
||||||
.child(IconName::CaretRight),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.when_some(self.hovered_menu_ix, |this, hovered_ix| {
|
|
||||||
let (anchor, left) = if window.bounds().size.width
|
|
||||||
- bounds.origin.x
|
|
||||||
< max_width
|
|
||||||
{
|
|
||||||
(Corner::TopRight, -px(15.))
|
|
||||||
} else {
|
|
||||||
(Corner::TopLeft, bounds.size.width - px(10.))
|
|
||||||
};
|
|
||||||
|
|
||||||
let top = if bounds.origin.y + bounds.size.height
|
|
||||||
> window.bounds().size.height
|
|
||||||
{
|
|
||||||
px(32.)
|
|
||||||
} else {
|
|
||||||
-px(10.)
|
|
||||||
};
|
|
||||||
|
|
||||||
if hovered_ix == ix {
|
|
||||||
this.child(
|
|
||||||
anchored()
|
|
||||||
.anchor(anchor)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.occlude()
|
|
||||||
.top(top)
|
|
||||||
.left(left)
|
|
||||||
.child(menu.clone()),
|
|
||||||
)
|
|
||||||
.snap_to_window_with_margin(Edges::all(px(8.))),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.when(self.scrollable, |this| {
|
|
||||||
// TODO: When the menu is limited by `overflow_y_scroll`, the sub-menu will cannot be displayed.
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.top_0()
|
|
||||||
.left_0()
|
|
||||||
.right_0p5()
|
|
||||||
.bottom_0()
|
|
||||||
.child(Scrollbar::vertical(&self.scroll_state, &self.scroll_handle)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the Platform specific keybinding string by KeyStroke
|
|
||||||
pub fn key_shortcut(key: Keystroke) -> String {
|
|
||||||
if cfg!(target_os = "macos") {
|
|
||||||
return format!("{key}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut parts = vec![];
|
|
||||||
if key.modifiers.control {
|
|
||||||
parts.push("Ctrl");
|
|
||||||
}
|
|
||||||
if key.modifiers.alt {
|
|
||||||
parts.push("Alt");
|
|
||||||
}
|
|
||||||
if key.modifiers.platform {
|
|
||||||
parts.push("Win");
|
|
||||||
}
|
|
||||||
if key.modifiers.shift {
|
|
||||||
parts.push("Shift");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capitalize the first letter
|
|
||||||
let key = if let Some(first_c) = key.key.chars().next() {
|
|
||||||
format!("{}{}", first_c.to_uppercase(), &key.key[1..])
|
|
||||||
} else {
|
|
||||||
key.key.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
parts.push(&key);
|
|
||||||
parts.join("+")
|
|
||||||
}
|
|
||||||
@@ -183,39 +183,43 @@ impl<T: Styled> StyleSized<T> for T {
|
|||||||
|
|
||||||
fn input_pl(self, size: Size) -> Self {
|
fn input_pl(self, size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::Large => self.pl_5(),
|
Size::XSmall => self.pl_1(),
|
||||||
Size::Medium => self.pl_3(),
|
Size::Medium => self.pl_3(),
|
||||||
|
Size::Large => self.pl_5(),
|
||||||
_ => self.pl_2(),
|
_ => self.pl_2(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_pr(self, size: Size) -> Self {
|
fn input_pr(self, size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::Large => self.pr_5(),
|
Size::XSmall => self.pr_1(),
|
||||||
Size::Medium => self.pr_3(),
|
Size::Medium => self.pr_3(),
|
||||||
|
Size::Large => self.pr_5(),
|
||||||
_ => self.pr_2(),
|
_ => self.pr_2(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_px(self, size: Size) -> Self {
|
fn input_px(self, size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::Large => self.px_5(),
|
Size::XSmall => self.px_1(),
|
||||||
Size::Medium => self.px_3(),
|
Size::Medium => self.px_3(),
|
||||||
|
Size::Large => self.px_5(),
|
||||||
_ => self.px_2(),
|
_ => self.px_2(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_py(self, size: Size) -> Self {
|
fn input_py(self, size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::Large => self.py_5(),
|
Size::XSmall => self.py_0p5(),
|
||||||
Size::Medium => self.py_2(),
|
Size::Medium => self.py_2(),
|
||||||
|
Size::Large => self.py_5(),
|
||||||
_ => self.py_1(),
|
_ => self.py_1(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_h(self, size: Size) -> Self {
|
fn input_h(self, size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::XSmall => self.h_7(),
|
Size::XSmall => self.h_6(),
|
||||||
Size::Small => self.h_8(),
|
Size::Small => self.h_8(),
|
||||||
Size::Medium => self.h_9(),
|
Size::Medium => self.h_9(),
|
||||||
Size::Large => self.h_12(),
|
Size::Large => self.h_12(),
|
||||||
|
|||||||
Reference in New Issue
Block a user