Compare commits
82 Commits
dynamic-ru
...
move-to-en
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce45e1cb4c | ||
|
|
78aaaa8b1e | ||
|
|
2045c83099 | ||
|
|
f57f6344b4 | ||
|
|
022fada0f2 | ||
|
|
ba45a04045 | ||
|
|
f52d9f8c24 | ||
|
|
8847f73af5 | ||
|
|
3a7b4de0f5 | ||
|
|
82ec8405d6 | ||
|
|
7b7a046b94 | ||
|
|
1ba395a33a | ||
|
|
a165e997ba | ||
|
|
8444b11e76 | ||
|
|
609370f9d6 | ||
|
|
2108c764ad | ||
|
|
bdedeab7af | ||
|
|
dab886f479 | ||
|
|
cbcd011a36 | ||
|
|
542fb5c89a | ||
|
|
b3b94e64ba | ||
|
|
db9cc42245 | ||
|
|
0fde56909c | ||
|
|
faa6f979be | ||
|
|
dc7befb884 | ||
|
|
519655297a | ||
|
|
47bcb305af | ||
|
|
953bc5eee2 | ||
|
|
d75ef8e62d | ||
|
|
c94852b843 | ||
|
|
81886a9baf | ||
|
|
225dd0f9a0 | ||
|
|
778b6fb27b | ||
|
|
b7429bf29d | ||
|
|
9bd5ebb74b | ||
|
|
ac30ded80e | ||
|
|
7f954cbbb8 | ||
|
|
c07237df33 | ||
|
|
387c161d8c | ||
|
|
b76e0d997e | ||
|
|
198dfe0097 | ||
|
|
16eb17e2f8 | ||
|
|
014e6f66bb | ||
|
|
9e4b3ce94c | ||
|
|
4a4ca2c3b8 | ||
|
|
495de89747 | ||
|
|
6a3ac94eea | ||
|
|
f5cd8247d1 | ||
|
|
821960bf14 | ||
|
|
6e04c1f924 | ||
|
|
57f5f128f3 | ||
|
|
7efa8d079d | ||
|
|
d0ffd51bb1 | ||
|
|
7aba9eb4b7 | ||
|
|
517ea734ee | ||
|
|
a52177fd39 | ||
|
|
893e55ff96 | ||
|
|
0ab1e6f451 | ||
|
|
f8959834c4 | ||
|
|
2e516261fe | ||
|
|
ca092fb694 | ||
|
|
96d9df073e | ||
|
|
9f7e625d37 | ||
|
|
17d736b23d | ||
|
|
6006b171f7 | ||
|
|
5790d9ba27 | ||
|
|
d5766dc69f | ||
|
|
3e5c11dc41 | ||
|
|
ed1a256f99 | ||
|
|
28e795f2fd | ||
|
|
98374a70d3 | ||
|
|
33abbcb535 | ||
|
|
7481c0d556 | ||
|
|
a1d5249b8e | ||
|
|
21bde9b653 | ||
|
|
5183dbb5be | ||
|
|
7c6e6971da | ||
|
|
81b5c74b0e | ||
|
|
47e48e20d3 | ||
|
|
4584c5e2b3 | ||
|
|
e07c53c27f | ||
|
|
e917d1d251 |
2
.github/cherry-pick-bot.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
enabled: true
|
||||
preservePullRequestTitle: true
|
||||
4
.github/workflows/ci.yml
vendored
@@ -99,7 +99,7 @@ jobs:
|
||||
- name: Build other binaries and features
|
||||
run: cargo build --workspace --bins --all-features; cargo check -p gpui --features "macos-blade"
|
||||
|
||||
# todo!(linux): Actually run the tests
|
||||
# todo(linux): Actually run the tests
|
||||
linux_tests:
|
||||
name: (Linux) Run Clippy and tests
|
||||
runs-on: ubuntu-latest
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
- name: Build Zed
|
||||
run: cargo build -p zed
|
||||
|
||||
# todo!(windows): Actually run the tests
|
||||
# todo(windows): Actually run the tests
|
||||
windows_tests:
|
||||
name: (Windows) Run Clippy and tests
|
||||
runs-on: windows-latest
|
||||
|
||||
149
Cargo.lock
generated
@@ -404,9 +404,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.6"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c"
|
||||
checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"futures-core",
|
||||
@@ -681,9 +681,9 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.77"
|
||||
version = "0.1.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1631,6 +1631,32 @@ dependencies = [
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"log",
|
||||
"polling 3.3.2",
|
||||
"rustix 0.38.30",
|
||||
"slab",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"rustix 0.38.30",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.1.2"
|
||||
@@ -2421,18 +2447,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-bforest"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29a6391a9172a93f413370fa561c6bca786e06c89cf85f23f02f6345b1c8ee34"
|
||||
checksum = "9515fcc42b6cb5137f76b84c1a6f819782d0cf12473d145d3bc5cd67eedc8bc2"
|
||||
dependencies = [
|
||||
"cranelift-entity",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "409c6cbb326604a53ec47eb6341fc85128f24c81012a014b4c728ed24f6e9350"
|
||||
checksum = "1ad827c6071bfe6d22de1bc331296a29f9ddc506ff926d8415b435ec6a6efce0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"cranelift-bforest",
|
||||
@@ -2451,33 +2477,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-meta"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fff55e100130995b9ad9ac6b03a24ed5da3c1a1261dcdeb8a7a0292656994fb3"
|
||||
checksum = "10e6b36237a9ca2ce2fb4cc7741d418a080afa1327402138412ef85d5367bef1"
|
||||
dependencies = [
|
||||
"cranelift-codegen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-codegen-shared"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1446e2eb395fc7b3019a36dccb7eccea923f6caf581b903c8e7e751b6d214a7"
|
||||
checksum = "c36bf4bfb86898a94ccfa773a1f86e8a5346b1983ff72059bdd2db4600325251"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-control"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24076ecf69cbf8b9e1e532ae8e7ac01d850a1c2e127058a26eb3245f9d5b89d1"
|
||||
checksum = "7cbf36560e7a6bd1409ca91e7b43b2cc7ed8429f343d7605eadf9046e8fac0d0"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-entity"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f40df95180ad317c60459bb90dd87803d35e538f4c54376d8b26c851f6f0a1b"
|
||||
checksum = "a71e11061a75b1184c09bea97c026a88f08b59ade96a7bb1f259d4ea0df2e942"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -2485,9 +2511,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-frontend"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c3974cc665b699b626742775dae1c1cdea5170f5028ab1f3eb61a7a9a6e2979"
|
||||
checksum = "af5d4da63143ee3485c7bcedde0a818727d737d1083484a0ceedb8950c89e495"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"log",
|
||||
@@ -2497,15 +2523,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-isle"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99543f92b9c361f3c54a29e945adb5b9ef1318feaa5944453cabbfcb3c495919"
|
||||
checksum = "457a9832b089e26f5eea70dcf49bed8ec6edafed630ce7c83161f24d46ab8085"
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-native"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c0d84dc7d9b3f73ad565eacc4ab36525c407ef5150893b4b94d5f5f904eb48a"
|
||||
checksum = "9b490d579df1ce365e1ea359e24ed86d82289fa785153327c2f6a69a59a731e4"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"libc",
|
||||
@@ -2514,9 +2540,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cranelift-wasm"
|
||||
version = "0.105.1"
|
||||
version = "0.105.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53781039219944d59c6d3ec57e6cae31a1a33db71573a945d84ba6d875d0a743"
|
||||
checksum = "8cd747ed7f9a461dda9c388415392f6bb95d1a6ef3b7694d17e0817eb74b7798"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
@@ -4002,6 +4028,8 @@ dependencies = [
|
||||
"blade-macros",
|
||||
"block",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cbindgen",
|
||||
"cocoa",
|
||||
"collections",
|
||||
@@ -7571,9 +7599,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.2.0"
|
||||
version = "8.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
|
||||
checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
@@ -7582,9 +7610,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.2.0"
|
||||
version = "8.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
|
||||
checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7595,9 +7623,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.2.0"
|
||||
version = "8.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
|
||||
checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2 0.10.7",
|
||||
@@ -8318,9 +8346,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
@@ -9101,7 +9129,6 @@ dependencies = [
|
||||
"collections",
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"project_core",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json_lenient",
|
||||
@@ -9117,12 +9144,11 @@ dependencies = [
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"language",
|
||||
"lsp",
|
||||
"menu",
|
||||
"picker",
|
||||
"project",
|
||||
"project_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"task",
|
||||
"ui",
|
||||
"util",
|
||||
@@ -9896,7 +9922,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tree-sitter-gitcommit"
|
||||
version = "0.3.3"
|
||||
source = "git+https://github.com/gbprod/tree-sitter-gitcommit#7c01af8d227b5344f62aade2ff00f19bd0c458ca"
|
||||
source = "git+https://github.com/gbprod/tree-sitter-gitcommit#e8d9eda4e5ea0b08aa39d48dab0f6553058fbe0f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
@@ -10492,6 +10518,7 @@ dependencies = [
|
||||
"take-until",
|
||||
"tempfile",
|
||||
"tendril",
|
||||
"unicase",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -10771,9 +10798,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06f80b13fdeba0ea5267813d0f06af822309f7125fc8db6094bcd485f0a4ae7"
|
||||
checksum = "4c843b8bc4dd4f3a76173ba93405c71111d570af0d90ea5f6299c705d0c2add2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -10801,18 +10828,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-asm-macros"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d7395b475c6f858c7edfce375f00d8282a32fbf5d1ebc93eddfac5c2458a52"
|
||||
checksum = "86b9d329c718b3a18412a6a017c912b539baa8fe1210d21b651f6b4dbafed743"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-c-api-impl"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c09ac0c18464f8ef0b554c12defc94e3fc082b62309a3da229de60d47cf75a"
|
||||
checksum = "cc93587c24d8e3cb28912eb7abf95f7e350380656faccc46cff04c0821ec58c2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
@@ -10824,9 +10851,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-c-api-macros"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864c4a337294fe690f02b39f2b3f45414447d9321d0ed24d3dc7696bf291e789"
|
||||
checksum = "2e571a71eba52dfe81ef653a3a336888141f00fc2208a9962722e036fe2a34be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -10834,9 +10861,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-cranelift"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "974d9455611e26c97d31705e19545de58fa8867416592bd93b7a54a7fc37cedb"
|
||||
checksum = "31ca62f519225492bd555d0ec85a2dacb0c10315db3418c8b9aeb3824bf54a24"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -10859,9 +10886,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-cranelift-shared"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40667ba458634db703aea3bd960e80bc9352c21d5e765b69f43e3b0c964eb611"
|
||||
checksum = "fd5f2071f42e61490bf7cb95b9acdbe6a29dd577a398019304a960585f28b844"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cranelift-codegen",
|
||||
@@ -10875,9 +10902,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-environ"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8da991421528c2767053cb0cfa70b5d28279100dbcf70ed7f74b51abe1656ef"
|
||||
checksum = "82bf1a47f384610da19f58b0fd392ca6a3b720974315c08afb0392c0f3951fed"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -10896,9 +10923,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-jit-icache-coherence"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3346431a41fbb0c5af0081c2322361b00289f2902e54ee7b115e9b2ad32b156b"
|
||||
checksum = "33f4121cb29dda08139b2824a734dd095d83ce843f2d613a84eb580b9cfc17ac"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
@@ -10907,9 +10934,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-runtime"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a489353aa297b46a66cde8da48cab8e1e967e7f4b0ae3d9889a0550bf274810b"
|
||||
checksum = "4e517f2b996bb3b0e34a82a2bce194f850d9bcfc25c08328ef5fb71b071066b8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
@@ -10934,9 +10961,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-types"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12c56e31fd7fa707fbd7720b2b29ac42ccfb092fe9d85c98f1d3988f9a1d4558"
|
||||
checksum = "54a327d7a0ef57bd52a507d28b4561a74126c7a8535a2fc6f2025716bc6a52e8"
|
||||
dependencies = [
|
||||
"cranelift-entity",
|
||||
"serde",
|
||||
@@ -10947,9 +10974,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-versioned-export-macros"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b0300976c36a9427d184e3ecf7c121c2cb3f030844faf9fcb767821e9d4c382"
|
||||
checksum = "8ef32eea9fc7035a55159a679d1e89b43ece5ae45d24eed4808e6a92c99a0da4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -10958,9 +10985,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmtime-wmemcheck"
|
||||
version = "18.0.1"
|
||||
version = "18.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdf5b8da6ebf7549dad0cd32ca4a3a0461449ef4feec9d0d8450d8da9f51f9b"
|
||||
checksum = "7f4cbfb052d66f03603a9b77f18171ea245c7805714caad370a549a6344bf86b"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
@@ -11607,7 +11634,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.125.0"
|
||||
version = "0.126.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
@@ -294,6 +294,7 @@ tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", r
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
|
||||
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
|
||||
unindent = "0.1.7"
|
||||
unicase = "2.6"
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
wasmtime = "18.0"
|
||||
@@ -309,7 +310,7 @@ pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "e4fc
|
||||
split-debuginfo = "unpacked"
|
||||
debug = "limited"
|
||||
|
||||
# todo!(linux) - Remove this
|
||||
# todo(linux) - Remove this
|
||||
[profile.dev.package.blade-graphics]
|
||||
split-debuginfo = "off"
|
||||
debug = "full"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z"/>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7 13.179688 C 10.867188 13.179688 14 10.652344 14 7.53125 C 14 5.59375 12.800781 3.886719 10.957031 2.871094 C 10.207031 2.453125 9.617188 2.078125 9.125 1.769531 C 8.199219 1.179688 7.628906 0.820312 7 0.820312 C 6.359375 0.820312 5.636719 1.277344 4.6875 1.882812 C 4.148438 2.230469 3.601562 2.558594 3.042969 2.871094 C 1.199219 3.886719 0 5.59375 0 7.53125 C 0 10.652344 3.132812 13.179688 7 13.179688 Z M 6.183594 2.75 C 6.378906 2.308594 6.476562 1.828125 6.472656 1.34375 C 6.472656 1.261719 6.589844 1.234375 6.605469 1.328125 C 6.992188 2.953125 6.082031 3.757812 5.40625 4.027344 C 5.335938 4.054688 5.292969 3.953125 5.347656 3.902344 C 5.703125 3.582031 5.988281 3.191406 6.183594 2.75 Z M 7.382812 2.691406 C 7.328125 2.214844 7.171875 1.757812 6.925781 1.347656 L 6.925781 1.335938 C 6.886719 1.265625 6.976562 1.183594 7.035156 1.234375 C 8.179688 2.46875 7.796875 3.609375 7.359375 4.183594 C 7.3125 4.242188 7.226562 4.179688 7.25 4.109375 C 7.394531 3.652344 7.4375 3.167969 7.382812 2.691406 Z M 8.417969 2.363281 C 8.183594 1.949219 7.863281 1.589844 7.480469 1.308594 L 7.480469 1.300781 C 7.414062 1.253906 7.464844 1.140625 7.546875 1.175781 C 9.058594 1.808594 9.164062 3.03125 8.980469 3.746094 C 8.976562 3.761719 8.964844 3.777344 8.953125 3.785156 C 8.921875 3.808594 8.882812 3.800781 8.863281 3.773438 C 8.851562 3.757812 8.847656 3.742188 8.847656 3.722656 C 8.800781 3.246094 8.65625 2.78125 8.417969 2.363281 Z M 5.453125 2.691406 C 5.09375 3.007812 4.703125 3.132812 4.25 3.273438 C 4.179688 3.273438 4.132812 3.230469 4.15625 3.167969 C 5.179688 2.636719 5.542969 2.207031 5.90625 1.546875 C 5.90625 1.546875 5.996094 1.480469 6.015625 1.597656 C 6.015625 1.773438 5.8125 2.371094 5.453125 2.691406 Z M 8.335938 9.246094 C 8.253906 9.597656 8.0625 9.914062 7.789062 10.152344 C 7.589844 10.355469 7.324219 10.480469 7.039062 10.511719 C 6.746094 10.484375 6.472656 10.359375 6.265625 10.152344 C 5.996094 9.914062 5.808594 9.597656 5.726562 9.246094 C 5.71875 9.203125 5.734375 9.160156 5.761719 9.128906 C 5.792969 9.101562 5.835938 9.085938 5.875 9.089844 L 8.1875 9.089844 C 8.230469 9.085938 8.269531 9.101562 8.300781 9.132812 C 8.328125 9.160156 8.34375 9.203125 8.335938 9.246094 Z M 5.152344 7.976562 C 4.714844 8.273438 4.128906 8.210938 3.761719 7.832031 C 3.390625 7.445312 3.335938 6.855469 3.625 6.40625 C 3.746094 6.21875 3.917969 6.074219 4.121094 5.992188 C 4.320312 5.90625 4.542969 5.882812 4.757812 5.925781 C 4.972656 5.96875 5.167969 6.078125 5.320312 6.234375 C 5.636719 6.5625 5.730469 7.046875 5.558594 7.46875 C 5.476562 7.675781 5.335938 7.851562 5.152344 7.976562 Z M 10.109375 7.980469 C 9.671875 8.273438 9.085938 8.210938 8.71875 7.832031 C 8.511719 7.617188 8.398438 7.332031 8.398438 7.035156 C 8.398438 6.8125 8.464844 6.589844 8.585938 6.40625 C 8.707031 6.21875 8.878906 6.074219 9.082031 5.988281 C 9.28125 5.90625 9.503906 5.882812 9.71875 5.925781 C 9.933594 5.972656 10.128906 6.078125 10.285156 6.238281 C 10.597656 6.566406 10.691406 7.050781 10.515625 7.472656 C 10.433594 7.679688 10.292969 7.855469 10.109375 7.980469 Z M 10.109375 7.980469 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.3 KiB |
6
assets/icons/file_icons/coffeescript.svg
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
@@ -1,4 +1,6 @@
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.235 6.519l-16.47-0.004 0.266 3.277 12.653 0.002-0.319 3.394h-8.298l0.3 3.215h7.725l-0.457 4.403-3.636 1.005-3.694-1.012-0.235-2.637h-3.262l0.362 4.817 6.829 2.128 6.714-1.912 1.521-16.675zM2.879 1.004h26.242l-2.387 26.946-10.763 3.045-10.703-3.047z"></path>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.601562 2.851562 L 3.398438 2.851562 L 3.511719 4.285156 L 9.050781 4.285156 L 8.910156 5.769531 L 5.28125 5.769531 L 5.410156 7.175781 L 8.789062 7.175781 L 8.589844 9.101562 L 7 9.542969 L 5.382812 9.097656 L 5.28125 7.945312 L 3.851562 7.945312 L 4.011719 10.054688 L 7 10.984375 L 9.9375 10.148438 Z M 1.257812 0.4375 L 12.742188 0.4375 L 11.695312 12.226562 L 6.988281 13.558594 L 2.304688 12.226562 Z M 1.257812 0.4375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 730 B |
@@ -1 +1,12 @@
|
||||
<svg height="64" viewBox="0 0 128 128" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" x2="128" y1="128" y2="0"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#5d5d5d"/></linearGradient><path d="m12.239265 30.664279h14.960911c-5.59432 5.460938-7.654216 10.692785-10.342106 18.023379-3.200764 8.729348-.549141 29.987457 3.815534 37.55289 2.943384 5.101853 6.282685 8.994876 8.233522 11.095173h-16.667861zm89.614855 0h13.90661v66.671442h-13.55518c1.31391-1.750328 3.43934-4.534454 5.12085-6.426163 2.32782-2.618784 4.97023-6.978412 4.97023-6.978412l-16.015202-8.133112s-5.48977 11.600331-15.964999 15.964998c-10.475214 4.364666-19.784679-.838179-25.604243-7.530659-5.819578-6.692502-5.82371-22.14014-5.82371-22.14014h60.797524c1.16391-14.839892-2.63216-21.249816-4.66901-25.90547-.91799-2.098266-1.89261-3.810819-3.16287-5.522484zm-38.356164 1.757154c.35429-.01632.731685-.0092 1.104497 0 11.930114.290977 13.053143 12.802122 13.053143 12.802122h-27.311192s2.170772-12.298638 13.153552-12.802122z" fill="url(#a)"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<defs>
|
||||
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="0" y1="128" x2="128" y2="0" gradientTransform="matrix(0.109375,0,0,0.109375,0,0)">
|
||||
<stop offset="0" style="stop-color:rgb(20%,20%,20%);stop-opacity:1;"/>
|
||||
<stop offset="1" style="stop-color:rgb(36.470588%,36.470588%,36.470588%);stop-opacity:1;"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 1.339844 3.355469 L 2.976562 3.355469 C 2.363281 3.953125 2.136719 4.523438 1.84375 5.324219 C 1.492188 6.28125 1.785156 8.605469 2.261719 9.433594 C 2.582031 9.992188 2.949219 10.417969 3.160156 10.644531 L 1.339844 10.644531 Z M 11.140625 3.355469 L 12.660156 3.355469 L 12.660156 10.644531 L 11.179688 10.644531 C 11.324219 10.453125 11.554688 10.148438 11.738281 9.941406 C 11.992188 9.65625 12.28125 9.179688 12.28125 9.179688 L 10.53125 8.289062 C 10.53125 8.289062 9.929688 9.558594 8.785156 10.035156 C 7.640625 10.515625 6.621094 9.945312 5.984375 9.214844 C 5.347656 8.480469 5.347656 6.792969 5.347656 6.792969 L 11.996094 6.792969 C 12.125 5.167969 11.710938 4.46875 11.484375 3.957031 C 11.386719 3.726562 11.277344 3.542969 11.140625 3.355469 Z M 6.945312 3.546875 C 6.984375 3.542969 7.023438 3.546875 7.066406 3.546875 C 8.371094 3.578125 8.492188 4.945312 8.492188 4.945312 L 5.507812 4.945312 C 5.507812 4.945312 5.742188 3.601562 6.945312 3.546875 Z M 6.945312 3.546875 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -27,6 +27,7 @@
|
||||
"css": "css",
|
||||
"csv": "storage",
|
||||
"cts": "typescript",
|
||||
"coffee": "coffeescript",
|
||||
"dart": "dart",
|
||||
"dat": "storage",
|
||||
"db": "storage",
|
||||
@@ -48,6 +49,7 @@
|
||||
"fmp": "storage",
|
||||
"fp7": "storage",
|
||||
"frm": "storage",
|
||||
"fs": "fsharp",
|
||||
"gdb": "storage",
|
||||
"gif": "image",
|
||||
"gitattributes": "vcs",
|
||||
@@ -104,6 +106,7 @@
|
||||
"myd": "storage",
|
||||
"myi": "storage",
|
||||
"nu": "terminal",
|
||||
"nim": "nim",
|
||||
"odp": "document",
|
||||
"ods": "document",
|
||||
"odt": "document",
|
||||
@@ -138,6 +141,9 @@
|
||||
"sqlite": "storage",
|
||||
"svelte": "template",
|
||||
"svg": "image",
|
||||
"sc": "scala",
|
||||
"scala": "scala",
|
||||
"sql": "storage",
|
||||
"swift": "swift",
|
||||
"tf": "terraform",
|
||||
"tfvars": "terraform",
|
||||
@@ -148,6 +154,7 @@
|
||||
"ttf": "font",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"tcl": "tcl",
|
||||
"vue": "vue",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
@@ -189,6 +196,9 @@
|
||||
"css": {
|
||||
"icon": "icons/file_icons/css.svg"
|
||||
},
|
||||
"coffeescript": {
|
||||
"icon": "icons/file_icons/coffeescript.svg"
|
||||
},
|
||||
"dart": {
|
||||
"icon": "icons/file_icons/dart.svg"
|
||||
},
|
||||
@@ -222,6 +232,9 @@
|
||||
"font": {
|
||||
"icon": "icons/file_icons/font.svg"
|
||||
},
|
||||
"fsharp": {
|
||||
"icon": "icons/file_icons/fsharp.svg"
|
||||
},
|
||||
"haskell": {
|
||||
"icon": "icons/file_icons/haskell.svg"
|
||||
},
|
||||
@@ -258,6 +271,9 @@
|
||||
"ocaml": {
|
||||
"icon": "icons/file_icons/ocaml.svg"
|
||||
},
|
||||
"nim": {
|
||||
"icon": "icons/file_icons/nim.svg"
|
||||
},
|
||||
"phoenix": {
|
||||
"icon": "icons/file_icons/phoenix.svg"
|
||||
},
|
||||
@@ -288,6 +304,9 @@
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"scala": {
|
||||
"icon": "icons/file_icons/scala.svg"
|
||||
},
|
||||
"swift": {
|
||||
"icon": "icons/file_icons/swift.svg"
|
||||
},
|
||||
@@ -306,6 +325,9 @@
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"tcl": {
|
||||
"icon": "icons/file_icons/tcl.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
|
||||
8
assets/icons/file_icons/fsharp.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 3.324219 3.496094 C 1.507812 5.40625 0.0273438 6.984375 0.0273438 6.996094 C 0.0273438 7.015625 1.511719 8.59375 3.332031 10.503906 L 6.632812 13.980469 L 6.632812 10.472656 L 4.984375 8.734375 L 3.332031 6.996094 L 4.984375 5.257812 L 6.632812 3.519531 L 6.628906 1.773438 L 6.621094 0.0273438 Z M 3.324219 3.496094 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.125 1.773438 L 7.125 3.492188 L 8.769531 5.222656 C 9.675781 6.171875 10.421875 6.96875 10.425781 6.984375 C 10.433594 7.003906 9.691406 7.800781 8.78125 8.761719 L 7.125 10.503906 L 7.125 13.972656 L 7.214844 13.890625 C 7.296875 13.820312 8.203125 12.90625 9.167969 11.910156 C 9.398438 11.671875 9.605469 11.464844 9.621094 11.449219 C 9.671875 11.402344 11.261719 9.789062 11.601562 9.4375 C 11.773438 9.261719 11.957031 9.082031 12 9.035156 C 12.046875 8.988281 12.433594 8.59375 12.863281 8.160156 C 13.289062 7.726562 13.722656 7.289062 13.824219 7.183594 L 14.007812 6.996094 L 13.808594 6.796875 C 13.179688 6.167969 12.527344 5.503906 11.820312 4.785156 C 11.574219 4.53125 11.105469 4.058594 10.785156 3.734375 C 10.460938 3.414062 9.871094 2.8125 9.472656 2.402344 C 9.074219 1.996094 8.609375 1.515625 8.4375 1.339844 C 8.265625 1.160156 7.910156 0.800781 7.652344 0.539062 C 7.394531 0.273438 7.167969 0.0585938 7.15625 0.0585938 C 7.132812 0.0585938 7.125 0.59375 7.125 1.773438 Z M 7.125 1.773438 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.453125 5.757812 C 4.308594 6.964844 4.285156 6.992188 4.332031 7.050781 C 4.359375 7.082031 4.886719 7.636719 5.5 8.289062 L 6.621094 9.464844 L 6.628906 8.222656 C 6.632812 7.539062 6.632812 6.429688 6.628906 5.753906 L 6.621094 4.527344 Z M 5.453125 5.757812 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -1,13 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg977"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs981" />
|
||||
<path
|
||||
id="path973"
|
||||
d="M 10.699219 8.9003906 L 10.699219 9 C 12.199219 11.3 13.800781 13.600391 15.300781 15.900391 L 15.300781 16 C 13.800781 18.3 12.199219 20.600391 10.699219 22.900391 L 10.699219 23 L 14.199219 23 L 14.300781 22.900391 C 15.200781 21.500391 16.199609 20.099219 17.099609 18.699219 C 17.199609 18.599219 17.099219 18.599219 17.199219 18.699219 C 18.099219 20.099219 19.1 21.500391 20 22.900391 L 20.099609 23 L 23.599609 23 C 21.699609 20 19.699219 17.099609 17.699219 14.099609 C 16.499219 12.399609 15.399219 10.600391 14.199219 8.9003906 L 10.699219 8.9003906 z M 6 9 C 7.6 11.3 9.0996094 13.6 10.599609 16 L 10.599609 16.099609 C 9.4996094 17.799609 8.4007813 19.399609 7.3007812 21.099609 C 6.8007813 21.699609 6.4 22.4 6 23 L 6 23.099609 L 9.5 23.099609 C 11.1 20.699609 12.699219 18.399609 14.199219 16.099609 L 14.199219 16 C 13.499219 14.8 12.700391 13.7 11.900391 12.5 C 11.100391 11.4 10.399609 10.199609 9.5996094 9.0996094 L 9.5 9 L 6 9 z M 18.199219 13 L 18.199219 13.099609 C 18.699219 13.899609 19.199219 14.600391 19.699219 15.400391 L 26 15.400391 L 26 13 L 18.199219 13 z M 20.5 16.599609 L 20.5 16.699219 C 21 17.499219 21.5 18.2 22 19 L 26 19 L 26 16.599609 L 20.5 16.599609 z " />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.679688 3.894531 L 4.679688 3.9375 C 5.335938 4.945312 6.039062 5.949219 6.695312 6.957031 L 6.695312 7 C 6.039062 8.007812 5.335938 9.011719 4.679688 10.019531 L 4.679688 10.0625 L 6.210938 10.0625 L 6.257812 10.019531 C 6.648438 9.40625 7.085938 8.792969 7.480469 8.179688 C 7.523438 8.136719 7.480469 8.136719 7.523438 8.179688 C 7.917969 8.792969 8.355469 9.40625 8.75 10.019531 L 8.792969 10.0625 L 10.324219 10.0625 C 9.492188 8.75 8.617188 7.480469 7.742188 6.167969 C 7.21875 5.425781 6.738281 4.636719 6.210938 3.894531 Z M 2.625 3.9375 C 3.324219 4.945312 3.980469 5.949219 4.636719 7 L 4.636719 7.042969 C 4.15625 7.789062 3.675781 8.488281 3.195312 9.230469 C 2.976562 9.492188 2.800781 9.800781 2.625 10.0625 L 2.625 10.105469 L 4.15625 10.105469 C 4.855469 9.054688 5.554688 8.050781 6.210938 7.042969 L 6.210938 7 C 5.90625 6.476562 5.554688 5.992188 5.207031 5.46875 C 4.855469 4.988281 4.550781 4.460938 4.199219 3.980469 L 4.15625 3.9375 Z M 7.960938 5.6875 L 7.960938 5.730469 C 8.179688 6.082031 8.398438 6.386719 8.617188 6.738281 L 11.375 6.738281 L 11.375 5.6875 Z M 8.96875 7.261719 L 8.96875 7.304688 C 9.1875 7.65625 9.40625 7.960938 9.625 8.3125 L 11.375 8.3125 L 11.375 7.261719 Z M 8.96875 7.261719 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
6
assets/icons/file_icons/nim.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.054688 1.691406 C 7.054688 1.691406 6.519531 2.148438 5.972656 2.59375 C 5.410156 2.578125 4.304688 2.710938 3.710938 2.945312 C 3.15625 2.570312 2.671875 2.15625 2.671875 2.15625 C 2.671875 2.15625 2.257812 2.917969 2 3.367188 C 1.613281 3.585938 1.226562 3.832031 0.882812 4.160156 C 0.480469 3.988281 0.015625 3.78125 0 3.777344 C 0.53125 4.921875 0.886719 6.070312 1.863281 6.761719 C 3.410156 4.144531 10.601562 4.386719 12.183594 6.746094 C 13.203125 6.175781 13.597656 4.953125 14 3.820312 C 13.957031 3.835938 13.410156 4.03125 13.058594 4.175781 C 12.84375 3.929688 12.347656 3.550781 12.0625 3.367188 C 11.847656 2.949219 11.628906 2.539062 11.402344 2.128906 C 11.402344 2.128906 10.941406 2.496094 10.402344 2.902344 C 9.675781 2.757812 8.800781 2.585938 8.0625 2.628906 C 7.71875 2.320312 7.382812 2.011719 7.054688 1.691406 Z M 0.550781 6.210938 L 1.828125 9.519531 C 4.046875 12.652344 9.707031 12.867188 12.175781 9.582031 C 12.757812 8.171875 13.546875 6.191406 13.546875 6.191406 C 12.914062 7.199219 11.882812 7.890625 11.246094 8.261719 C 10.796875 8.527344 9.757812 8.6875 9.757812 8.6875 L 7.023438 7.171875 L 4.277344 8.65625 C 4.277344 8.65625 3.25 8.480469 2.785156 8.25 C 1.847656 7.714844 1.214844 7.078125 0.550781 6.210938 Z M 0.550781 6.210938 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1 +1,6 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path d="M170.322 349.808c-2.4-15.66-9-28.38-25.020-34.531-6.27-2.4-11.7-6.78-17.88-9.54-7.020-3.15-14.16-6.15-21.57-8.1-5.61-1.5-10.83 1.020-14.16 5.94-3.15 4.62-0.87 8.97 1.77 12.84 2.97 4.35 6.27 8.49 9.6 12.57 5.52 6.78 11.37 13.29 16.74 20.161 5.13 6.57 9.51 13.86 8.76 22.56-1.65 19.080-10.29 34.891-24.21 47.76-1.53 1.38-4.23 2.37-6.21 2.19-8.88-0.96-16.95-4.32-23.46-10.53-7.47-7.11-6.33-15.48 2.61-20.67 2.13-1.23 4.35-2.37 6.3-3.87 5.46-4.11 7.29-11.13 4.32-17.22-1.41-2.94-3-6.12-5.34-8.25-11.43-10.41-22.651-21.151-34.891-30.63-29.671-23.041-44.91-53.52-47.251-90.421-2.64-40.981 6.87-79.231 28.5-114.242 8.19-13.29 17.73-25.951 32.37-32.52 9.96-4.47 20.88-6.99 31.531-9.78 29.311-7.71 58.89-13.5 89.401-8.34 26.28 4.41 45.511 17.94 54.331 43.77 5.79 16.89 7.17 34.35 5.37 52.231-3.54 35.131-29.49 66.541-63.331 75.841-14.67 4.020-22.68 1.77-31.5-10.44-6.33-8.79-11.58-18.36-17.25-27.631-0.84-1.38-1.44-2.97-2.16-4.44-0.69-1.47-1.44-2.88-2.16-4.35 2.13 15.24 5.67 29.911 13.98 42.99 4.5 7.11 10.5 12.36 19.29 13.14 32.34 2.91 59.641-7.71 79.021-33.721 21.69-29.101 26.461-62.581 20.19-97.831-1.23-6.96-3.3-13.77-4.77-20.7-0.99-4.47 0.78-7.77 5.19-9.33 2.040-0.69 4.14-1.26 6.18-1.68 26.461-5.7 53.221-7.59 80.191-4.86 30.601 3.060 59.551 11.46 85.441 28.471 40.531 26.67 65.641 64.621 79.291 110.522 1.98 6.66 2.28 13.95 2.46 20.971 0.12 4.68-2.88 5.91-6.45 2.97-3.93-3.21-7.53-6.87-10.92-10.65-3.15-3.57-5.67-7.65-8.73-11.4-2.37-2.94-4.44-2.49-5.58 1.17-0.72 2.22-1.35 4.41-1.98 6.63-7.080 25.26-18.24 48.3-36.33 67.711-2.52 2.73-4.77 6.78-5.070 10.38-0.78 9.96-1.35 20.13-0.39 30.060 1.98 21.331 5.070 42.57 7.47 63.871 1.35 12.030-2.52 19.11-13.83 23.281-7.95 2.91-16.47 5.040-24.87 5.64-13.38 0.93-26.88 0.27-40.32 0.27-0.36-15 0.93-29.731-13.17-37.771 2.73-11.13 5.88-21.69 7.77-32.49 1.56-8.97 0.24-17.79-6.060-25.14-5.91-6.93-13.32-8.82-20.101-4.86-20.43 11.91-41.671 11.97-63.301 4.17-9.93-3.6-16.86-1.56-22.351 7.5-5.91 9.75-8.4 20.7-7.74 31.771 0.84 13.95 3.27 27.75 5.13 41.64 1.020 7.77 0.15 9.78-7.56 11.76-17.13 4.35-34.56 4.83-52.081 3.42-0.93-0.090-1.86-0.48-2.46-0.63-0.87-14.55 0.66-29.671-16.68-37.411 7.68-16.29 6.63-33.18 3.99-50.070l-0.060-0.15zM66.761 292.718c2.55-2.4 4.59-6.15 5.31-9.6 1.8-8.64-4.68-20.22-12.18-23.43-3.99-1.74-7.47-1.11-10.29 2.070-6.87 7.77-13.65 15.63-20.401 23.521-1.14 1.35-2.16 2.94-2.97 4.53-2.7 5.19-1.11 8.97 4.65 10.38 3.48 0.87 7.080 1.050 10.65 1.56 9.3-0.9 18.3-2.46 25.23-9v-0.030zM67.541 206.347c-0.030-6.18-5.19-11.34-11.28-11.37-6.27-0.030-11.67 5.58-11.46 11.76 0.27 6.21 5.43 11.19 11.61 11.070 6.24-0.090 11.22-5.19 11.16-11.43l-0.030-0.030z"></path></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.65625 9.566406 C 4.589844 9.136719 4.410156 8.789062 3.972656 8.621094 C 3.800781 8.554688 3.652344 8.433594 3.484375 8.359375 C 3.292969 8.273438 3.097656 8.191406 2.894531 8.136719 C 2.742188 8.097656 2.597656 8.167969 2.507812 8.300781 C 2.421875 8.425781 2.484375 8.546875 2.554688 8.652344 C 2.636719 8.769531 2.726562 8.882812 2.816406 8.996094 C 2.96875 9.179688 3.128906 9.359375 3.277344 9.546875 C 3.417969 9.726562 3.535156 9.925781 3.515625 10.164062 C 3.46875 10.6875 3.234375 11.117188 2.851562 11.46875 C 2.8125 11.507812 2.738281 11.535156 2.683594 11.53125 C 2.441406 11.503906 2.21875 11.410156 2.042969 11.242188 C 1.835938 11.046875 1.867188 10.820312 2.113281 10.675781 C 2.171875 10.644531 2.230469 10.613281 2.285156 10.570312 C 2.433594 10.457031 2.484375 10.265625 2.402344 10.101562 C 2.367188 10.019531 2.320312 9.933594 2.257812 9.875 C 1.945312 9.589844 1.636719 9.296875 1.304688 9.035156 C 0.492188 8.40625 0.0742188 7.574219 0.0117188 6.5625 C -0.0585938 5.445312 0.199219 4.398438 0.792969 3.441406 C 1.015625 3.078125 1.277344 2.730469 1.675781 2.550781 C 1.949219 2.429688 2.246094 2.359375 2.539062 2.285156 C 3.339844 2.074219 4.148438 1.914062 4.984375 2.054688 C 5.703125 2.175781 6.226562 2.546875 6.46875 3.253906 C 6.625 3.714844 6.664062 4.191406 6.617188 4.679688 C 6.519531 5.640625 5.808594 6.5 4.882812 6.753906 C 4.484375 6.863281 4.261719 6.804688 4.023438 6.46875 C 3.847656 6.230469 3.707031 5.96875 3.550781 5.714844 C 3.527344 5.675781 3.511719 5.632812 3.492188 5.59375 C 3.472656 5.550781 3.453125 5.511719 3.433594 5.472656 C 3.492188 5.890625 3.585938 6.292969 3.816406 6.648438 C 3.9375 6.84375 4.101562 6.988281 4.34375 7.007812 C 5.226562 7.085938 5.972656 6.796875 6.503906 6.085938 C 7.097656 5.289062 7.226562 4.375 7.054688 3.410156 C 7.019531 3.21875 6.964844 3.035156 6.925781 2.84375 C 6.898438 2.722656 6.945312 2.632812 7.066406 2.589844 C 7.121094 2.570312 7.179688 2.554688 7.234375 2.542969 C 7.960938 2.386719 8.691406 2.335938 9.429688 2.410156 C 10.265625 2.496094 11.054688 2.722656 11.765625 3.191406 C 12.871094 3.917969 13.558594 4.957031 13.933594 6.210938 C 13.988281 6.394531 13.996094 6.59375 14 6.785156 C 14.003906 6.914062 13.921875 6.945312 13.824219 6.867188 C 13.714844 6.777344 13.617188 6.679688 13.523438 6.574219 C 13.4375 6.476562 13.371094 6.367188 13.285156 6.261719 C 13.222656 6.183594 13.164062 6.195312 13.132812 6.296875 C 13.113281 6.355469 13.097656 6.414062 13.078125 6.476562 C 12.886719 7.167969 12.582031 7.796875 12.085938 8.328125 C 12.015625 8.402344 11.957031 8.511719 11.949219 8.613281 C 11.925781 8.882812 11.910156 9.164062 11.9375 9.433594 C 11.992188 10.015625 12.074219 10.597656 12.140625 11.179688 C 12.179688 11.507812 12.070312 11.703125 11.761719 11.816406 C 11.546875 11.894531 11.3125 11.953125 11.082031 11.972656 C 10.71875 11.996094 10.347656 11.976562 9.980469 11.976562 C 9.96875 11.566406 10.003906 11.164062 9.621094 10.945312 C 9.695312 10.640625 9.78125 10.351562 9.832031 10.058594 C 9.875 9.8125 9.839844 9.570312 9.667969 9.371094 C 9.503906 9.179688 9.304688 9.128906 9.117188 9.238281 C 8.558594 9.5625 7.976562 9.5625 7.386719 9.351562 C 7.113281 9.253906 6.925781 9.308594 6.773438 9.554688 C 6.613281 9.824219 6.546875 10.121094 6.5625 10.425781 C 6.585938 10.804688 6.652344 11.183594 6.703125 11.5625 C 6.730469 11.777344 6.707031 11.832031 6.496094 11.886719 C 6.027344 12.003906 5.550781 12.015625 5.074219 11.976562 C 5.046875 11.976562 5.023438 11.964844 5.007812 11.960938 C 4.980469 11.5625 5.023438 11.148438 4.550781 10.9375 C 4.761719 10.492188 4.730469 10.03125 4.660156 9.570312 Z M 1.824219 8.003906 C 1.894531 7.9375 1.949219 7.835938 1.96875 7.742188 C 2.019531 7.503906 1.84375 7.1875 1.636719 7.101562 C 1.527344 7.054688 1.433594 7.070312 1.355469 7.15625 C 1.167969 7.371094 0.984375 7.585938 0.796875 7.800781 C 0.765625 7.835938 0.738281 7.882812 0.71875 7.925781 C 0.644531 8.066406 0.6875 8.167969 0.84375 8.207031 C 0.941406 8.230469 1.039062 8.238281 1.136719 8.25 C 1.390625 8.226562 1.636719 8.183594 1.824219 8.003906 Z M 1.847656 5.640625 C 1.847656 5.472656 1.703125 5.332031 1.539062 5.332031 C 1.367188 5.332031 1.21875 5.484375 1.226562 5.652344 C 1.230469 5.824219 1.375 5.957031 1.542969 5.957031 C 1.714844 5.953125 1.847656 5.8125 1.847656 5.644531 Z M 1.847656 5.640625 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.5 KiB |
6
assets/icons/file_icons/scala.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 11.214844 0.015625 C 11.214844 1.125 5.859375 1.976562 2.785156 2.179688 L 2.785156 5.335938 C 4.082031 5.421875 5.789062 5.628906 7.324219 5.917969 C 5.789062 6.207031 4.082031 6.410156 2.785156 6.492188 L 2.785156 9.648438 C 4.082031 9.734375 5.785156 9.9375 7.320312 10.226562 C 5.785156 10.515625 4.082031 10.71875 2.785156 10.804688 L 2.785156 13.988281 L 4.308594 13.765625 C 7.152344 13.339844 10.285156 12.53125 11.214844 11.820312 L 11.214844 8.632812 C 11.214844 8.433594 10.851562 8.21875 10.265625 8 C 10.675781 7.832031 11.003906 7.667969 11.214844 7.507812 L 11.214844 4.324219 C 11.214844 4.125 10.855469 3.90625 10.277344 3.6875 C 10.683594 3.523438 11.003906 3.355469 11.214844 3.195312 Z M 10.269531 8.339844 C 10.6875 8.503906 10.839844 8.617188 10.890625 8.664062 C 10.855469 8.734375 10.726562 8.871094 10.359375 9.050781 C 10.300781 9.078125 10.238281 9.105469 10.183594 9.128906 C 9.550781 9.402344 8.570312 9.667969 7.320312 9.90625 C 6.707031 9.792969 6.046875 9.6875 5.382812 9.597656 C 7.25 9.261719 9.09375 8.796875 10.269531 8.339844 Z M 10.277344 4.027344 C 10.691406 4.195312 10.839844 4.308594 10.890625 4.355469 C 10.863281 4.40625 10.78125 4.5 10.578125 4.625 L 10.574219 4.625 C 10.535156 4.648438 10.488281 4.675781 10.441406 4.699219 C 9.839844 5.011719 8.765625 5.324219 7.335938 5.59375 C 6.71875 5.480469 6.058594 5.375 5.394531 5.285156 C 7.269531 4.953125 9.097656 4.492188 10.277344 4.027344 Z M 10.277344 4.027344 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
8
assets/icons/file_icons/tcl.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.5 20.821429 10.241071 19.5625 L 11.133929 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.607143 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.294643 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
|
||||
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 20.375 3.133929 18.973214 3.991071 18.017857 5.258929 L 18.017857 5.267857 L 17.392857 6.321429 L 17.339286 6.419643 L 17.294643 6.321429 L 17 5.544643 C 14.928571 7.625 13.366071 9.955357 13.419643 12.267857 L 13.419643 12.375 L 13.339286 12.3125 L 12.598214 11.821429 C 11.571429 14.669643 10.517857 16.848214 11.196429 19.875 L 11.223214 19.973214 L 11.125 19.9375 L 10.241071 19.5625 C 10.241071 19.580357 10.25 19.598214 10.25 19.616071 C 10.517857 20.839286 11.294643 21.901786 12.375 22.544643 C 12.428571 22.160714 12.482143 21.776786 12.544643 21.383929 C 11 17.767857 12.348214 15.107143 12.955357 12.723214 L 13.892857 13.258929 C 13.758929 11.026786 15.080357 8.607143 16.785714 6.508929 L 17.285714 7.375 C 18.553571 4.767857 19.5625 3.723214 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
|
||||
<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 22.517857 2 L 22.464286 2.008929 C 20.383929 2.375 18.339286 3.133929 17.446429 4.973214 L 17.071429 4.3125 L 17.035714 4.25 L 16.991071 4.303571 C 15.883929 5.357143 14.892857 6.526786 14.044643 7.803571 C 13.339286 8.723214 12.919643 9.839286 12.839286 11 L 12.303571 10.339286 L 12.25 10.267857 L 12.214286 10.348214 C 11.508929 11.857143 10.9375 13.428571 10.517857 15.044643 C 10.116071 16.25 10.0625 17.535714 10.366071 18.767857 L 9.482143 18.258929 L 9.410714 18.214286 L 9.401786 18.294643 C 9.223214 20.151786 9.982143 21.973214 11.419643 23.142857 L 10.455357 23.383929 L 10.25 23.428571 L 10.455357 23.482143 C 10.973214 23.598214 11.464286 23.794643 11.901786 24.089286 C 12.3125 24.383929 12.508929 24.892857 12.419643 25.383929 L 12.419643 28.098214 L 12.428571 28.116071 L 13.651786 29.857143 L 13.75 30 L 13.75 25.723214 C 13.839286 25.178571 14.053571 24.678571 14.366071 24.232143 C 14.660714 23.910714 15.071429 23.723214 15.5 23.696429 L 15.678571 23.678571 L 15.517857 23.598214 L 14.875 23.303571 C 16.714286 22.035714 18.035714 20.142857 18.571429 17.982143 L 18.589286 17.892857 L 18.508929 17.919643 L 17.714286 18.133929 C 18.607143 17.098214 19.3125 15.910714 19.803571 14.633929 C 20.508929 13 21.178571 11.169643 21.732143 9.696429 L 21.758929 9.625 L 21.678571 9.625 L 21.0625 9.669643 C 21.857143 8.651786 22.339286 7.428571 22.446429 6.142857 C 22.633929 4.785714 22.660714 3.419643 22.526786 2.053571 Z M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.508929 20.821429 10.241071 19.5625 L 11.142857 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.616071 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.303571 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -1,6 +1,9 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 8.01531L19.2375 12.1073V20.2894L12.5066 16.1992V8.01531Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0294 12.1073V20.2894L27.1563 16.1992V8.01531L20.0294 12.1073Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58781 3.66V11.5787L11.7147 15.5381V7.61937L4.58781 3.66Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 25.04L19.2375 29V21.1348V21.0818L12.5066 17.1219V25.04Z" fill="black"/>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 3.507812 L 8.417969 5.296875 L 8.417969 8.875 L 5.472656 7.085938 Z M 5.472656 3.507812 "/>
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 8.761719 5.296875 L 8.761719 8.875 L 11.882812 7.085938 L 11.882812 3.507812 Z M 8.761719 5.296875 "/>
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2.007812 1.601562 L 2.007812 5.066406 L 5.125 6.796875 L 5.125 3.332031 Z M 2.007812 1.601562 "/>
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 10.953125 L 8.417969 12.6875 L 8.417969 9.222656 L 5.472656 7.492188 Z M 5.472656 10.953125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 961 B |
@@ -16,6 +16,7 @@
|
||||
"ctrl-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"shift-enter": "menu::UseSelectedQuery",
|
||||
"ctrl-shift-w": "workspace::CloseWindow",
|
||||
"shift-escape": "workspace::ToggleZoom",
|
||||
"ctrl-o": "workspace::Open",
|
||||
@@ -72,7 +73,7 @@
|
||||
"ctrl-n": "editor::MoveDown",
|
||||
"ctrl-b": "editor::MoveLeft",
|
||||
"ctrl-f": "editor::MoveRight",
|
||||
"ctrl-shift-l": "editor::NextScreen", // todo!(linux): What is this
|
||||
"ctrl-shift-l": "editor::NextScreen", // todo(linux): What is this
|
||||
"alt-left": "editor::MoveToPreviousWordStart",
|
||||
"alt-b": "editor::MoveToPreviousWordStart",
|
||||
"alt-right": "editor::MoveToNextWordEnd",
|
||||
@@ -387,7 +388,7 @@
|
||||
}
|
||||
},
|
||||
// Bindings from Sublime Text
|
||||
// todo!(linux) make sure these match linux bindings or remove above comment?
|
||||
// todo(linux) make sure these match linux bindings or remove above comment?
|
||||
{
|
||||
"context": "Editor",
|
||||
"bindings": {
|
||||
@@ -411,7 +412,7 @@
|
||||
}
|
||||
},
|
||||
// Bindings from Atom
|
||||
// todo!(linux) make sure these match linux bindings or remove above comment?
|
||||
// todo(linux) make sure these match linux bindings or remove above comment?
|
||||
{
|
||||
"context": "Pane",
|
||||
"bindings": {
|
||||
@@ -466,6 +467,7 @@
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"ctrl-k enter": "editor::OpenExcerptsSplit",
|
||||
"ctrl-f8": "editor::GoToHunk",
|
||||
"ctrl-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"shift-enter": "menu::UseSelectedQuery",
|
||||
"cmd-shift-w": "workspace::CloseWindow",
|
||||
"shift-escape": "workspace::ToggleZoom",
|
||||
"cmd-o": "workspace::Open",
|
||||
@@ -48,6 +49,7 @@
|
||||
"cmd-backspace": "editor::DeleteToBeginningOfLine",
|
||||
"cmd-delete": "editor::DeleteToEndOfLine",
|
||||
"alt-backspace": "editor::DeleteToPreviousWordStart",
|
||||
"ctrl-w": "editor::DeleteToPreviousWordStart",
|
||||
"alt-delete": "editor::DeleteToNextWordEnd",
|
||||
"alt-h": "editor::DeleteToPreviousWordStart",
|
||||
"alt-d": "editor::DeleteToNextWordEnd",
|
||||
@@ -506,6 +508,7 @@
|
||||
"context": "Editor && mode == full",
|
||||
"bindings": {
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"cmd-k enter": "editor::OpenExcerptsSplit",
|
||||
"cmd-f8": "editor::GoToHunk",
|
||||
"cmd-shift-f8": "editor::GoToPrevHunk",
|
||||
"ctrl-enter": "assistant::InlineAssist"
|
||||
|
||||
@@ -288,6 +288,13 @@
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
|
||||
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
|
||||
|
||||
"ctrl-w d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
"ctrl-w space": "editor::OpenExcerptsSplit",
|
||||
"ctrl-w g space": "editor::OpenExcerptsSplit",
|
||||
"-": "pane::RevealInProjectPanel"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ impl ActivityIndicator {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
workspace.add_item(
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(
|
||||
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
|
||||
),
|
||||
|
||||
@@ -102,8 +102,9 @@ pub struct OpenAiResponseStreamEvent {
|
||||
pub usage: Option<OpenAiUsage>,
|
||||
}
|
||||
|
||||
pub async fn stream_completion(
|
||||
async fn stream_completion(
|
||||
api_url: String,
|
||||
kind: OpenAiCompletionProviderKind,
|
||||
credential: ProviderCredential,
|
||||
executor: BackgroundExecutor,
|
||||
request: Box<dyn CompletionRequest>,
|
||||
@@ -117,10 +118,11 @@ pub async fn stream_completion(
|
||||
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAiResponseStreamEvent>>();
|
||||
|
||||
let (auth_header_name, auth_header_value) = kind.auth_header(api_key);
|
||||
let json_data = request.data()?;
|
||||
let mut response = Request::post(format!("{api_url}/chat/completions"))
|
||||
let mut response = Request::post(kind.completions_endpoint_url(&api_url))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header(auth_header_name, auth_header_value)
|
||||
.body(json_data)?
|
||||
.send_async()
|
||||
.await?;
|
||||
@@ -194,22 +196,65 @@ pub async fn stream_completion(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum OpenAiCompletionProviderKind {
|
||||
OpenAi,
|
||||
AzureOpenAi {
|
||||
deployment_id: String,
|
||||
api_version: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl OpenAiCompletionProviderKind {
|
||||
/// Returns the chat completion endpoint URL for this [`OpenAiCompletionProviderKind`].
|
||||
fn completions_endpoint_url(&self, api_url: &str) -> String {
|
||||
match self {
|
||||
Self::OpenAi => {
|
||||
// https://platform.openai.com/docs/api-reference/chat/create
|
||||
format!("{api_url}/chat/completions")
|
||||
}
|
||||
Self::AzureOpenAi {
|
||||
deployment_id,
|
||||
api_version,
|
||||
} => {
|
||||
// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#completions
|
||||
format!("{api_url}/openai/deployments/{deployment_id}/completions?api-version={api_version}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the authentication header for this [`OpenAiCompletionProviderKind`].
|
||||
fn auth_header(&self, api_key: String) -> (&'static str, String) {
|
||||
match self {
|
||||
Self::OpenAi => ("Authorization", format!("Bearer {api_key}")),
|
||||
Self::AzureOpenAi { .. } => ("Api-Key", api_key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAiCompletionProvider {
|
||||
api_url: String,
|
||||
kind: OpenAiCompletionProviderKind,
|
||||
model: OpenAiLanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl OpenAiCompletionProvider {
|
||||
pub async fn new(api_url: String, model_name: String, executor: BackgroundExecutor) -> Self {
|
||||
pub async fn new(
|
||||
api_url: String,
|
||||
kind: OpenAiCompletionProviderKind,
|
||||
model_name: String,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Self {
|
||||
let model = executor
|
||||
.spawn(async move { OpenAiLanguageModel::load(&model_name) })
|
||||
.await;
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
Self {
|
||||
api_url,
|
||||
kind,
|
||||
model,
|
||||
credential,
|
||||
executor,
|
||||
@@ -297,6 +342,7 @@ impl CompletionProvider for OpenAiCompletionProvider {
|
||||
let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
|
||||
model
|
||||
}
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
prompt: Box<dyn CompletionRequest>,
|
||||
@@ -307,7 +353,8 @@ impl CompletionProvider for OpenAiCompletionProvider {
|
||||
// At some point in the future we should rectify this.
|
||||
let credential = self.credential.read().clone();
|
||||
let api_url = self.api_url.clone();
|
||||
let request = stream_completion(api_url, credential, self.executor.clone(), prompt);
|
||||
let kind = self.kind.clone();
|
||||
let request = stream_completion(api_url, kind, credential, self.executor.clone(), prompt);
|
||||
async move {
|
||||
let response = request.await?;
|
||||
let stream = response
|
||||
@@ -322,6 +369,7 @@ impl CompletionProvider for OpenAiCompletionProvider {
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn box_clone(&self) -> Box<dyn CompletionProvider> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ use crate::{
|
||||
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
|
||||
};
|
||||
use ai::prompts::repository_context::PromptCodeSnippet;
|
||||
use ai::providers::open_ai::OPEN_AI_API_URL;
|
||||
use ai::{
|
||||
auth::ProviderCredential,
|
||||
completion::{CompletionProvider, CompletionRequest},
|
||||
providers::open_ai::{OpenAiCompletionProvider, OpenAiRequest, RequestMessage},
|
||||
providers::open_ai::{
|
||||
OpenAiCompletionProvider, OpenAiCompletionProviderKind, OpenAiRequest, RequestMessage,
|
||||
OPEN_AI_API_URL,
|
||||
},
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use chrono::{DateTime, Local};
|
||||
@@ -29,9 +31,9 @@ use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
|
||||
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter,
|
||||
FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement,
|
||||
IntoElement, Model, ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
|
||||
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context, EventEmitter, FocusHandle,
|
||||
FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
|
||||
ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
|
||||
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
|
||||
};
|
||||
@@ -131,6 +133,7 @@ impl AssistantPanel {
|
||||
})?;
|
||||
let completion_provider = OpenAiCompletionProvider::new(
|
||||
api_url,
|
||||
OpenAiCompletionProviderKind::OpenAi,
|
||||
model_name,
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
@@ -771,7 +774,7 @@ impl AssistantPanel {
|
||||
} else {
|
||||
editor.highlight_background::<PendingInlineAssist>(
|
||||
background_ranges,
|
||||
|theme| theme.editor_active_line_background, // todo!("use the appropriate color")
|
||||
|theme| theme.editor_active_line_background, // todo("use the appropriate color")
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -1274,25 +1277,25 @@ impl Render for AssistantPanel {
|
||||
let view = cx.view().clone();
|
||||
let scroll_handle = self.saved_conversations_scroll_handle.clone();
|
||||
let conversation_count = self.saved_conversations.len();
|
||||
canvas(move |bounds, cx| {
|
||||
uniform_list(
|
||||
view,
|
||||
"saved_conversations",
|
||||
conversation_count,
|
||||
|this, range, cx| {
|
||||
range
|
||||
.map(|ix| this.render_saved_conversation(ix, cx))
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.track_scroll(scroll_handle)
|
||||
.into_any_element()
|
||||
.draw(
|
||||
bounds.origin,
|
||||
bounds.size.map(AvailableSpace::Definite),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
canvas(
|
||||
move |bounds, cx| {
|
||||
let mut list = uniform_list(
|
||||
view,
|
||||
"saved_conversations",
|
||||
conversation_count,
|
||||
|this, range, cx| {
|
||||
range
|
||||
.map(|ix| this.render_saved_conversation(ix, cx))
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.track_scroll(scroll_handle)
|
||||
.into_any_element();
|
||||
list.layout(bounds.origin, bounds.size.into(), cx);
|
||||
list
|
||||
},
|
||||
|_bounds, mut list, cx| list.paint(cx),
|
||||
)
|
||||
.size_full()
|
||||
.into_any_element()
|
||||
}),
|
||||
@@ -1533,6 +1536,7 @@ impl Conversation {
|
||||
api_url
|
||||
.clone()
|
||||
.unwrap_or_else(|| OPEN_AI_API_URL.to_string()),
|
||||
OpenAiCompletionProviderKind::OpenAi,
|
||||
model.full_name().into(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ use smol::io::AsyncReadExt;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use smol::{fs::File, process::Command};
|
||||
|
||||
use release_channel::{AppCommitSha, ReleaseChannel};
|
||||
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
|
||||
use std::{
|
||||
env::consts::{ARCH, OS},
|
||||
ffi::OsString,
|
||||
@@ -190,7 +190,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
|
||||
|
||||
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
let version = AppVersion::global(cx).to_string();
|
||||
|
||||
let client = client::Client::global(cx).http_client();
|
||||
let url = client.build_url(&format!(
|
||||
@@ -242,7 +242,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
|
||||
Some(tab_description),
|
||||
cx,
|
||||
);
|
||||
workspace.add_item(Box::new(view.clone()), cx);
|
||||
workspace.add_item_to_active_pane(Box::new(view.clone()), cx);
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Render for UpdateNotification {
|
||||
crate::view_release_notes(&Default::default(), cx);
|
||||
this.dismiss(&menu::Cancel, cx)
|
||||
})),
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ mod linux {
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows {
|
||||
use std::path::Path;
|
||||
|
||||
@@ -130,7 +130,7 @@ async fn main() -> Result<()> {
|
||||
})
|
||||
.await?;
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(windows)]
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ async fn test_basic_following(
|
||||
let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
|
||||
let editor =
|
||||
cx.new_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
|
||||
workspace.add_item(Box::new(editor.clone()), cx);
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), cx);
|
||||
editor
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
@@ -204,7 +204,7 @@ impl ChatPanel {
|
||||
let panel = Self::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(|r| r.round());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
@@ -551,7 +551,6 @@ impl ChatPanel {
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(1)
|
||||
.right_0()
|
||||
.w_6()
|
||||
.bg(background)
|
||||
@@ -788,7 +787,7 @@ impl Render for ChatPanel {
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::send))
|
||||
.child(
|
||||
h_flex().z_index(1).child(
|
||||
h_flex().child(
|
||||
TabBar::new("chat_header").child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
|
||||
@@ -327,7 +327,7 @@ impl CollabPanel {
|
||||
let panel = CollabPanel::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
panel.collapsed_channels = serialized_panel
|
||||
.collapsed_channels
|
||||
.unwrap_or_else(|| Vec::new())
|
||||
@@ -993,7 +993,6 @@ impl CollabPanel {
|
||||
.children(has_channel_buffer_changed.then(|| {
|
||||
div()
|
||||
.w_1p5()
|
||||
.z_index(1)
|
||||
.absolute()
|
||||
.right(px(2.))
|
||||
.top(px(2.))
|
||||
@@ -1026,7 +1025,6 @@ impl CollabPanel {
|
||||
.children(has_messages_notification.then(|| {
|
||||
div()
|
||||
.w_1p5()
|
||||
.z_index(1)
|
||||
.absolute()
|
||||
.right(px(2.))
|
||||
.top(px(4.))
|
||||
@@ -1052,7 +1050,7 @@ impl CollabPanel {
|
||||
.indent_step_size(px(20.))
|
||||
.selected(is_selected)
|
||||
.on_click(cx.listener(move |_this, _, _cx| {
|
||||
// todo!()
|
||||
// todo()
|
||||
}))
|
||||
.start_slot(
|
||||
h_flex()
|
||||
@@ -1531,7 +1529,7 @@ impl CollabPanel {
|
||||
id: _id,
|
||||
name: _name,
|
||||
} => {
|
||||
// todo!()
|
||||
// todo()
|
||||
}
|
||||
|
||||
ListEntry::OutgoingRequest(_) => {}
|
||||
@@ -2614,7 +2612,6 @@ impl CollabPanel {
|
||||
.children(has_notes_notification.then(|| {
|
||||
div()
|
||||
.w_1p5()
|
||||
.z_index(1)
|
||||
.absolute()
|
||||
.right(px(-1.))
|
||||
.top(px(-1.))
|
||||
@@ -2629,49 +2626,44 @@ impl CollabPanel {
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.right(rems(0.))
|
||||
.z_index(1)
|
||||
.h_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.child(
|
||||
IconButton::new("channel_chat", IconName::MessageBubbles)
|
||||
.style(ButtonStyle::Filled)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(if has_messages_notification {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.join_channel_chat(channel_id, cx)
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
|
||||
.visible_on_hover(""),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("channel_notes", IconName::File)
|
||||
.style(ButtonStyle::Filled)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(if has_notes_notification {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.open_channel_notes(channel_id, cx)
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
|
||||
.visible_on_hover(""),
|
||||
),
|
||||
),
|
||||
h_flex().absolute().right(rems(0.)).h_full().child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.child(
|
||||
IconButton::new("channel_chat", IconName::MessageBubbles)
|
||||
.style(ButtonStyle::Filled)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(if has_messages_notification {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.join_channel_chat(channel_id, cx)
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
|
||||
.visible_on_hover(""),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("channel_notes", IconName::File)
|
||||
.style(ButtonStyle::Filled)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(if has_notes_notification {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.open_channel_notes(channel_id, cx)
|
||||
}))
|
||||
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
|
||||
.visible_on_hover(""),
|
||||
),
|
||||
),
|
||||
)
|
||||
.tooltip({
|
||||
let channel_store = self.channel_store.clone();
|
||||
@@ -2717,31 +2709,34 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
|
||||
let thickness = px(1.);
|
||||
let color = cx.theme().colors().text;
|
||||
|
||||
canvas(move |bounds, cx| {
|
||||
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
|
||||
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
|
||||
let right = bounds.right();
|
||||
let top = bounds.top();
|
||||
canvas(
|
||||
|_, _| {},
|
||||
move |bounds, _, cx| {
|
||||
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
|
||||
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
|
||||
let right = bounds.right();
|
||||
let top = bounds.top();
|
||||
|
||||
cx.paint_quad(fill(
|
||||
Bounds::from_corners(
|
||||
point(start_x, top),
|
||||
point(
|
||||
start_x + thickness,
|
||||
if is_last {
|
||||
start_y
|
||||
} else {
|
||||
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
|
||||
},
|
||||
cx.paint_quad(fill(
|
||||
Bounds::from_corners(
|
||||
point(start_x, top),
|
||||
point(
|
||||
start_x + thickness,
|
||||
if is_last {
|
||||
start_y
|
||||
} else {
|
||||
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
color,
|
||||
));
|
||||
cx.paint_quad(fill(
|
||||
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
|
||||
color,
|
||||
));
|
||||
})
|
||||
color,
|
||||
));
|
||||
cx.paint_quad(fill(
|
||||
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
|
||||
color,
|
||||
));
|
||||
},
|
||||
)
|
||||
.w(width)
|
||||
.h(line_height)
|
||||
}
|
||||
|
||||
@@ -329,24 +329,27 @@ impl Render for CollabTitlebarItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_color_ribbon(color: Hsla) -> gpui::Canvas {
|
||||
canvas(move |bounds, cx| {
|
||||
let height = bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(height.0 / 2.0);
|
||||
let mut path = Path::new(bounds.lower_left());
|
||||
path.curve_to(
|
||||
bounds.origin + point(horizontal_offset, vertical_offset),
|
||||
bounds.origin + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
|
||||
path.curve_to(
|
||||
bounds.lower_right(),
|
||||
bounds.upper_right() + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.lower_left());
|
||||
cx.paint_path(path, color);
|
||||
})
|
||||
fn render_color_ribbon(color: Hsla) -> impl Element {
|
||||
canvas(
|
||||
move |_, _| {},
|
||||
move |bounds, _, cx| {
|
||||
let height = bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(height.0 / 2.0);
|
||||
let mut path = Path::new(bounds.lower_left());
|
||||
path.curve_to(
|
||||
bounds.origin + point(horizontal_offset, vertical_offset),
|
||||
bounds.origin + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
|
||||
path.curve_to(
|
||||
bounds.lower_right(),
|
||||
bounds.upper_right() + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.lower_left());
|
||||
cx.paint_path(path, color);
|
||||
},
|
||||
)
|
||||
.h_1()
|
||||
.w_full()
|
||||
}
|
||||
|
||||
@@ -27,10 +27,7 @@ impl RenderOnce for FacePile {
|
||||
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
|
||||
let isnt_last = ix < player_count - 1;
|
||||
|
||||
div()
|
||||
.z_index((player_count - ix) as u16)
|
||||
.when(isnt_last, |div| div.neg_mr_1())
|
||||
.child(player)
|
||||
div().when(isnt_last, |div| div.neg_mr_1()).child(player)
|
||||
});
|
||||
self.base.children(player_list)
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ impl NotificationPanel {
|
||||
let panel = Self::new(workspace, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width;
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ pub struct ColorStates {
|
||||
|
||||
/// Returns a set of colors for different states of an element.
|
||||
///
|
||||
/// todo!("This should take a theme and use appropriate colors from it")
|
||||
/// todo("This should take a theme and use appropriate colors from it")
|
||||
pub fn states_for_color(color: RGBAColor, is_light: bool) -> ColorStates {
|
||||
let adjustment_factor = if is_light { 0.1 } else { -0.1 };
|
||||
let hover_adjustment = 1.0 - adjustment_factor;
|
||||
|
||||
@@ -37,6 +37,24 @@ pub struct CommandPalette {
|
||||
picker: View<Picker<CommandPaletteDelegate>>,
|
||||
}
|
||||
|
||||
fn trim_consecutive_whitespaces(input: &str) -> String {
|
||||
let mut result = String::with_capacity(input.len());
|
||||
let mut last_char_was_whitespace = false;
|
||||
|
||||
for char in input.trim().chars() {
|
||||
if char.is_whitespace() {
|
||||
if !last_char_was_whitespace {
|
||||
result.push(char);
|
||||
}
|
||||
last_char_was_whitespace = true;
|
||||
} else {
|
||||
result.push(char);
|
||||
last_char_was_whitespace = false;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
impl CommandPalette {
|
||||
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||
workspace.register_action(|workspace, _: &Toggle, cx| {
|
||||
@@ -247,7 +265,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
let mut commands = self.all_commands.clone();
|
||||
let hit_counts = cx.global::<HitCounts>().clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
let query = query.clone();
|
||||
let query = trim_consecutive_whitespaces(&query.as_str());
|
||||
async move {
|
||||
commands.sort_by_key(|action| {
|
||||
(
|
||||
@@ -265,7 +283,6 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||
char_bag: command.name.chars().collect(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let matches = if query.is_empty() {
|
||||
candidates
|
||||
.into_iter()
|
||||
@@ -468,7 +485,7 @@ mod tests {
|
||||
});
|
||||
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item(Box::new(editor.clone()), cx);
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), cx);
|
||||
editor.update(cx, |editor, cx| editor.focus(cx))
|
||||
});
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ impl ProjectDiagnosticsEditor {
|
||||
let diagnostics = cx.new_view(|cx| {
|
||||
ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
|
||||
});
|
||||
workspace.add_item(Box::new(diagnostics), cx);
|
||||
workspace.add_item_to_active_pane(Box::new(diagnostics), cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1593,20 +1593,18 @@ mod tests {
|
||||
let name: SharedString = match block {
|
||||
TransformBlock::Custom(block) => cx.with_element_context({
|
||||
|cx| -> Option<SharedString> {
|
||||
block
|
||||
.render(&mut BlockContext {
|
||||
context: cx,
|
||||
anchor_x: px(0.),
|
||||
gutter_dimensions: &GutterDimensions::default(),
|
||||
line_height: px(0.),
|
||||
em_width: px(0.),
|
||||
max_width: px(0.),
|
||||
block_id: ix,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
})
|
||||
.inner_id()?
|
||||
.try_into()
|
||||
.ok()
|
||||
let mut element = block.render(&mut BlockContext {
|
||||
context: cx,
|
||||
anchor_x: px(0.),
|
||||
gutter_dimensions: &GutterDimensions::default(),
|
||||
line_height: px(0.),
|
||||
em_width: px(0.),
|
||||
max_width: px(0.),
|
||||
block_id: ix,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
});
|
||||
let element = element.downcast_mut::<Div>().unwrap();
|
||||
element.interactivity().element_id.clone()?.try_into().ok()
|
||||
}
|
||||
})?,
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ gpui::actions!(
|
||||
NewlineBelow,
|
||||
NextScreen,
|
||||
OpenExcerpts,
|
||||
OpenExcerptsSplit,
|
||||
OpenPermalinkToLine,
|
||||
Outdent,
|
||||
PageDown,
|
||||
|
||||
@@ -28,7 +28,10 @@ use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
|
||||
pub use block_map::{BlockMap, BlockPoint};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use fold_map::FoldMap;
|
||||
use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle};
|
||||
use gpui::{
|
||||
Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
|
||||
WindowContext,
|
||||
};
|
||||
use inlay_map::InlayMap;
|
||||
use language::{
|
||||
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
|
||||
@@ -593,13 +596,13 @@ impl DisplaySnapshot {
|
||||
&self,
|
||||
display_row: u32,
|
||||
TextLayoutDetails {
|
||||
text_system,
|
||||
editor_style,
|
||||
rem_size,
|
||||
scroll_anchor: _,
|
||||
visible_rows: _,
|
||||
vertical_scroll_margin: _,
|
||||
}: &TextLayoutDetails,
|
||||
cx: &WindowContext,
|
||||
) -> Arc<LineLayout> {
|
||||
let mut runs = Vec::new();
|
||||
let mut line = String::new();
|
||||
@@ -628,7 +631,7 @@ impl DisplaySnapshot {
|
||||
}
|
||||
|
||||
let font_size = editor_style.text.font_size.to_pixels(*rem_size);
|
||||
text_system
|
||||
cx.text_system()
|
||||
.layout_line(&line, font_size, &runs)
|
||||
.expect("we expect the font to be loaded because it's rendered by the editor")
|
||||
}
|
||||
@@ -637,8 +640,9 @@ impl DisplaySnapshot {
|
||||
&self,
|
||||
display_point: DisplayPoint,
|
||||
text_layout_details: &TextLayoutDetails,
|
||||
cx: &WindowContext,
|
||||
) -> Pixels {
|
||||
let line = self.layout_row(display_point.row(), text_layout_details);
|
||||
let line = self.layout_row(display_point.row(), text_layout_details, cx);
|
||||
line.x_for_index(display_point.column() as usize)
|
||||
}
|
||||
|
||||
@@ -647,8 +651,9 @@ impl DisplaySnapshot {
|
||||
display_row: u32,
|
||||
x: Pixels,
|
||||
details: &TextLayoutDetails,
|
||||
cx: &WindowContext,
|
||||
) -> u32 {
|
||||
let layout_line = self.layout_row(display_row, details);
|
||||
let layout_line = self.layout_row(display_row, details, cx);
|
||||
layout_line.closest_index_for_x(x) as u32
|
||||
}
|
||||
|
||||
@@ -1336,7 +1341,8 @@ pub mod tests {
|
||||
DisplayPoint::new(0, 7)
|
||||
);
|
||||
|
||||
let x = snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details);
|
||||
let x =
|
||||
snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details, cx);
|
||||
assert_eq!(
|
||||
movement::up(
|
||||
&snapshot,
|
||||
|
||||
@@ -51,7 +51,7 @@ pub use display_map::DisplayPoint;
|
||||
use display_map::*;
|
||||
pub use editor_settings::EditorSettings;
|
||||
use element::LineWithInvisibles;
|
||||
pub use element::{Cursor, EditorElement, HighlightedRange, HighlightedRangeLine};
|
||||
pub use element::{CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine};
|
||||
use futures::FutureExt;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use git::diff_hunk_to_display;
|
||||
@@ -1642,7 +1642,7 @@ impl Editor {
|
||||
.update(cx, |project, cx| project.create_buffer("", None, cx))
|
||||
.log_err()
|
||||
{
|
||||
workspace.add_item(
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
|
||||
cx,
|
||||
);
|
||||
@@ -3173,7 +3173,6 @@ impl Editor {
|
||||
|
||||
pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails {
|
||||
TextLayoutDetails {
|
||||
text_system: cx.text_system().clone(),
|
||||
editor_style: self.style.clone().unwrap(),
|
||||
rem_size: cx.rem_size(),
|
||||
scroll_anchor: self.scroll_manager.anchor(),
|
||||
@@ -3639,7 +3638,7 @@ impl Editor {
|
||||
let project = workspace.project().clone();
|
||||
let editor =
|
||||
cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
|
||||
workspace.add_item(Box::new(editor.clone()), cx);
|
||||
workspace.add_item_to_active_pane(Box::new(editor.clone()), cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.highlight_background::<Self>(
|
||||
ranges_to_highlight,
|
||||
@@ -4099,7 +4098,7 @@ impl Editor {
|
||||
_line_height: Pixels,
|
||||
_gutter_margin: Pixels,
|
||||
editor_view: View<Editor>,
|
||||
) -> Vec<Option<IconButton>> {
|
||||
) -> Vec<Option<AnyElement>> {
|
||||
fold_data
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -4126,6 +4125,7 @@ impl Editor {
|
||||
.selected(fold_status == FoldStatus::Folded)
|
||||
.selected_icon(ui::IconName::ChevronRight)
|
||||
.size(ui::ButtonSize::None)
|
||||
.into_any_element()
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
@@ -5258,7 +5258,7 @@ impl Editor {
|
||||
head = display_map.clip_point(head, Bias::Right);
|
||||
let goal = SelectionGoal::HorizontalPosition(
|
||||
display_map
|
||||
.x_for_display_point(head, &text_layout_details)
|
||||
.x_for_display_point(head, &text_layout_details, cx)
|
||||
.into(),
|
||||
);
|
||||
selection.collapse_to(head, goal);
|
||||
@@ -6297,8 +6297,8 @@ impl Editor {
|
||||
let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
|
||||
let range = oldest_selection.display_range(&display_map).sorted();
|
||||
|
||||
let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
|
||||
let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
|
||||
let start_x = display_map.x_for_display_point(range.start, &text_layout_details, cx);
|
||||
let end_x = display_map.x_for_display_point(range.end, &text_layout_details, cx);
|
||||
let positions = start_x.min(end_x)..start_x.max(end_x);
|
||||
|
||||
selections.clear();
|
||||
@@ -6337,16 +6337,17 @@ impl Editor {
|
||||
let range = selection.display_range(&display_map).sorted();
|
||||
debug_assert_eq!(range.start.row(), range.end.row());
|
||||
let mut row = range.start.row();
|
||||
let positions =
|
||||
if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
|
||||
px(start)..px(end)
|
||||
} else {
|
||||
let start_x =
|
||||
display_map.x_for_display_point(range.start, &text_layout_details);
|
||||
let end_x =
|
||||
display_map.x_for_display_point(range.end, &text_layout_details);
|
||||
start_x.min(end_x)..start_x.max(end_x)
|
||||
};
|
||||
let positions = if let SelectionGoal::HorizontalRange { start, end } =
|
||||
selection.goal
|
||||
{
|
||||
px(start)..px(end)
|
||||
} else {
|
||||
let start_x =
|
||||
display_map.x_for_display_point(range.start, &text_layout_details, cx);
|
||||
let end_x =
|
||||
display_map.x_for_display_point(range.end, &text_layout_details, cx);
|
||||
start_x.min(end_x)..start_x.max(end_x)
|
||||
};
|
||||
|
||||
while row != end_row {
|
||||
if above {
|
||||
@@ -7033,7 +7034,7 @@ impl Editor {
|
||||
let display_point = point.to_display_point(display_snapshot);
|
||||
let goal = SelectionGoal::HorizontalPosition(
|
||||
display_snapshot
|
||||
.x_for_display_point(display_point, &text_layout_details)
|
||||
.x_for_display_point(display_point, &text_layout_details, cx)
|
||||
.into(),
|
||||
);
|
||||
(display_point, goal)
|
||||
@@ -7506,11 +7507,13 @@ impl Editor {
|
||||
cx.window_context().defer(move |cx| {
|
||||
let target_editor: View<Self> =
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if split {
|
||||
workspace.split_project_item(target.buffer.clone(), cx)
|
||||
let pane = if split {
|
||||
workspace.adjacent_pane(cx)
|
||||
} else {
|
||||
workspace.open_project_item(target.buffer.clone(), cx)
|
||||
}
|
||||
workspace.active_pane().clone()
|
||||
};
|
||||
|
||||
workspace.open_project_item(pane, target.buffer.clone(), cx)
|
||||
});
|
||||
target_editor.update(cx, |target_editor, cx| {
|
||||
// When selecting a definition in a different buffer, disable the nav history
|
||||
@@ -7661,12 +7664,68 @@ impl Editor {
|
||||
let workspace = self.workspace()?;
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
|
||||
Some(cx.spawn(|_, mut cx| async move {
|
||||
let locations = references.await?;
|
||||
Some(cx.spawn(|editor, mut cx| async move {
|
||||
let mut locations = references.await?;
|
||||
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
|
||||
let head_offset = text::ToOffset::to_offset(&head, &snapshot);
|
||||
|
||||
// LSP may return references that contain the item itself we requested `find_all_references` for (eg. rust-analyzer)
|
||||
// So we will remove it from locations
|
||||
// If there is only one reference, we will not do this filter cause it may make locations empty
|
||||
if locations.len() > 1 {
|
||||
cx.update(|cx| {
|
||||
locations.retain(|location| {
|
||||
// fn foo(x : i64) {
|
||||
// ^
|
||||
// println!(x);
|
||||
// }
|
||||
// It is ok to find reference when caret being at ^ (the end of the word)
|
||||
// So we turn offset into inclusive to include the end of the word
|
||||
!location
|
||||
.range
|
||||
.to_offset(location.buffer.read(cx))
|
||||
.to_inclusive()
|
||||
.contains(&head_offset)
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
if locations.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If there is one reference, just open it directly
|
||||
if locations.len() == 1 {
|
||||
let target = locations.pop().unwrap();
|
||||
|
||||
return editor.update(&mut cx, |editor, cx| {
|
||||
let range = target.range.to_offset(target.buffer.read(cx));
|
||||
let range = editor.range_for_match(&range);
|
||||
|
||||
if Some(&target.buffer) == editor.buffer().read(cx).as_singleton().as_ref() {
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
} else {
|
||||
cx.window_context().defer(move |cx| {
|
||||
let target_editor: View<Self> =
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_project_item(
|
||||
workspace.active_pane().clone(),
|
||||
target.buffer.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
target_editor.update(cx, |target_editor, cx| {
|
||||
target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let title = locations
|
||||
.first()
|
||||
@@ -7747,7 +7806,7 @@ impl Editor {
|
||||
if split {
|
||||
workspace.split_item(SplitDirection::Right, Box::new(editor), cx);
|
||||
} else {
|
||||
workspace.add_item(Box::new(editor), cx);
|
||||
workspace.add_item_to_active_pane(Box::new(editor), cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9067,7 +9126,15 @@ impl Editor {
|
||||
self.searchable
|
||||
}
|
||||
|
||||
fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
|
||||
self.open_excerpts_common(true, cx)
|
||||
}
|
||||
|
||||
fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
|
||||
self.open_excerpts_common(false, cx)
|
||||
}
|
||||
|
||||
fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
if buffer.is_singleton() {
|
||||
cx.propagate();
|
||||
@@ -9094,18 +9161,20 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
|
||||
|
||||
// We defer the pane interaction because we ourselves are a workspace item
|
||||
// and activating a new item causes the pane to call a method on us reentrantly,
|
||||
// which panics if we're on the stack.
|
||||
cx.window_context().defer(move |cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
let pane = workspace.active_pane().clone();
|
||||
let pane = if split {
|
||||
workspace.adjacent_pane(cx)
|
||||
} else {
|
||||
workspace.active_pane().clone()
|
||||
};
|
||||
pane.update(cx, |pane, _| pane.disable_history());
|
||||
|
||||
for (buffer, ranges) in new_selections_by_buffer.into_iter() {
|
||||
let editor = workspace.open_project_item::<Self>(buffer, cx);
|
||||
let editor = workspace.open_project_item::<Self>(pane.clone(), buffer, cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
|
||||
s.select_ranges(ranges);
|
||||
@@ -10069,7 +10138,7 @@ impl ViewInputHandler for Editor {
|
||||
let scroll_left = scroll_position.x * em_width;
|
||||
|
||||
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
|
||||
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
|
||||
let x = snapshot.x_for_display_point(start, &text_layout_details, cx) - scroll_left
|
||||
+ self.gutter_width;
|
||||
let y = line_height * (start.row() as f32 - scroll_position.y);
|
||||
|
||||
|
||||
@@ -8098,6 +8098,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
"Some other server name".into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: Some(json!({
|
||||
"some other init value": false
|
||||
})),
|
||||
@@ -8115,6 +8116,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
@@ -8132,6 +8134,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: Some(json!({
|
||||
"anotherInitValue": false
|
||||
})),
|
||||
@@ -8149,6 +8152,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
project_settings.lsp.insert(
|
||||
language_server_name.into(),
|
||||
LspSettings {
|
||||
settings: None,
|
||||
initialization_options: None,
|
||||
},
|
||||
);
|
||||
@@ -8426,6 +8430,105 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_find_all_references(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
document_formatting_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn foo(«paramˇ»: i64) {
|
||||
println!(param);
|
||||
}
|
||||
"});
|
||||
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::References, _, _>(move |_, _| async move {
|
||||
Ok(Some(vec![
|
||||
lsp::Location {
|
||||
uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 12)),
|
||||
},
|
||||
lsp::Location {
|
||||
uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 18)),
|
||||
},
|
||||
]))
|
||||
});
|
||||
|
||||
let references = cx
|
||||
.update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.executor().start_waiting();
|
||||
references.await.unwrap();
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn foo(param: i64) {
|
||||
println!(«paramˇ»);
|
||||
}
|
||||
"});
|
||||
|
||||
let references = cx
|
||||
.update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.executor().start_waiting();
|
||||
references.await.unwrap();
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn foo(«paramˇ»: i64) {
|
||||
println!(param);
|
||||
}
|
||||
"});
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
fn foo(param: i64) {
|
||||
let a = param;
|
||||
let aˇ = param;
|
||||
let a = param;
|
||||
println!(param);
|
||||
}
|
||||
"});
|
||||
|
||||
cx.lsp
|
||||
.handle_request::<lsp::request::References, _, _>(move |_, _| async move {
|
||||
Ok(Some(vec![lsp::Location {
|
||||
uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
|
||||
range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 9)),
|
||||
}]))
|
||||
});
|
||||
|
||||
let references = cx
|
||||
.update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.executor().start_waiting();
|
||||
references.await.unwrap();
|
||||
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn foo(param: i64) {
|
||||
let a = param;
|
||||
let «aˇ» = param;
|
||||
let a = param;
|
||||
println!(param);
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||
let point = DisplayPoint::new(row as u32, column as u32);
|
||||
point..point
|
||||
|
||||
@@ -8,6 +8,9 @@ enum GitHostingProvider {
|
||||
Github,
|
||||
Gitlab,
|
||||
Gitee,
|
||||
Bitbucket,
|
||||
Sourcehut,
|
||||
Codeberg,
|
||||
}
|
||||
|
||||
impl GitHostingProvider {
|
||||
@@ -16,6 +19,9 @@ impl GitHostingProvider {
|
||||
Self::Github => "https://github.com",
|
||||
Self::Gitlab => "https://gitlab.com",
|
||||
Self::Gitee => "https://gitee.com",
|
||||
Self::Bitbucket => "https://bitbucket.org",
|
||||
Self::Sourcehut => "https://git.sr.ht",
|
||||
Self::Codeberg => "https://codeberg.org",
|
||||
};
|
||||
|
||||
Url::parse(&base_url).unwrap()
|
||||
@@ -28,16 +34,21 @@ impl GitHostingProvider {
|
||||
let line = selection.start.row + 1;
|
||||
|
||||
match self {
|
||||
Self::Github | Self::Gitlab | Self::Gitee => format!("L{}", line),
|
||||
Self::Github | Self::Gitlab | Self::Gitee | Self::Sourcehut | Self::Codeberg => {
|
||||
format!("L{}", line)
|
||||
}
|
||||
Self::Bitbucket => format!("lines-{}", line),
|
||||
}
|
||||
} else {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
|
||||
match self {
|
||||
Self::Github => format!("L{}-L{}", start_line, end_line),
|
||||
Self::Gitlab => format!("L{}-{}", start_line, end_line),
|
||||
Self::Gitee => format!("L{}-{}", start_line, end_line),
|
||||
Self::Github | Self::Codeberg => format!("L{}-L{}", start_line, end_line),
|
||||
Self::Gitlab | Self::Gitee | Self::Sourcehut => {
|
||||
format!("L{}-{}", start_line, end_line)
|
||||
}
|
||||
Self::Bitbucket => format!("lines-{}:{}", start_line, end_line),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +80,9 @@ pub fn build_permalink(params: BuildPermalinkParams) -> Result<Url> {
|
||||
GitHostingProvider::Github => format!("{owner}/{repo}/blob/{sha}/{path}"),
|
||||
GitHostingProvider::Gitlab => format!("{owner}/{repo}/-/blob/{sha}/{path}"),
|
||||
GitHostingProvider::Gitee => format!("{owner}/{repo}/blob/{sha}/{path}"),
|
||||
GitHostingProvider::Bitbucket => format!("{owner}/{repo}/src/{sha}/{path}"),
|
||||
GitHostingProvider::Sourcehut => format!("~{owner}/{repo}/tree/{sha}/item/{path}"),
|
||||
GitHostingProvider::Codeberg => format!("{owner}/{repo}/src/commit/{sha}/{path}"),
|
||||
};
|
||||
let line_fragment = selection.map(|selection| provider.line_fragment(&selection));
|
||||
|
||||
@@ -130,6 +144,52 @@ fn parse_git_remote_url(url: &str) -> Option<ParsedGitRemote> {
|
||||
});
|
||||
}
|
||||
|
||||
if url.contains("bitbucket.org") {
|
||||
let (_, repo_with_owner) = url.trim_end_matches(".git").split_once("bitbucket.org")?;
|
||||
let (owner, repo) = repo_with_owner
|
||||
.trim_start_matches("/")
|
||||
.trim_start_matches(":")
|
||||
.split_once("/")?;
|
||||
|
||||
return Some(ParsedGitRemote {
|
||||
provider: GitHostingProvider::Bitbucket,
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
}
|
||||
|
||||
if url.starts_with("git@git.sr.ht:") || url.starts_with("https://git.sr.ht/") {
|
||||
// sourcehut indicates a repo with '.git' suffix as a separate repo.
|
||||
// For example, "git@git.sr.ht:~username/repo" and "git@git.sr.ht:~username/repo.git"
|
||||
// are two distinct repositories.
|
||||
let repo_with_owner = url
|
||||
.trim_start_matches("git@git.sr.ht:~")
|
||||
.trim_start_matches("https://git.sr.ht/~");
|
||||
|
||||
let (owner, repo) = repo_with_owner.split_once("/")?;
|
||||
|
||||
return Some(ParsedGitRemote {
|
||||
provider: GitHostingProvider::Sourcehut,
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
}
|
||||
|
||||
if url.starts_with("git@codeberg.org:") || url.starts_with("https://codeberg.org/") {
|
||||
let repo_with_owner = url
|
||||
.trim_start_matches("git@codeberg.org:")
|
||||
.trim_start_matches("https://codeberg.org/")
|
||||
.trim_end_matches(".git");
|
||||
|
||||
let (owner, repo) = repo_with_owner.split_once("/")?;
|
||||
|
||||
return Some(ParsedGitRemote {
|
||||
provider: GitHostingProvider::Codeberg,
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -387,4 +447,257 @@ mod tests {
|
||||
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/zed/src/main.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_git_remote_url_bitbucket_https_with_username() {
|
||||
let url = "https://thorstenballzed@bitbucket.org/thorstenzed/testingrepo.git";
|
||||
let parsed = parse_git_remote_url(url).unwrap();
|
||||
assert!(matches!(parsed.provider, GitHostingProvider::Bitbucket));
|
||||
assert_eq!(parsed.owner, "thorstenzed");
|
||||
assert_eq!(parsed.repo, "testingrepo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_git_remote_url_bitbucket_https_without_username() {
|
||||
let url = "https://bitbucket.org/thorstenzed/testingrepo.git";
|
||||
let parsed = parse_git_remote_url(url).unwrap();
|
||||
assert!(matches!(parsed.provider, GitHostingProvider::Bitbucket));
|
||||
assert_eq!(parsed.owner, "thorstenzed");
|
||||
assert_eq!(parsed.repo, "testingrepo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_git_remote_url_bitbucket_git() {
|
||||
let url = "git@bitbucket.org:thorstenzed/testingrepo.git";
|
||||
let parsed = parse_git_remote_url(url).unwrap();
|
||||
assert!(matches!(parsed.provider, GitHostingProvider::Bitbucket));
|
||||
assert_eq!(parsed.owner, "thorstenzed");
|
||||
assert_eq!(parsed.repo, "testingrepo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_bitbucket_permalink_from_ssh_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@bitbucket.org:thorstenzed/testingrepo.git",
|
||||
sha: "f00b4r",
|
||||
path: "main.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://bitbucket.org/thorstenzed/testingrepo/src/f00b4r/main.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_bitbucket_permalink_from_ssh_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@bitbucket.org:thorstenzed/testingrepo.git",
|
||||
sha: "f00b4r",
|
||||
path: "main.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url =
|
||||
"https://bitbucket.org/thorstenzed/testingrepo/src/f00b4r/main.rs#lines-7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_bitbucket_permalink_from_ssh_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@bitbucket.org:thorstenzed/testingrepo.git",
|
||||
sha: "f00b4r",
|
||||
path: "main.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url =
|
||||
"https://bitbucket.org/thorstenzed/testingrepo/src/f00b4r/main.rs#lines-24:48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_from_ssh_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@git.sr.ht:~rajveermalviya/zed",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_from_ssh_url_with_git_prefix() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@git.sr.ht:~rajveermalviya/zed.git",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://git.sr.ht/~rajveermalviya/zed.git/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_from_ssh_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@git.sr.ht:~rajveermalviya/zed",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_from_ssh_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@git.sr.ht:~rajveermalviya/zed",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_from_https_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://git.sr.ht/~rajveermalviya/zed",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/zed/src/main.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_from_https_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://git.sr.ht/~rajveermalviya/zed",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/zed/src/main.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_sourcehut_permalink_from_https_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://git.sr.ht/~rajveermalviya/zed",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/zed/src/main.rs#L24-48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_codeberg_permalink_from_ssh_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@codeberg.org:rajveermalviya/zed.git",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/editor/src/git/permalink.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_codeberg_permalink_from_ssh_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@codeberg.org:rajveermalviya/zed.git",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/editor/src/git/permalink.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_codeberg_permalink_from_ssh_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "git@codeberg.org:rajveermalviya/zed.git",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/editor/src/git/permalink.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/editor/src/git/permalink.rs#L24-L48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_codeberg_permalink_from_https_url() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://codeberg.org/rajveermalviya/zed.git",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/zed/src/main.rs";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_codeberg_permalink_from_https_url_single_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://codeberg.org/rajveermalviya/zed.git",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/zed/src/main.rs#L7";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_codeberg_permalink_from_https_url_multi_line_selection() {
|
||||
let permalink = build_permalink(BuildPermalinkParams {
|
||||
remote_url: "https://codeberg.org/rajveermalviya/zed.git",
|
||||
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
|
||||
path: "crates/zed/src/main.rs",
|
||||
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/zed/src/main.rs#L24-L48";
|
||||
assert_eq!(permalink.to_string(), expected_url.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
|
||||
// Highlight the selected symbol using a background highlight
|
||||
this.highlight_inlay_background::<HoverState>(
|
||||
vec![inlay_hover.range],
|
||||
|theme| theme.element_hover, // todo!("use a proper background here")
|
||||
|theme| theme.element_hover, // todo("use a proper background here")
|
||||
cx,
|
||||
);
|
||||
this.hover_state.info_popover = Some(hover_popover);
|
||||
@@ -332,7 +332,7 @@ fn show_hover(
|
||||
// Highlight the selected symbol using a background highlight
|
||||
this.highlight_background::<HoverState>(
|
||||
vec![symbol_range],
|
||||
|theme| theme.element_hover, // todo! update theme
|
||||
|theme| theme.element_hover, // todo update theme
|
||||
cx,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
|
||||
use crate::{char_kind, scroll::ScrollAnchor, CharKind, EditorStyle, ToOffset, ToPoint};
|
||||
use gpui::{px, Pixels, WindowTextSystem};
|
||||
use gpui::{px, Pixels};
|
||||
use language::Point;
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use std::ops::Range;
|
||||
|
||||
/// Defines search strategy for items in `movement` module.
|
||||
/// `FindRange::SingeLine` only looks for a match on a single line at a time, whereas
|
||||
@@ -21,7 +21,6 @@ pub enum FindRange {
|
||||
/// TextLayoutDetails encompasses everything we need to move vertically
|
||||
/// taking into account variable width characters.
|
||||
pub struct TextLayoutDetails {
|
||||
pub(crate) text_system: Arc<WindowTextSystem>,
|
||||
pub(crate) editor_style: EditorStyle,
|
||||
pub(crate) rem_size: Pixels,
|
||||
pub scroll_anchor: ScrollAnchor,
|
||||
|
||||
@@ -105,7 +105,7 @@ pub fn expand_macro_recursively(
|
||||
let buffer = cx.new_model(|cx| {
|
||||
MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
|
||||
});
|
||||
workspace.add_item(
|
||||
workspace.add_item_to_active_pane(
|
||||
Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(move |workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(move |workspace, _: &Extensions, cx| {
|
||||
let extensions_page = ExtensionsPage::new(workspace, cx);
|
||||
workspace.add_item(Box::new(extensions_page), cx)
|
||||
workspace.add_item_to_active_pane(Box::new(extensions_page), cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
@@ -55,7 +55,11 @@ impl ExtensionsPage {
|
||||
let store = ExtensionStore::global(cx);
|
||||
let subscription = cx.observe(&store, |_, _, cx| cx.notify());
|
||||
|
||||
let query_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||
let query_editor = cx.new_view(|cx| {
|
||||
let mut input = Editor::single_line(cx);
|
||||
input.set_placeholder_text("Search extensions...", cx);
|
||||
input
|
||||
});
|
||||
cx.subscribe(&query_editor, Self::on_query_change).detach();
|
||||
|
||||
let mut this = Self {
|
||||
@@ -464,88 +468,96 @@ impl Render for ExtensionsPage {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.p_4()
|
||||
.gap_4()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.child(Headline::new("Extensions").size(HeadlineSize::XLarge)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.child(h_flex().child(self.render_search(cx)))
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.p_4()
|
||||
.border_b()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.child(Headline::new("Extensions").size(HeadlineSize::XLarge)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(h_flex().child(self.render_search(cx)))
|
||||
.child(
|
||||
ToggleButton::new("filter-all", "All")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::All)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::All;
|
||||
}))
|
||||
.tooltip(move |cx| Tooltip::text("Show all extensions", cx))
|
||||
.first(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("filter-installed", "Installed")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::Installed)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::Installed;
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Show installed extensions", cx)
|
||||
})
|
||||
.middle(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("filter-not-installed", "Not Installed")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::NotInstalled)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::NotInstalled;
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Show not installed extensions", cx)
|
||||
})
|
||||
.last(),
|
||||
h_flex()
|
||||
.child(
|
||||
ToggleButton::new("filter-all", "All")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::All)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::All;
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Show all extensions", cx)
|
||||
})
|
||||
.first(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("filter-installed", "Installed")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::Installed)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::Installed;
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Show installed extensions", cx)
|
||||
})
|
||||
.middle(),
|
||||
)
|
||||
.child(
|
||||
ToggleButton::new("filter-not-installed", "Not Installed")
|
||||
.style(ButtonStyle::Filled)
|
||||
.size(ButtonSize::Large)
|
||||
.selected(self.filter == ExtensionFilter::NotInstalled)
|
||||
.on_click(cx.listener(|this, _event, _cx| {
|
||||
this.filter = ExtensionFilter::NotInstalled;
|
||||
}))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text("Show not installed extensions", cx)
|
||||
})
|
||||
.last(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(v_flex().size_full().overflow_y_hidden().map(|this| {
|
||||
.child(v_flex().px_4().size_full().overflow_y_hidden().map(|this| {
|
||||
let entries = self.filtered_extension_entries(cx);
|
||||
if entries.is_empty() {
|
||||
return this.child(self.render_empty_state(cx));
|
||||
return this.py_4().child(self.render_empty_state(cx));
|
||||
}
|
||||
|
||||
let view = cx.view().clone();
|
||||
let scroll_handle = self.list.clone();
|
||||
let item_count = entries.len();
|
||||
this.child(
|
||||
canvas({
|
||||
let view = cx.view().clone();
|
||||
let scroll_handle = self.list.clone();
|
||||
let item_count = entries.len();
|
||||
canvas(
|
||||
move |bounds, cx| {
|
||||
uniform_list::<_, Div, _>(
|
||||
let mut list = uniform_list::<_, Div, _>(
|
||||
view,
|
||||
"entries",
|
||||
item_count,
|
||||
Self::render_extensions,
|
||||
)
|
||||
.size_full()
|
||||
.pb_4()
|
||||
.track_scroll(scroll_handle)
|
||||
.into_any_element()
|
||||
.draw(
|
||||
bounds.origin,
|
||||
bounds.size.map(AvailableSpace::Definite),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.into_any_element();
|
||||
list.layout(bounds.origin, bounds.size.into(), cx);
|
||||
list
|
||||
},
|
||||
|_bounds, mut list, cx| list.paint(cx),
|
||||
)
|
||||
.size_full(),
|
||||
)
|
||||
}))
|
||||
|
||||
@@ -365,14 +365,7 @@ impl FileFinderDelegate {
|
||||
history_items: Vec<FoundPath>,
|
||||
cx: &mut ViewContext<FileFinder>,
|
||||
) -> Self {
|
||||
cx.observe(&project, |file_finder, _, cx| {
|
||||
//todo We should probably not re-render on every project anything
|
||||
file_finder
|
||||
.picker
|
||||
.update(cx, |picker, cx| picker.refresh(cx))
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self::subscribe_to_updates(&project, cx);
|
||||
Self {
|
||||
file_finder,
|
||||
workspace,
|
||||
@@ -389,6 +382,20 @@ impl FileFinderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
fn subscribe_to_updates(project: &Model<Project>, cx: &mut ViewContext<FileFinder>) {
|
||||
cx.subscribe(project, |file_finder, _, event, cx| {
|
||||
match event {
|
||||
project::Event::WorktreeUpdatedEntries(_, _)
|
||||
| project::Event::WorktreeAdded
|
||||
| project::Event::WorktreeRemoved(_) => file_finder
|
||||
.picker
|
||||
.update(cx, |picker, cx| picker.refresh(cx)),
|
||||
_ => {}
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn spawn_search(
|
||||
&mut self,
|
||||
query: PathLikeWithPosition<FileSearchQuery>,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::{assert_eq, path::Path, time::Duration};
|
||||
use std::{assert_eq, future::IntoFuture, path::Path, time::Duration};
|
||||
|
||||
use super::*;
|
||||
use editor::Editor;
|
||||
use gpui::{Entity, TestAppContext, VisualTestContext};
|
||||
use menu::{Confirm, SelectNext};
|
||||
use project::worktree::FS_WATCH_LATENCY;
|
||||
use serde_json::json;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
@@ -1337,6 +1338,137 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
"/src",
|
||||
json!({
|
||||
"lib.rs": "// Lib file",
|
||||
"main.rs": "// Bar file",
|
||||
"read.me": "// Readme file",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
// Initial state
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
cx.simulate_input("rs");
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 2);
|
||||
assert_match_at_position(finder, 0, "lib.rs");
|
||||
assert_match_at_position(finder, 1, "main.rs");
|
||||
});
|
||||
|
||||
// Delete main.rs
|
||||
app_state
|
||||
.fs
|
||||
.remove_file("/src/main.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.expect("unable to remove file");
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// main.rs is in not among search results anymore
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 1);
|
||||
assert_match_at_position(finder, 0, "lib.rs");
|
||||
});
|
||||
|
||||
// Create util.rs
|
||||
app_state
|
||||
.fs
|
||||
.create_file("/src/util.rs".as_ref(), Default::default())
|
||||
.await
|
||||
.expect("unable to create file");
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// util.rs is among search results
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 2);
|
||||
assert_match_at_position(finder, 0, "lib.rs");
|
||||
assert_match_at_position(finder, 1, "util.rs");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
"/test",
|
||||
json!({
|
||||
"project_1": {
|
||||
"bar.rs": "// Bar file",
|
||||
"lib.rs": "// Lib file",
|
||||
},
|
||||
"project_2": {
|
||||
"Cargo.toml": "// Cargo file",
|
||||
"main.rs": "// Main file",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let worktree_1_id = project.update(cx, |project, cx| {
|
||||
let worktree = project.worktrees().last().expect("worktree not found");
|
||||
worktree.read(cx).id()
|
||||
});
|
||||
|
||||
// Initial state
|
||||
let picker = open_file_picker(&workspace, cx);
|
||||
cx.simulate_input("rs");
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 2);
|
||||
assert_match_at_position(finder, 0, "bar.rs");
|
||||
assert_match_at_position(finder, 1, "lib.rs");
|
||||
});
|
||||
|
||||
// Add new worktree
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
.find_or_create_local_worktree("/test/project_2", true, cx)
|
||||
.into_future()
|
||||
})
|
||||
.await
|
||||
.expect("unable to create workdir");
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// main.rs is among search results
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 3);
|
||||
assert_match_at_position(finder, 0, "bar.rs");
|
||||
assert_match_at_position(finder, 1, "lib.rs");
|
||||
assert_match_at_position(finder, 2, "main.rs");
|
||||
});
|
||||
|
||||
// Remove the first worktree
|
||||
project.update(cx, |project, cx| {
|
||||
project.remove_worktree(worktree_1_id, cx);
|
||||
});
|
||||
cx.executor().advance_clock(FS_WATCH_LATENCY);
|
||||
|
||||
// Files from the first worktree are not in the search results anymore
|
||||
picker.update(cx, |finder, _| {
|
||||
assert_eq!(finder.delegate.matches.len(), 1);
|
||||
assert_match_at_position(finder, 0, "main.rs");
|
||||
});
|
||||
}
|
||||
|
||||
async fn open_close_queried_buffer(
|
||||
input: &str,
|
||||
expected_matches: usize,
|
||||
|
||||
@@ -1343,7 +1343,7 @@ pub fn copy_recursive<'a>(
|
||||
.boxed()
|
||||
}
|
||||
|
||||
// todo!(windows)
|
||||
// todo(windows)
|
||||
// can we get file id not open the file twice?
|
||||
// https://github.com/rust-lang/rust/issues/63010
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -96,13 +96,15 @@ objc = "0.2"
|
||||
flume = "0.11"
|
||||
open = "5.0.1"
|
||||
ashpd = "0.7.0"
|
||||
# todo!(linux) - Technically do not use `randr`, but it doesn't compile otherwise
|
||||
# todo(linux) - Technically do not use `randr`, but it doesn't compile otherwise
|
||||
xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr", "xkb"] }
|
||||
wayland-client= { version = "0.31.2" }
|
||||
wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] }
|
||||
wayland-backend = { version = "0.3.3", features = ["client_system"] }
|
||||
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
|
||||
as-raw-xcb-connection = "1"
|
||||
calloop = "0.12.4"
|
||||
calloop-wayland-source = "0.2.0"
|
||||
#TODO: use these on all platforms
|
||||
blade-graphics.workspace = true
|
||||
blade-macros.workspace = true
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
|
||||
ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Modifiers,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
|
||||
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Empty, Entity,
|
||||
EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
|
||||
TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
@@ -177,7 +177,7 @@ impl TestAppContext {
|
||||
/// Adds a new window with no content.
|
||||
pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
|
||||
let mut cx = self.app.borrow_mut();
|
||||
let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| ()));
|
||||
let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| Empty));
|
||||
drop(cx);
|
||||
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
|
||||
cx.run_until_parked();
|
||||
@@ -674,17 +674,10 @@ impl<'a> VisualTestContext {
|
||||
f: impl FnOnce(&mut WindowContext) -> AnyElement,
|
||||
) {
|
||||
self.update(|cx| {
|
||||
let entity_id = cx
|
||||
.window
|
||||
.root_view
|
||||
.as_ref()
|
||||
.expect("Can't draw to this window without a root view")
|
||||
.entity_id();
|
||||
|
||||
cx.with_element_context(|cx| {
|
||||
cx.with_view_id(entity_id, |cx| {
|
||||
f(cx).draw(origin, space, cx);
|
||||
})
|
||||
let mut element = f(cx);
|
||||
element.layout(origin, space, cx);
|
||||
element.paint(cx);
|
||||
});
|
||||
|
||||
cx.refresh();
|
||||
|
||||
365
crates/gpui/src/bounds_tree.rs
Normal file
@@ -0,0 +1,365 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::{Bounds, Half, Point};
|
||||
use std::{
|
||||
cmp,
|
||||
fmt::Debug,
|
||||
ops::{Add, Sub},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BoundsTree<U, T>
|
||||
where
|
||||
U: Default + Clone + Debug,
|
||||
T: Clone + Debug,
|
||||
{
|
||||
root: Option<usize>,
|
||||
nodes: Vec<Node<U, T>>,
|
||||
stack: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<U, T> BoundsTree<U, T>
|
||||
where
|
||||
U: Clone + Debug + PartialOrd + Add<U, Output = U> + Sub<Output = U> + Half + Default,
|
||||
T: Clone + Debug,
|
||||
{
|
||||
pub fn clear(&mut self) {
|
||||
self.root = None;
|
||||
self.nodes.clear();
|
||||
self.stack.clear();
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, new_bounds: Bounds<U>, payload: T) -> u32 {
|
||||
// If the tree is empty, make the root the new leaf.
|
||||
if self.root.is_none() {
|
||||
let new_node = self.push_leaf(new_bounds, payload, 1);
|
||||
self.root = Some(new_node);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Search for the best place to add the new leaf based on heuristics.
|
||||
let mut max_intersecting_ordering = 0;
|
||||
let mut index = self.root.unwrap();
|
||||
while let Node::Internal {
|
||||
left,
|
||||
right,
|
||||
bounds: node_bounds,
|
||||
..
|
||||
} = &mut self.nodes[index]
|
||||
{
|
||||
let left = *left;
|
||||
let right = *right;
|
||||
*node_bounds = node_bounds.union(&new_bounds);
|
||||
self.stack.push(index);
|
||||
|
||||
// Descend to the best-fit child, based on which one would increase
|
||||
// the surface area the least. This attempts to keep the tree balanced
|
||||
// in terms of surface area. If there is an intersection with the other child,
|
||||
// add its keys to the intersections vector.
|
||||
let left_cost = new_bounds
|
||||
.union(&self.nodes[left].bounds())
|
||||
.half_perimeter();
|
||||
let right_cost = new_bounds
|
||||
.union(&self.nodes[right].bounds())
|
||||
.half_perimeter();
|
||||
if left_cost < right_cost {
|
||||
max_intersecting_ordering =
|
||||
self.find_max_ordering(right, &new_bounds, max_intersecting_ordering);
|
||||
index = left;
|
||||
} else {
|
||||
max_intersecting_ordering =
|
||||
self.find_max_ordering(left, &new_bounds, max_intersecting_ordering);
|
||||
index = right;
|
||||
}
|
||||
}
|
||||
|
||||
// We've found a leaf ('index' now refers to a leaf node).
|
||||
// We'll insert a new parent node above the leaf and attach our new leaf to it.
|
||||
let sibling = index;
|
||||
|
||||
// Check for collision with the located leaf node
|
||||
let Node::Leaf {
|
||||
bounds: sibling_bounds,
|
||||
order: sibling_ordering,
|
||||
..
|
||||
} = &self.nodes[index]
|
||||
else {
|
||||
unreachable!();
|
||||
};
|
||||
if sibling_bounds.intersects(&new_bounds) {
|
||||
max_intersecting_ordering = cmp::max(max_intersecting_ordering, *sibling_ordering);
|
||||
}
|
||||
|
||||
let ordering = max_intersecting_ordering + 1;
|
||||
let new_node = self.push_leaf(new_bounds, payload, ordering);
|
||||
let new_parent = self.push_internal(sibling, new_node);
|
||||
|
||||
// If there was an old parent, we need to update its children indices.
|
||||
if let Some(old_parent) = self.stack.last().copied() {
|
||||
let Node::Internal { left, right, .. } = &mut self.nodes[old_parent] else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
if *left == sibling {
|
||||
*left = new_parent;
|
||||
} else {
|
||||
*right = new_parent;
|
||||
}
|
||||
} else {
|
||||
// If the old parent was the root, the new parent is the new root.
|
||||
self.root = Some(new_parent);
|
||||
}
|
||||
|
||||
for node_index in self.stack.drain(..) {
|
||||
let Node::Internal {
|
||||
max_order: max_ordering,
|
||||
..
|
||||
} = &mut self.nodes[node_index]
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
*max_ordering = cmp::max(*max_ordering, ordering);
|
||||
}
|
||||
|
||||
ordering
|
||||
}
|
||||
|
||||
/// Finds all nodes whose bounds contain the given point and pushes their (bounds, payload) pairs onto the result vector.
|
||||
pub(crate) fn find_containing(
|
||||
&mut self,
|
||||
point: &Point<U>,
|
||||
result: &mut Vec<BoundsSearchResult<U, T>>,
|
||||
) {
|
||||
if let Some(mut index) = self.root {
|
||||
self.stack.clear();
|
||||
self.stack.push(index);
|
||||
|
||||
while let Some(current_index) = self.stack.pop() {
|
||||
match &self.nodes[current_index] {
|
||||
Node::Leaf {
|
||||
bounds,
|
||||
order,
|
||||
data,
|
||||
} => {
|
||||
if bounds.contains(point) {
|
||||
result.push(BoundsSearchResult {
|
||||
bounds: bounds.clone(),
|
||||
order: *order,
|
||||
data: data.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Node::Internal {
|
||||
left,
|
||||
right,
|
||||
bounds,
|
||||
..
|
||||
} => {
|
||||
if bounds.contains(point) {
|
||||
self.stack.push(*left);
|
||||
self.stack.push(*right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_max_ordering(&self, index: usize, bounds: &Bounds<U>, mut max_ordering: u32) -> u32 {
|
||||
match {
|
||||
let this = &self;
|
||||
&this.nodes[index]
|
||||
} {
|
||||
Node::Leaf {
|
||||
bounds: node_bounds,
|
||||
order: ordering,
|
||||
..
|
||||
} => {
|
||||
if bounds.intersects(node_bounds) {
|
||||
max_ordering = cmp::max(*ordering, max_ordering);
|
||||
}
|
||||
}
|
||||
Node::Internal {
|
||||
left,
|
||||
right,
|
||||
bounds: node_bounds,
|
||||
max_order: node_max_ordering,
|
||||
..
|
||||
} => {
|
||||
if bounds.intersects(node_bounds) && max_ordering < *node_max_ordering {
|
||||
let left_max_ordering = self.nodes[*left].max_ordering();
|
||||
let right_max_ordering = self.nodes[*right].max_ordering();
|
||||
if left_max_ordering > right_max_ordering {
|
||||
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
|
||||
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
|
||||
} else {
|
||||
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
|
||||
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
max_ordering
|
||||
}
|
||||
|
||||
fn push_leaf(&mut self, bounds: Bounds<U>, payload: T, order: u32) -> usize {
|
||||
self.nodes.push(Node::Leaf {
|
||||
bounds,
|
||||
data: payload,
|
||||
order,
|
||||
});
|
||||
self.nodes.len() - 1
|
||||
}
|
||||
|
||||
fn push_internal(&mut self, left: usize, right: usize) -> usize {
|
||||
let left_node = &self.nodes[left];
|
||||
let right_node = &self.nodes[right];
|
||||
let new_bounds = left_node.bounds().union(right_node.bounds());
|
||||
let max_ordering = cmp::max(left_node.max_ordering(), right_node.max_ordering());
|
||||
self.nodes.push(Node::Internal {
|
||||
bounds: new_bounds,
|
||||
left,
|
||||
right,
|
||||
max_order: max_ordering,
|
||||
});
|
||||
self.nodes.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
impl<U, T> Default for BoundsTree<U, T>
|
||||
where
|
||||
U: Default + Clone + Debug,
|
||||
T: Clone + Debug,
|
||||
{
|
||||
fn default() -> Self {
|
||||
BoundsTree {
|
||||
root: None,
|
||||
nodes: Vec::new(),
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Node<U, T>
|
||||
where
|
||||
U: Clone + Default + Debug,
|
||||
T: Clone + Debug,
|
||||
{
|
||||
Leaf {
|
||||
bounds: Bounds<U>,
|
||||
order: u32,
|
||||
data: T,
|
||||
},
|
||||
Internal {
|
||||
left: usize,
|
||||
right: usize,
|
||||
bounds: Bounds<U>,
|
||||
max_order: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl<U, T> Node<U, T>
|
||||
where
|
||||
U: Clone + Default + Debug,
|
||||
T: Clone + Debug,
|
||||
{
|
||||
fn bounds(&self) -> &Bounds<U> {
|
||||
match self {
|
||||
Node::Leaf { bounds, .. } => bounds,
|
||||
Node::Internal { bounds, .. } => bounds,
|
||||
}
|
||||
}
|
||||
|
||||
fn max_ordering(&self) -> u32 {
|
||||
match self {
|
||||
Node::Leaf {
|
||||
order: ordering, ..
|
||||
} => *ordering,
|
||||
Node::Internal {
|
||||
max_order: max_ordering,
|
||||
..
|
||||
} => *max_ordering,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BoundsSearchResult<U: Clone + Default + Debug, T> {
|
||||
pub bounds: Bounds<U>,
|
||||
pub order: u32,
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Bounds, Point, Size};
|
||||
|
||||
#[test]
|
||||
fn test_insert_and_find_containing() {
|
||||
let mut tree = BoundsTree::<f32, String>::default();
|
||||
let bounds1 = Bounds {
|
||||
origin: Point { x: 0.0, y: 0.0 },
|
||||
size: Size {
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
},
|
||||
};
|
||||
let bounds2 = Bounds {
|
||||
origin: Point { x: 5.0, y: 5.0 },
|
||||
size: Size {
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
},
|
||||
};
|
||||
let bounds3 = Bounds {
|
||||
origin: Point { x: 10.0, y: 10.0 },
|
||||
size: Size {
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
},
|
||||
};
|
||||
|
||||
// Insert bounds into the tree
|
||||
tree.insert(bounds1.clone(), "Payload 1".to_string());
|
||||
tree.insert(bounds2.clone(), "Payload 2".to_string());
|
||||
tree.insert(bounds3.clone(), "Payload 3".to_string());
|
||||
|
||||
// Points for testing
|
||||
let point_inside_bounds1 = Point { x: 1.0, y: 1.0 };
|
||||
let point_inside_bounds1_and_2 = Point { x: 6.0, y: 6.0 };
|
||||
let point_inside_bounds2_and_3 = Point { x: 12.0, y: 12.0 };
|
||||
let point_outside_all_bounds = Point { x: 21.0, y: 21.0 };
|
||||
|
||||
assert!(!bounds1.contains(&point_inside_bounds2_and_3));
|
||||
assert!(!bounds1.contains(&point_outside_all_bounds));
|
||||
assert!(bounds2.contains(&point_inside_bounds1_and_2));
|
||||
assert!(bounds2.contains(&point_inside_bounds2_and_3));
|
||||
assert!(!bounds2.contains(&point_outside_all_bounds));
|
||||
assert!(!bounds3.contains(&point_inside_bounds1));
|
||||
assert!(bounds3.contains(&point_inside_bounds2_and_3));
|
||||
assert!(!bounds3.contains(&point_outside_all_bounds));
|
||||
|
||||
// Test find_containing for different points
|
||||
let mut result = Vec::new();
|
||||
tree.find_containing(&point_inside_bounds1, &mut result);
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0].data, "Payload 1");
|
||||
|
||||
result.clear();
|
||||
tree.find_containing(&point_inside_bounds1_and_2, &mut result);
|
||||
assert_eq!(result.len(), 2);
|
||||
assert!(result.iter().any(|r| r.data == "Payload 1"));
|
||||
assert!(result.iter().any(|r| r.data == "Payload 2"));
|
||||
|
||||
result.clear();
|
||||
tree.find_containing(&point_inside_bounds2_and_3, &mut result);
|
||||
assert_eq!(result.len(), 2);
|
||||
assert!(result.iter().any(|r| r.data == "Payload 2"));
|
||||
assert!(result.iter().any(|r| r.data == "Payload 3"));
|
||||
|
||||
result.clear();
|
||||
tree.find_containing(&point_outside_all_bounds, &mut result);
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,6 @@
|
||||
//!
|
||||
//! But some state is too simple and voluminous to store in every view that needs it, e.g.
|
||||
//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type.
|
||||
//! If an element returns an [`ElementId`] from [`IntoElement::element_id()`], and that element id
|
||||
//! appears in the same place relative to other views and ElementIds in the frame, then the previous
|
||||
//! frame's state will be passed to the element's layout and paint methods.
|
||||
//!
|
||||
//! # Implementing your own elements
|
||||
//!
|
||||
@@ -35,33 +32,48 @@
|
||||
//! your own custom layout algorithm or rendering a code editor.
|
||||
|
||||
use crate::{
|
||||
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
|
||||
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
|
||||
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext,
|
||||
ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
use std::{any::Any, fmt::Debug, ops::DerefMut};
|
||||
use std::{any::Any, fmt::Debug, mem, ops::DerefMut};
|
||||
|
||||
/// Implemented by types that participate in laying out and painting the contents of a window.
|
||||
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
||||
/// You can create custom elements by implementing this trait, see the module-level documentation
|
||||
/// for more details.
|
||||
pub trait Element: 'static + IntoElement {
|
||||
/// The type of state to store for this element between frames. See the module-level documentation
|
||||
/// for details.
|
||||
type State: 'static;
|
||||
/// The type of state returned from [`Element::before_layout`]. A mutable reference to this state is subsequently
|
||||
/// provided to [`Element::after_layout`] and [`Element::paint`].
|
||||
type BeforeLayout: 'static;
|
||||
|
||||
/// The type of state returned from [`Element::after_layout`]. A mutable reference to this state is subsequently
|
||||
/// provided to [`Element::paint`].
|
||||
type AfterLayout: 'static;
|
||||
|
||||
/// Before an element can be painted, we need to know where it's going to be and how big it is.
|
||||
/// Use this method to request a layout from Taffy and initialize the element's state.
|
||||
fn request_layout(
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout);
|
||||
|
||||
/// After laying out an element, we need to commit its bounds to the current frame for hitbox
|
||||
/// purposes. The state argument is the same state that was returned from [`Element::before_layout()`].
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State);
|
||||
) -> Self::AfterLayout;
|
||||
|
||||
/// Once layout has been completed, this method will be called to paint the element to the screen.
|
||||
/// The state argument is the same state that was returned from [`Element::request_layout()`].
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext);
|
||||
/// The state argument is the same state that was returned from [`Element::before_layout()`].
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
after_layout: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
);
|
||||
|
||||
/// Convert this element into a dynamically-typed [`AnyElement`].
|
||||
fn into_any(self) -> AnyElement {
|
||||
@@ -75,10 +87,6 @@ pub trait IntoElement: Sized {
|
||||
/// Useful for converting other types into elements automatically, like Strings
|
||||
type Element: Element;
|
||||
|
||||
/// The [`ElementId`] of self once converted into an [`Element`].
|
||||
/// If present, the resulting element's state will be carried across frames.
|
||||
fn element_id(&self) -> Option<ElementId>;
|
||||
|
||||
/// Convert self into a type that implements [`Element`].
|
||||
fn into_element(self) -> Self::Element;
|
||||
|
||||
@@ -86,41 +94,6 @@ pub trait IntoElement: Sized {
|
||||
fn into_any_element(self) -> AnyElement {
|
||||
self.into_element().into_any()
|
||||
}
|
||||
|
||||
/// Convert into an element, then draw in the current window at the given origin.
|
||||
/// The available space argument is provided to the layout engine to determine the size of the
|
||||
// root element. Once the element is drawn, its associated element state is yielded to the
|
||||
// given callback.
|
||||
fn draw_and_update_state<T, R>(
|
||||
self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<T>,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut ElementContext) -> R,
|
||||
) -> R
|
||||
where
|
||||
T: Clone + Default + Debug + Into<AvailableSpace>,
|
||||
{
|
||||
let element = self.into_element();
|
||||
let element_id = element.element_id();
|
||||
let element = DrawableElement {
|
||||
element: Some(element),
|
||||
phase: ElementDrawPhase::Start,
|
||||
};
|
||||
|
||||
let frame_state =
|
||||
DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
|
||||
|
||||
if let Some(mut frame_state) = frame_state {
|
||||
f(&mut frame_state, cx)
|
||||
} else {
|
||||
cx.with_element_state(element_id.unwrap(), |element_state, cx| {
|
||||
let mut element_state = element_state.unwrap();
|
||||
let result = f(&mut element_state, cx);
|
||||
(result, element_state)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoElement> FluentBuilder for T {}
|
||||
@@ -132,8 +105,10 @@ pub trait Render: 'static + Sized {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
|
||||
}
|
||||
|
||||
impl Render for () {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {}
|
||||
impl Render for Empty {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
Empty
|
||||
}
|
||||
}
|
||||
|
||||
/// You can derive [`IntoElement`] on any type that implements this trait.
|
||||
@@ -186,24 +161,36 @@ impl<C: RenderOnce> Component<C> {
|
||||
}
|
||||
|
||||
impl<C: RenderOnce> Element for Component<C> {
|
||||
type State = AnyElement;
|
||||
type BeforeLayout = AnyElement;
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let mut element = self
|
||||
.0
|
||||
.take()
|
||||
.unwrap()
|
||||
.render(cx.deref_mut())
|
||||
.into_any_element();
|
||||
let layout_id = element.request_layout(cx);
|
||||
let layout_id = element.before_layout(cx);
|
||||
(layout_id, element)
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut AnyElement,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
element.after_layout(cx);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
element.paint(cx)
|
||||
}
|
||||
}
|
||||
@@ -211,10 +198,6 @@ impl<C: RenderOnce> Element for Component<C> {
|
||||
impl<C: RenderOnce> IntoElement for Component<C> {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -225,9 +208,11 @@ impl<C: RenderOnce> IntoElement for Component<C> {
|
||||
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
||||
|
||||
trait ElementObject {
|
||||
fn element_id(&self) -> Option<ElementId>;
|
||||
fn inner_element(&mut self) -> &mut dyn Any;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
|
||||
|
||||
fn after_layout(&mut self, cx: &mut ElementContext);
|
||||
|
||||
fn paint(&mut self, cx: &mut ElementContext);
|
||||
|
||||
@@ -236,110 +221,103 @@ trait ElementObject {
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) -> Size<Pixels>;
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
);
|
||||
}
|
||||
|
||||
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
||||
pub(crate) struct DrawableElement<E: Element> {
|
||||
element: Option<E>,
|
||||
phase: ElementDrawPhase<E::State>,
|
||||
pub struct Drawable<E: Element> {
|
||||
/// The drawn element.
|
||||
pub element: E,
|
||||
phase: ElementDrawPhase<E::BeforeLayout, E::AfterLayout>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum ElementDrawPhase<S> {
|
||||
enum ElementDrawPhase<BeforeLayout, AfterLayout> {
|
||||
#[default]
|
||||
Start,
|
||||
LayoutRequested {
|
||||
BeforeLayout {
|
||||
layout_id: LayoutId,
|
||||
frame_state: Option<S>,
|
||||
before_layout: BeforeLayout,
|
||||
},
|
||||
LayoutComputed {
|
||||
layout_id: LayoutId,
|
||||
available_space: Size<AvailableSpace>,
|
||||
frame_state: Option<S>,
|
||||
before_layout: BeforeLayout,
|
||||
},
|
||||
AfterLayout {
|
||||
node_id: DispatchNodeId,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: BeforeLayout,
|
||||
after_layout: AfterLayout,
|
||||
},
|
||||
Painted,
|
||||
}
|
||||
|
||||
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
||||
impl<E: Element> DrawableElement<E> {
|
||||
impl<E: Element> Drawable<E> {
|
||||
fn new(element: E) -> Self {
|
||||
DrawableElement {
|
||||
element: Some(element),
|
||||
Drawable {
|
||||
element,
|
||||
phase: ElementDrawPhase::Start,
|
||||
}
|
||||
}
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
self.element.as_ref()?.element_id()
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
match mem::take(&mut self.phase) {
|
||||
ElementDrawPhase::Start => {
|
||||
let (layout_id, before_layout) = self.element.before_layout(cx);
|
||||
self.phase = ElementDrawPhase::BeforeLayout {
|
||||
layout_id,
|
||||
before_layout,
|
||||
};
|
||||
layout_id
|
||||
}
|
||||
_ => panic!("must call before_layout only once"),
|
||||
}
|
||||
}
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
|
||||
{
|
||||
let layout_id = cx.with_element_state(id, |element_state, cx| {
|
||||
self.element
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.request_layout(element_state, cx)
|
||||
});
|
||||
(layout_id, None)
|
||||
} else {
|
||||
let (layout_id, frame_state) = self.element.as_mut().unwrap().request_layout(None, cx);
|
||||
(layout_id, Some(frame_state))
|
||||
};
|
||||
|
||||
self.phase = ElementDrawPhase::LayoutRequested {
|
||||
layout_id,
|
||||
frame_state,
|
||||
};
|
||||
layout_id
|
||||
}
|
||||
|
||||
fn paint(mut self, cx: &mut ElementContext) -> Option<E::State> {
|
||||
match self.phase {
|
||||
ElementDrawPhase::LayoutRequested {
|
||||
fn after_layout(&mut self, cx: &mut ElementContext) {
|
||||
match mem::take(&mut self.phase) {
|
||||
ElementDrawPhase::BeforeLayout {
|
||||
layout_id,
|
||||
frame_state,
|
||||
mut before_layout,
|
||||
}
|
||||
| ElementDrawPhase::LayoutComputed {
|
||||
layout_id,
|
||||
frame_state,
|
||||
mut before_layout,
|
||||
..
|
||||
} => {
|
||||
let bounds = cx.layout_bounds(layout_id);
|
||||
|
||||
if let Some(mut frame_state) = frame_state {
|
||||
self.element
|
||||
.take()
|
||||
.unwrap()
|
||||
.paint(bounds, &mut frame_state, cx);
|
||||
Some(frame_state)
|
||||
} else {
|
||||
let element_id = self
|
||||
.element
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.element_id()
|
||||
.expect("if we don't have frame state, we should have element state");
|
||||
cx.with_element_state(element_id, |element_state, cx| {
|
||||
let mut element_state = element_state.unwrap();
|
||||
self.element
|
||||
.take()
|
||||
.unwrap()
|
||||
.paint(bounds, &mut element_state, cx);
|
||||
((), element_state)
|
||||
});
|
||||
None
|
||||
}
|
||||
cx.layout_element(|node_id, cx| {
|
||||
let after_layout = self.element.after_layout(bounds, &mut before_layout, cx);
|
||||
self.phase = ElementDrawPhase::AfterLayout {
|
||||
node_id,
|
||||
bounds,
|
||||
before_layout,
|
||||
after_layout,
|
||||
};
|
||||
});
|
||||
}
|
||||
_ => panic!("must call before_layout before after_layout"),
|
||||
}
|
||||
}
|
||||
|
||||
_ => panic!("must call layout before paint"),
|
||||
fn paint(&mut self, cx: &mut ElementContext) -> E::BeforeLayout {
|
||||
match mem::take(&mut self.phase) {
|
||||
ElementDrawPhase::AfterLayout {
|
||||
node_id,
|
||||
bounds,
|
||||
mut before_layout,
|
||||
mut after_layout,
|
||||
..
|
||||
} => {
|
||||
cx.paint_element(node_id, |cx| {
|
||||
self.element
|
||||
.paint(bounds, &mut before_layout, &mut after_layout, cx);
|
||||
});
|
||||
self.phase = ElementDrawPhase::Painted;
|
||||
before_layout
|
||||
}
|
||||
_ => panic!("must call after_layout before paint"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,66 +327,63 @@ impl<E: Element> DrawableElement<E> {
|
||||
cx: &mut ElementContext,
|
||||
) -> Size<Pixels> {
|
||||
if matches!(&self.phase, ElementDrawPhase::Start) {
|
||||
self.request_layout(cx);
|
||||
self.before_layout(cx);
|
||||
}
|
||||
|
||||
let layout_id = match &mut self.phase {
|
||||
ElementDrawPhase::LayoutRequested {
|
||||
let layout_id = match mem::take(&mut self.phase) {
|
||||
ElementDrawPhase::BeforeLayout {
|
||||
layout_id,
|
||||
frame_state,
|
||||
before_layout,
|
||||
} => {
|
||||
cx.compute_layout(*layout_id, available_space);
|
||||
let layout_id = *layout_id;
|
||||
cx.compute_layout(layout_id, available_space);
|
||||
self.phase = ElementDrawPhase::LayoutComputed {
|
||||
layout_id,
|
||||
available_space,
|
||||
frame_state: frame_state.take(),
|
||||
before_layout,
|
||||
};
|
||||
layout_id
|
||||
}
|
||||
ElementDrawPhase::LayoutComputed {
|
||||
layout_id,
|
||||
available_space: prev_available_space,
|
||||
..
|
||||
before_layout,
|
||||
} => {
|
||||
if available_space != *prev_available_space {
|
||||
cx.compute_layout(*layout_id, available_space);
|
||||
*prev_available_space = available_space;
|
||||
if available_space != prev_available_space {
|
||||
cx.compute_layout(layout_id, available_space);
|
||||
}
|
||||
*layout_id
|
||||
self.phase = ElementDrawPhase::LayoutComputed {
|
||||
layout_id,
|
||||
available_space,
|
||||
before_layout,
|
||||
};
|
||||
layout_id
|
||||
}
|
||||
_ => panic!("cannot measure after painting"),
|
||||
};
|
||||
|
||||
cx.layout_bounds(layout_id).size
|
||||
}
|
||||
|
||||
fn draw(
|
||||
mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<E::State> {
|
||||
self.measure(available_space, cx);
|
||||
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> ElementObject for Option<DrawableElement<E>>
|
||||
impl<E> ElementObject for Drawable<E>
|
||||
where
|
||||
E: Element,
|
||||
E::State: 'static,
|
||||
E::BeforeLayout: 'static,
|
||||
{
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
self.as_ref().unwrap().element_id()
|
||||
fn inner_element(&mut self) -> &mut dyn Any {
|
||||
&mut self.element
|
||||
}
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
DrawableElement::request_layout(self.as_mut().unwrap(), cx)
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
Drawable::before_layout(self, cx)
|
||||
}
|
||||
|
||||
fn after_layout(&mut self, cx: &mut ElementContext) {
|
||||
Drawable::after_layout(self, cx);
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut ElementContext) {
|
||||
DrawableElement::paint(self.take().unwrap(), cx);
|
||||
Drawable::paint(self, cx);
|
||||
}
|
||||
|
||||
fn measure(
|
||||
@@ -416,16 +391,7 @@ where
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) -> Size<Pixels> {
|
||||
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
|
||||
Drawable::measure(self, available_space, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -436,18 +402,28 @@ impl AnyElement {
|
||||
pub(crate) fn new<E>(element: E) -> Self
|
||||
where
|
||||
E: 'static + Element,
|
||||
E::State: Any,
|
||||
E::BeforeLayout: Any,
|
||||
{
|
||||
let element = ELEMENT_ARENA
|
||||
.with_borrow_mut(|arena| arena.alloc(|| Some(DrawableElement::new(element))))
|
||||
.with_borrow_mut(|arena| arena.alloc(|| Drawable::new(element)))
|
||||
.map(|element| element as &mut dyn ElementObject);
|
||||
AnyElement(element)
|
||||
}
|
||||
|
||||
/// Attempt to downcast a reference to the boxed element to a specific type.
|
||||
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||
self.0.inner_element().downcast_mut()
|
||||
}
|
||||
|
||||
/// Request the layout ID of the element stored in this `AnyElement`.
|
||||
/// Used for laying out child elements in a parent element.
|
||||
pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
self.0.request_layout(cx)
|
||||
pub fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
self.0.before_layout(cx)
|
||||
}
|
||||
|
||||
/// Commits the element bounds of this [AnyElement] for hitbox purposes.
|
||||
pub fn after_layout(&mut self, cx: &mut ElementContext) {
|
||||
self.0.after_layout(cx)
|
||||
}
|
||||
|
||||
/// Paints the element stored in this `AnyElement`.
|
||||
@@ -464,35 +440,44 @@ impl AnyElement {
|
||||
self.0.measure(available_space, cx)
|
||||
}
|
||||
|
||||
/// Initializes this element and performs layout in the available space, then paints it at the given origin.
|
||||
pub fn draw(
|
||||
/// Initializes this element, performs layout if needed and commits its bounds.
|
||||
pub fn layout(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
absolute_offset: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.0.draw(origin, available_space, cx)
|
||||
}
|
||||
|
||||
/// Returns the element ID of the element stored in this `AnyElement`, if any.
|
||||
pub fn inner_id(&self) -> Option<ElementId> {
|
||||
self.0.element_id()
|
||||
) -> Size<Pixels> {
|
||||
let size = self.measure(available_space, cx);
|
||||
cx.with_absolute_element_offset(absolute_offset, |cx| self.after_layout(cx));
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for AnyElement {
|
||||
type State = ();
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
let layout_id = self.request_layout(cx);
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let layout_id = self.before_layout(cx);
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut ElementContext) {
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.after_layout(cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.paint(cx)
|
||||
}
|
||||
}
|
||||
@@ -500,10 +485,6 @@ impl Element for AnyElement {
|
||||
impl IntoElement for AnyElement {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -514,35 +495,37 @@ impl IntoElement for AnyElement {
|
||||
}
|
||||
|
||||
/// The empty element, which renders nothing.
|
||||
pub type Empty = ();
|
||||
pub struct Empty;
|
||||
|
||||
impl IntoElement for () {
|
||||
impl IntoElement for Empty {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for () {
|
||||
type State = ();
|
||||
impl Element for Empty {
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_state: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
(cx.request_layout(&crate::Style::default(), None), ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::BeforeLayout,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::State,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_after_layout: &mut Self::AfterLayout,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -4,54 +4,68 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
|
||||
|
||||
/// Construct a canvas element with the given paint callback.
|
||||
/// Useful for adding short term custom drawing to a view.
|
||||
pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut ElementContext)) -> Canvas {
|
||||
pub fn canvas<T>(
|
||||
after_layout: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
|
||||
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
|
||||
) -> Canvas<T> {
|
||||
Canvas {
|
||||
paint_callback: Some(Box::new(callback)),
|
||||
after_layout: Some(Box::new(after_layout)),
|
||||
paint: Some(Box::new(paint)),
|
||||
style: StyleRefinement::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
||||
/// custom element
|
||||
pub struct Canvas {
|
||||
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut ElementContext)>>,
|
||||
pub struct Canvas<T> {
|
||||
after_layout: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
|
||||
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
impl IntoElement for Canvas {
|
||||
impl<T: 'static> IntoElement for Canvas<T> {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Canvas {
|
||||
type State = Style;
|
||||
impl<T: 'static> Element for Canvas<T> {
|
||||
type BeforeLayout = Style;
|
||||
type AfterLayout = Option<T>;
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (crate::LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.style);
|
||||
let layout_id = cx.request_layout(&style, []);
|
||||
(layout_id, style)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Style,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<T> {
|
||||
Some(self.after_layout.take().unwrap()(bounds, cx))
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
style: &mut Style,
|
||||
after_layout: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let after_layout = after_layout.take().unwrap();
|
||||
style.paint(bounds, cx, |cx| {
|
||||
(self.paint_callback.take().unwrap())(&bounds, cx)
|
||||
(self.paint.take().unwrap())(bounds, after_layout, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Canvas {
|
||||
impl<T> Styled for Canvas<T> {
|
||||
fn style(&mut self) -> &mut crate::StyleRefinement {
|
||||
&mut self.style
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement,
|
||||
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
|
||||
point, size, Bounds, DevicePixels, Element, ElementContext, Hitbox, ImageData,
|
||||
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
|
||||
StyleRefinement, Styled, UriOrPath,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
@@ -88,86 +88,85 @@ impl Img {
|
||||
}
|
||||
|
||||
impl Element for Img {
|
||||
type State = InteractiveElementState;
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = Option<Hitbox>;
|
||||
|
||||
fn request_layout(
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let layout_id = self
|
||||
.interactivity
|
||||
.before_layout(cx, |style, cx| cx.request_layout(&style, []));
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
element_state: Option<Self::State>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
) -> Option<Hitbox> {
|
||||
self.interactivity
|
||||
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
|
||||
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
_: &mut Self::BeforeLayout,
|
||||
hitbox: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let source = self.source.clone();
|
||||
self.interactivity.paint(
|
||||
bounds,
|
||||
bounds.size,
|
||||
element_state,
|
||||
cx,
|
||||
|style, _scroll_offset, cx| {
|
||||
self.interactivity
|
||||
.paint(bounds, hitbox.as_ref(), cx, |style, cx| {
|
||||
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||
cx.with_z_index(1, |cx| {
|
||||
match source {
|
||||
ImageSource::Uri(_) | ImageSource::File(_) => {
|
||||
let uri_or_path: UriOrPath = match source {
|
||||
ImageSource::Uri(uri) => uri.into(),
|
||||
ImageSource::File(path) => path.into(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match source {
|
||||
ImageSource::Uri(_) | ImageSource::File(_) => {
|
||||
let uri_or_path: UriOrPath = match source {
|
||||
ImageSource::Uri(uri) => uri.into(),
|
||||
ImageSource::File(path) => path.into(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let image_future = cx.image_cache.get(uri_or_path.clone(), cx);
|
||||
if let Some(data) = image_future
|
||||
.clone()
|
||||
.now_or_never()
|
||||
.and_then(|result| result.ok())
|
||||
{
|
||||
let new_bounds = preserve_aspect_ratio(bounds, data.size());
|
||||
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
|
||||
.log_err();
|
||||
} else {
|
||||
cx.spawn(|mut cx| async move {
|
||||
if image_future.await.ok().is_some() {
|
||||
cx.on_next_frame(|cx| cx.refresh());
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
ImageSource::Data(data) => {
|
||||
let image_future = cx.image_cache.get(uri_or_path.clone(), cx);
|
||||
if let Some(data) = image_future
|
||||
.clone()
|
||||
.now_or_never()
|
||||
.and_then(|result| result.ok())
|
||||
{
|
||||
let new_bounds = preserve_aspect_ratio(bounds, data.size());
|
||||
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
|
||||
.log_err();
|
||||
} else {
|
||||
cx.spawn(|mut cx| async move {
|
||||
if image_future.await.ok().is_some() {
|
||||
cx.on_next_frame(|cx| cx.refresh());
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
ImageSource::Surface(surface) => {
|
||||
let size = size(surface.width().into(), surface.height().into());
|
||||
let new_bounds = preserve_aspect_ratio(bounds, size);
|
||||
// TODO: Add support for corner_radii and grayscale.
|
||||
cx.paint_surface(new_bounds, surface);
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
)
|
||||
ImageSource::Data(data) => {
|
||||
let new_bounds = preserve_aspect_ratio(bounds, data.size());
|
||||
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
|
||||
.log_err();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
ImageSource::Surface(surface) => {
|
||||
let size = size(surface.width().into(), surface.height().into());
|
||||
let new_bounds = preserve_aspect_ratio(bounds, size);
|
||||
// TODO: Add support for corner_radii and grayscale.
|
||||
cx.paint_surface(new_bounds, surface);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for Img {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
self.interactivity.element_id.clone()
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
|
||||
Element, ElementContext, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||
Element, ElementContext, HitboxId, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||
StyleRefinement, Styled, WindowContext,
|
||||
};
|
||||
use collections::VecDeque;
|
||||
use refineable::Refineable as _;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
use taffy::style::Overflow;
|
||||
@@ -96,6 +97,13 @@ struct LayoutItemsResponse {
|
||||
item_elements: VecDeque<AnyElement>,
|
||||
}
|
||||
|
||||
/// Frame state used by the [List] element.
|
||||
#[derive(Default)]
|
||||
pub struct ListFrameState {
|
||||
scroll_top: ListOffset,
|
||||
items: SmallVec<[AnyElement; 32]>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ListItem {
|
||||
Unrendered,
|
||||
@@ -302,7 +310,6 @@ impl StateInner {
|
||||
height: Pixels,
|
||||
delta: Point<Pixels>,
|
||||
cx: &mut WindowContext,
|
||||
padding: Edges<Pixels>,
|
||||
) {
|
||||
// Drop scroll events after a reset, since we can't calculate
|
||||
// the new logical scroll top without the item heights
|
||||
@@ -310,6 +317,7 @@ impl StateInner {
|
||||
return;
|
||||
}
|
||||
|
||||
let padding = self.last_padding.unwrap_or_default();
|
||||
let scroll_max =
|
||||
(self.items.summary().height + padding.top + padding.bottom - height).max(px(0.));
|
||||
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
|
||||
@@ -516,13 +524,13 @@ pub struct ListOffset {
|
||||
}
|
||||
|
||||
impl Element for List {
|
||||
type State = ();
|
||||
type BeforeLayout = ListFrameState;
|
||||
type AfterLayout = HitboxId;
|
||||
|
||||
fn request_layout(
|
||||
fn before_layout(
|
||||
&mut self,
|
||||
_state: Option<Self::State>,
|
||||
cx: &mut crate::ElementContext,
|
||||
) -> (crate::LayoutId, Self::State) {
|
||||
) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||
let layout_id = match self.sizing_behavior {
|
||||
ListSizingBehavior::Infer => {
|
||||
let mut style = Style::default();
|
||||
@@ -580,15 +588,15 @@ impl Element for List {
|
||||
})
|
||||
}
|
||||
};
|
||||
(layout_id, ())
|
||||
(layout_id, ListFrameState::default())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
_state: &mut Self::State,
|
||||
cx: &mut crate::ElementContext,
|
||||
) {
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> HitboxId {
|
||||
let state = &mut *self.state.0.borrow_mut();
|
||||
state.reset = false;
|
||||
|
||||
@@ -615,12 +623,11 @@ impl Element for List {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
|
||||
item_origin.y -= layout_response.scroll_top.offset_in_item;
|
||||
for item_element in &mut layout_response.item_elements {
|
||||
let item_height = item_element
|
||||
.measure(layout_response.available_item_space, cx)
|
||||
.height;
|
||||
item_element.draw(item_origin, layout_response.available_item_space, cx);
|
||||
item_origin.y += item_height;
|
||||
for mut item_element in layout_response.item_elements {
|
||||
let item_size =
|
||||
item_element.layout(item_origin, layout_response.available_item_space, cx);
|
||||
before_layout.items.push(item_element);
|
||||
item_origin.y += item_size.height;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -628,20 +635,33 @@ impl Element for List {
|
||||
state.last_layout_bounds = Some(bounds);
|
||||
state.last_padding = Some(padding);
|
||||
|
||||
cx.insert_hitbox(bounds, false).id
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
hitbox_id: &mut HitboxId,
|
||||
cx: &mut crate::ElementContext,
|
||||
) {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
for item in &mut before_layout.items {
|
||||
item.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
let list_state = self.state.clone();
|
||||
let height = bounds.size.height;
|
||||
|
||||
let scroll_top = before_layout.scroll_top;
|
||||
let hitbox_id = *hitbox_id;
|
||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& bounds.contains(&event.position)
|
||||
&& cx.was_top_layer(&event.position, cx.stacking_order())
|
||||
{
|
||||
if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) {
|
||||
list_state.0.borrow_mut().scroll(
|
||||
&layout_response.scroll_top,
|
||||
&scroll_top,
|
||||
height,
|
||||
event.delta.pixel_delta(px(20.)),
|
||||
cx,
|
||||
padding,
|
||||
)
|
||||
}
|
||||
});
|
||||
@@ -651,10 +671,6 @@ impl Element for List {
|
||||
impl IntoElement for List {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -761,7 +777,7 @@ mod test {
|
||||
cx.draw(
|
||||
point(px(0.), px(0.)),
|
||||
size(px(100.), px(20.)).into(),
|
||||
|_| list(state.clone()).w_full().h_full().z_index(10).into_any(),
|
||||
|_| list(state.clone()).w_full().h_full().into_any(),
|
||||
);
|
||||
|
||||
// Reset
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
/// The state that the overlay element uses to track its children.
|
||||
pub struct OverlayState {
|
||||
child_layout_ids: SmallVec<[LayoutId; 4]>,
|
||||
offset: Point<Pixels>,
|
||||
}
|
||||
|
||||
/// An overlay element that can be used to display UI that
|
||||
@@ -69,17 +70,14 @@ impl ParentElement for Overlay {
|
||||
}
|
||||
|
||||
impl Element for Overlay {
|
||||
type State = OverlayState;
|
||||
type BeforeLayout = OverlayState;
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (crate::LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
|
||||
let child_layout_ids = self
|
||||
.children
|
||||
.iter_mut()
|
||||
.map(|child| child.request_layout(cx))
|
||||
.map(|child| child.before_layout(cx))
|
||||
.collect::<SmallVec<_>>();
|
||||
|
||||
let overlay_style = Style {
|
||||
@@ -90,22 +88,28 @@ impl Element for Overlay {
|
||||
|
||||
let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
|
||||
|
||||
(layout_id, OverlayState { child_layout_ids })
|
||||
(
|
||||
layout_id,
|
||||
OverlayState {
|
||||
child_layout_ids,
|
||||
offset: Point::default(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if element_state.child_layout_ids.is_empty() {
|
||||
if before_layout.child_layout_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
||||
let mut child_max = Point::default();
|
||||
for child_layout_id in &element_state.child_layout_ids {
|
||||
for child_layout_id in &before_layout.child_layout_ids {
|
||||
let child_bounds = cx.layout_bounds(*child_layout_id);
|
||||
child_min = child_min.min(&child_bounds.origin);
|
||||
child_max = child_max.max(&child_bounds.lower_right());
|
||||
@@ -165,25 +169,30 @@ impl Element for Overlay {
|
||||
desired.origin.y = limits.origin.y;
|
||||
}
|
||||
|
||||
let mut offset = cx.element_offset() + desired.origin - bounds.origin;
|
||||
offset = point(offset.x.round(), offset.y.round());
|
||||
cx.with_absolute_element_offset(offset, |cx| {
|
||||
cx.break_content_mask(|cx| {
|
||||
for child in &mut self.children {
|
||||
child.paint(cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
before_layout.offset = cx.element_offset() + desired.origin - bounds.origin;
|
||||
before_layout.offset = point(
|
||||
before_layout.offset.x.round(),
|
||||
before_layout.offset.y.round(),
|
||||
);
|
||||
|
||||
for child in self.children.drain(..) {
|
||||
cx.defer_draw(child, before_layout.offset, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: crate::Bounds<crate::Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
_after_layout: &mut Self::AfterLayout,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for Overlay {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState,
|
||||
Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled,
|
||||
Bounds, Element, ElementContext, Hitbox, InteractiveElement, Interactivity, IntoElement,
|
||||
LayoutId, Pixels, SharedString, StyleRefinement, Styled,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
@@ -27,28 +27,37 @@ impl Svg {
|
||||
}
|
||||
|
||||
impl Element for Svg {
|
||||
type State = InteractiveElementState;
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = Option<Hitbox>;
|
||||
|
||||
fn request_layout(
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let layout_id = self
|
||||
.interactivity
|
||||
.before_layout(cx, |style, cx| cx.request_layout(&style, None));
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
element_state: Option<Self::State>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
self.interactivity.layout(element_state, cx, |style, cx| {
|
||||
cx.request_layout(&style, None)
|
||||
})
|
||||
) -> Option<Hitbox> {
|
||||
self.interactivity
|
||||
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
_before_layout: &mut Self::BeforeLayout,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
self.interactivity
|
||||
.paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
|
||||
.paint(bounds, hitbox.as_ref(), cx, |style, cx| {
|
||||
if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
|
||||
cx.paint_svg(bounds, path.clone(), color).log_err();
|
||||
}
|
||||
@@ -59,10 +68,6 @@ impl Element for Svg {
|
||||
impl IntoElement for Svg {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
self.interactivity.element_id.clone()
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
|
||||
HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
|
||||
Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
|
||||
HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
|
||||
TOOLTIP_DELAY,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
@@ -17,30 +17,37 @@ use std::{
|
||||
use util::ResultExt;
|
||||
|
||||
impl Element for &'static str {
|
||||
type State = TextState;
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
||||
state.paint(bounds, self, cx)
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_text_state: &mut Self::BeforeLayout,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut TextState,
|
||||
_: &mut (),
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
text_state.paint(bounds, self, cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for &'static str {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -49,41 +56,44 @@ impl IntoElement for &'static str {
|
||||
impl IntoElement for String {
|
||||
type Element = SharedString;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for SharedString {
|
||||
type State = TextState;
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.clone(), None, cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_text_state: &mut Self::BeforeLayout,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let text_str: &str = self.as_ref();
|
||||
state.paint(bounds, text_str, cx)
|
||||
text_state.paint(bounds, text_str, cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for SharedString {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -138,30 +148,37 @@ impl StyledText {
|
||||
}
|
||||
|
||||
impl Element for StyledText {
|
||||
type State = TextState;
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
state.paint(bounds, &self.text, cx)
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::BeforeLayout,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
text_state.paint(bounds, &self.text, cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for StyledText {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -324,8 +341,8 @@ struct InteractiveTextClickEvent {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Default)]
|
||||
pub struct InteractiveTextState {
|
||||
text_state: TextState,
|
||||
mouse_down_index: Rc<Cell<Option<usize>>>,
|
||||
hovered_index: Rc<Cell<Option<usize>>>,
|
||||
active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
|
||||
@@ -385,179 +402,181 @@ impl InteractiveText {
|
||||
}
|
||||
|
||||
impl Element for InteractiveText {
|
||||
type State = InteractiveTextState;
|
||||
type BeforeLayout = TextState;
|
||||
type AfterLayout = Hitbox;
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
if let Some(InteractiveTextState {
|
||||
mouse_down_index,
|
||||
hovered_index,
|
||||
active_tooltip,
|
||||
..
|
||||
}) = state
|
||||
{
|
||||
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
||||
let element_state = InteractiveTextState {
|
||||
text_state,
|
||||
mouse_down_index,
|
||||
hovered_index,
|
||||
active_tooltip,
|
||||
};
|
||||
(layout_id, element_state)
|
||||
} else {
|
||||
let (layout_id, text_state) = self.text.request_layout(None, cx);
|
||||
let element_state = InteractiveTextState {
|
||||
text_state,
|
||||
mouse_down_index: Rc::default(),
|
||||
hovered_index: Rc::default(),
|
||||
active_tooltip: Rc::default(),
|
||||
};
|
||||
(layout_id, element_state)
|
||||
}
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
self.text.before_layout(cx)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
if let Some(click_listener) = self.click_listener.take() {
|
||||
let mouse_position = cx.mouse_position();
|
||||
if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
|
||||
if self
|
||||
.clickable_ranges
|
||||
.iter()
|
||||
.any(|range| range.contains(&ix))
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
cx.set_cursor_style(crate::CursorStyle::PointingHand)
|
||||
}
|
||||
}
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> Hitbox {
|
||||
self.text.after_layout(bounds, state, cx);
|
||||
cx.insert_hitbox(bounds, false)
|
||||
}
|
||||
|
||||
let text_state = state.text_state.clone();
|
||||
let mouse_down = state.mouse_down_index.clone();
|
||||
if let Some(mouse_down_index) = mouse_down.get() {
|
||||
let clickable_ranges = mem::take(&mut self.clickable_ranges);
|
||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
if let Some(mouse_up_index) =
|
||||
text_state.index_for_position(bounds, event.position)
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::BeforeLayout,
|
||||
hitbox: &mut Hitbox,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.with_element_state::<InteractiveTextState, _>(
|
||||
Some(self.element_id.clone()),
|
||||
|interactive_state, cx| {
|
||||
let mut interactive_state = interactive_state.unwrap().unwrap_or_default();
|
||||
if let Some(click_listener) = self.click_listener.take() {
|
||||
let mouse_position = cx.mouse_position();
|
||||
if let Some(ix) = text_state.index_for_position(bounds, mouse_position) {
|
||||
if self
|
||||
.clickable_ranges
|
||||
.iter()
|
||||
.any(|range| range.contains(&ix))
|
||||
{
|
||||
click_listener(
|
||||
&clickable_ranges,
|
||||
InteractiveTextClickEvent {
|
||||
mouse_down_index,
|
||||
mouse_up_index,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
mouse_down.take();
|
||||
cx.refresh();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
if let Some(mouse_down_index) =
|
||||
text_state.index_for_position(bounds, event.position)
|
||||
{
|
||||
mouse_down.set(Some(mouse_down_index));
|
||||
cx.refresh();
|
||||
cx.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(hover_listener) = self.hover_listener.take() {
|
||||
let text_state = state.text_state.clone();
|
||||
let hovered_index = state.hovered_index.clone();
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble {
|
||||
let current = hovered_index.get();
|
||||
let updated = text_state.index_for_position(bounds, event.position);
|
||||
if current != updated {
|
||||
hovered_index.set(updated);
|
||||
hover_listener(updated, event.clone(), cx);
|
||||
cx.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
|
||||
let active_tooltip = state.active_tooltip.clone();
|
||||
let pending_mouse_down = state.mouse_down_index.clone();
|
||||
let text_state = state.text_state.clone();
|
||||
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
let position = text_state.index_for_position(bounds, event.position);
|
||||
let is_hovered = position.is_some() && pending_mouse_down.get().is_none();
|
||||
if !is_hovered {
|
||||
active_tooltip.take();
|
||||
return;
|
||||
}
|
||||
let position = position.unwrap();
|
||||
let text_state = text_state.clone();
|
||||
let mouse_down = interactive_state.mouse_down_index.clone();
|
||||
if let Some(mouse_down_index) = mouse_down.get() {
|
||||
let hitbox = hitbox.clone();
|
||||
let clickable_ranges = mem::take(&mut self.clickable_ranges);
|
||||
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
||||
if let Some(mouse_up_index) =
|
||||
text_state.index_for_position(bounds, event.position)
|
||||
{
|
||||
click_listener(
|
||||
&clickable_ranges,
|
||||
InteractiveTextClickEvent {
|
||||
mouse_down_index,
|
||||
mouse_up_index,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
|
||||
if active_tooltip.borrow().is_none() {
|
||||
let task = cx.spawn({
|
||||
let active_tooltip = active_tooltip.clone();
|
||||
let tooltip_builder = tooltip_builder.clone();
|
||||
|
||||
move |mut cx| async move {
|
||||
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
||||
cx.update(|cx| {
|
||||
let new_tooltip =
|
||||
tooltip_builder(position, cx).map(|tooltip| ActiveTooltip {
|
||||
tooltip: Some(AnyTooltip {
|
||||
view: tooltip,
|
||||
cursor_offset: cx.mouse_position(),
|
||||
}),
|
||||
_task: None,
|
||||
});
|
||||
*active_tooltip.borrow_mut() = new_tooltip;
|
||||
mouse_down.take();
|
||||
cx.refresh();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let hitbox = hitbox.clone();
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
||||
if let Some(mouse_down_index) =
|
||||
text_state.index_for_position(bounds, event.position)
|
||||
{
|
||||
mouse_down.set(Some(mouse_down_index));
|
||||
cx.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(hover_listener) = self.hover_listener.take() {
|
||||
let hitbox = hitbox.clone();
|
||||
let text_state = text_state.clone();
|
||||
let hovered_index = interactive_state.hovered_index.clone();
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
|
||||
let current = hovered_index.get();
|
||||
let updated = text_state.index_for_position(bounds, event.position);
|
||||
if current != updated {
|
||||
hovered_index.set(updated);
|
||||
hover_listener(updated, event.clone(), cx);
|
||||
cx.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
|
||||
tooltip: None,
|
||||
_task: Some(task),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let active_tooltip = state.active_tooltip.clone();
|
||||
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
|
||||
active_tooltip.take();
|
||||
});
|
||||
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
|
||||
let hitbox = hitbox.clone();
|
||||
let active_tooltip = interactive_state.active_tooltip.clone();
|
||||
let pending_mouse_down = interactive_state.mouse_down_index.clone();
|
||||
let text_state = text_state.clone();
|
||||
|
||||
if let Some(tooltip) = state
|
||||
.active_tooltip
|
||||
.clone()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|at| at.tooltip.clone())
|
||||
{
|
||||
cx.set_tooltip(tooltip);
|
||||
}
|
||||
}
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
let position = text_state.index_for_position(bounds, event.position);
|
||||
let is_hovered = position.is_some()
|
||||
&& hitbox.is_hovered(cx)
|
||||
&& pending_mouse_down.get().is_none();
|
||||
if !is_hovered {
|
||||
active_tooltip.take();
|
||||
return;
|
||||
}
|
||||
let position = position.unwrap();
|
||||
|
||||
self.text.paint(bounds, &mut state.text_state, cx)
|
||||
if phase != DispatchPhase::Bubble {
|
||||
return;
|
||||
}
|
||||
|
||||
if active_tooltip.borrow().is_none() {
|
||||
let task = cx.spawn({
|
||||
let active_tooltip = active_tooltip.clone();
|
||||
let tooltip_builder = tooltip_builder.clone();
|
||||
|
||||
move |mut cx| async move {
|
||||
cx.background_executor().timer(TOOLTIP_DELAY).await;
|
||||
cx.update(|cx| {
|
||||
let new_tooltip =
|
||||
tooltip_builder(position, cx).map(|tooltip| {
|
||||
ActiveTooltip {
|
||||
tooltip: Some(AnyTooltip {
|
||||
view: tooltip,
|
||||
cursor_offset: cx.mouse_position(),
|
||||
}),
|
||||
_task: None,
|
||||
}
|
||||
});
|
||||
*active_tooltip.borrow_mut() = new_tooltip;
|
||||
cx.refresh();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
|
||||
tooltip: None,
|
||||
_task: Some(task),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let active_tooltip = interactive_state.active_tooltip.clone();
|
||||
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
|
||||
active_tooltip.take();
|
||||
});
|
||||
|
||||
if let Some(tooltip) = interactive_state
|
||||
.active_tooltip
|
||||
.clone()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|at| at.tooltip.clone())
|
||||
{
|
||||
cx.set_tooltip(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
self.text.paint(bounds, text_state, &mut (), cx);
|
||||
|
||||
((), Some(interactive_state))
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for InteractiveText {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
Some(self.element_id.clone())
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
|
||||
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
|
||||
Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
|
||||
ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render,
|
||||
Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||
@@ -42,13 +42,13 @@ where
|
||||
};
|
||||
|
||||
UniformList {
|
||||
id: id.clone(),
|
||||
item_count,
|
||||
item_to_measure_index: 0,
|
||||
render_items: Box::new(render_range),
|
||||
interactivity: Interactivity {
|
||||
element_id: Some(id),
|
||||
base_style: Box::new(base_style),
|
||||
occlude_mouse: true,
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
location: Some(*core::panic::Location::caller()),
|
||||
@@ -61,7 +61,6 @@ where
|
||||
|
||||
/// A list element for efficiently laying out and displaying a list of uniform-height elements.
|
||||
pub struct UniformList {
|
||||
id: ElementId,
|
||||
item_count: usize,
|
||||
item_to_measure_index: usize,
|
||||
render_items:
|
||||
@@ -70,6 +69,12 @@ pub struct UniformList {
|
||||
scroll_handle: Option<UniformListScrollHandle>,
|
||||
}
|
||||
|
||||
/// Frame state used by the [UniformList].
|
||||
pub struct UniformListFrameState {
|
||||
item_size: Size<Pixels>,
|
||||
items: SmallVec<[AnyElement; 32]>,
|
||||
}
|
||||
|
||||
/// A handle for controlling the scroll position of a uniform list.
|
||||
/// This should be stored in your view and passed to the uniform_list on each frame.
|
||||
#[derive(Clone, Default)]
|
||||
@@ -97,72 +102,47 @@ impl Styled for UniformList {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Default)]
|
||||
pub struct UniformListState {
|
||||
interactive: InteractiveElementState,
|
||||
item_size: Size<Pixels>,
|
||||
}
|
||||
|
||||
impl Element for UniformList {
|
||||
type State = UniformListState;
|
||||
type BeforeLayout = UniformListFrameState;
|
||||
type AfterLayout = Option<Hitbox>;
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
let max_items = self.item_count;
|
||||
let item_size = state
|
||||
.as_ref()
|
||||
.map(|s| s.item_size)
|
||||
.unwrap_or_else(|| self.measure_item(None, cx));
|
||||
let item_size = self.measure_item(None, cx);
|
||||
let layout_id = self.interactivity.before_layout(cx, |style, cx| {
|
||||
cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| {
|
||||
let desired_height = item_size.height * max_items;
|
||||
let width = known_dimensions
|
||||
.width
|
||||
.unwrap_or(match available_space.width {
|
||||
AvailableSpace::Definite(x) => x,
|
||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
|
||||
});
|
||||
|
||||
let (layout_id, interactive) =
|
||||
self.interactivity
|
||||
.layout(state.map(|s| s.interactive), cx, |style, cx| {
|
||||
cx.request_measured_layout(
|
||||
style,
|
||||
move |known_dimensions, available_space, _cx| {
|
||||
let desired_height = item_size.height * max_items;
|
||||
let width =
|
||||
known_dimensions
|
||||
.width
|
||||
.unwrap_or(match available_space.width {
|
||||
AvailableSpace::Definite(x) => x,
|
||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
||||
item_size.width
|
||||
}
|
||||
});
|
||||
let height = match available_space.height {
|
||||
AvailableSpace::Definite(height) => desired_height.min(height),
|
||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
|
||||
};
|
||||
size(width, height)
|
||||
})
|
||||
});
|
||||
|
||||
let height = match available_space.height {
|
||||
AvailableSpace::Definite(height) => desired_height.min(height),
|
||||
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
|
||||
desired_height
|
||||
}
|
||||
};
|
||||
size(width, height)
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
let element_state = UniformListState {
|
||||
interactive,
|
||||
item_size,
|
||||
};
|
||||
|
||||
(layout_id, element_state)
|
||||
(
|
||||
layout_id,
|
||||
UniformListFrameState {
|
||||
item_size,
|
||||
items: SmallVec::new(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let style =
|
||||
self.interactivity
|
||||
.compute_style(Some(bounds), &mut element_state.interactive, cx);
|
||||
) -> Option<Hitbox> {
|
||||
let style = self.interactivity.compute_style(None, cx);
|
||||
let border = style.border_widths.to_pixels(cx.rem_size());
|
||||
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
||||
|
||||
@@ -172,17 +152,12 @@ impl Element for UniformList {
|
||||
- point(border.right + padding.right, border.bottom + padding.bottom),
|
||||
);
|
||||
|
||||
let item_size = element_state.item_size;
|
||||
let content_size = Size {
|
||||
width: padded_bounds.size.width,
|
||||
height: item_size.height * self.item_count + padding.top + padding.bottom,
|
||||
height: before_layout.item_size.height * self.item_count + padding.top + padding.bottom,
|
||||
};
|
||||
|
||||
let shared_scroll_offset = element_state
|
||||
.interactive
|
||||
.scroll_offset
|
||||
.get_or_insert_with(Rc::default)
|
||||
.clone();
|
||||
let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
|
||||
|
||||
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
|
||||
let shared_scroll_to_item = self
|
||||
@@ -190,12 +165,11 @@ impl Element for UniformList {
|
||||
.as_mut()
|
||||
.and_then(|handle| handle.deferred_scroll_to_item.take());
|
||||
|
||||
self.interactivity.paint(
|
||||
self.interactivity.after_layout(
|
||||
bounds,
|
||||
content_size,
|
||||
&mut element_state.interactive,
|
||||
cx,
|
||||
|style, mut scroll_offset, cx| {
|
||||
|style, mut scroll_offset, hitbox, cx| {
|
||||
let border = style.border_widths.to_pixels(cx.rem_size());
|
||||
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
||||
|
||||
@@ -238,36 +212,45 @@ impl Element for UniformList {
|
||||
..cmp::min(last_visible_element_ix, self.item_count);
|
||||
|
||||
let mut items = (self.render_items)(visible_range.clone(), cx);
|
||||
cx.with_z_index(1, |cx| {
|
||||
let content_mask = ContentMask { bounds };
|
||||
cx.with_content_mask(Some(content_mask), |cx| {
|
||||
for (item, ix) in items.iter_mut().zip(visible_range) {
|
||||
let item_origin = padded_bounds.origin
|
||||
+ point(
|
||||
px(0.),
|
||||
item_height * ix + scroll_offset.y + padding.top,
|
||||
);
|
||||
let available_space = size(
|
||||
AvailableSpace::Definite(padded_bounds.size.width),
|
||||
AvailableSpace::Definite(item_height),
|
||||
);
|
||||
item.draw(item_origin, available_space, cx);
|
||||
}
|
||||
});
|
||||
let content_mask = ContentMask { bounds };
|
||||
cx.with_content_mask(Some(content_mask), |cx| {
|
||||
for (mut item, ix) in items.into_iter().zip(visible_range) {
|
||||
let item_origin = padded_bounds.origin
|
||||
+ point(px(0.), item_height * ix + scroll_offset.y + padding.top);
|
||||
let available_space = size(
|
||||
AvailableSpace::Definite(padded_bounds.size.width),
|
||||
AvailableSpace::Definite(item_height),
|
||||
);
|
||||
item.layout(item_origin, available_space, cx);
|
||||
before_layout.items.push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hitbox
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.interactivity
|
||||
.paint(bounds, hitbox.as_ref(), cx, |_, cx| {
|
||||
for item in &mut before_layout.items {
|
||||
item.paint(cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for UniformList {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<crate::ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -828,6 +828,28 @@ where
|
||||
y: self.origin.y.clone() + self.size.height.clone().half(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the half perimeter of a rectangle defined by the bounds.
|
||||
///
|
||||
/// The half perimeter is calculated as the sum of the width and the height of the rectangle.
|
||||
/// This method is generic over the type `T` which must implement the `Sub` trait to allow
|
||||
/// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait
|
||||
/// to sum the width and height for the half perimeter.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use zed::{Bounds, Point, Size};
|
||||
/// let bounds = Bounds {
|
||||
/// origin: Point { x: 0, y: 0 },
|
||||
/// size: Size { width: 10, height: 20 },
|
||||
/// };
|
||||
/// let half_perimeter = bounds.half_perimeter();
|
||||
/// assert_eq!(half_perimeter, 30);
|
||||
/// ```
|
||||
pub fn half_perimeter(&self) -> T {
|
||||
self.size.width.clone() + self.size.height.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
|
||||
@@ -2617,6 +2639,12 @@ pub trait Half {
|
||||
fn half(&self) -> Self;
|
||||
}
|
||||
|
||||
impl Half for i32 {
|
||||
fn half(&self) -> Self {
|
||||
self / 2
|
||||
}
|
||||
}
|
||||
|
||||
impl Half for f32 {
|
||||
fn half(&self) -> Self {
|
||||
self / 2.
|
||||
|
||||
@@ -70,6 +70,7 @@ mod app;
|
||||
|
||||
mod arena;
|
||||
mod assets;
|
||||
mod bounds_tree;
|
||||
mod color;
|
||||
mod element;
|
||||
mod elements;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
point, seal::Sealed, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
|
||||
point, seal::Sealed, Empty, IntoElement, Keystroke, Modifiers, Pixels, Point, Render,
|
||||
ViewContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
|
||||
@@ -343,7 +344,8 @@ impl ExternalPaths {
|
||||
|
||||
impl Render for ExternalPaths {
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
// Intentionally left empty because the platform will render icons for the dragged files
|
||||
// the platform will render icons for the dragged files
|
||||
Empty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,12 @@ use crate::{
|
||||
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext,
|
||||
};
|
||||
use collections::FxHashMap;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
mem,
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
@@ -68,6 +69,7 @@ pub(crate) struct DispatchNodeId(usize);
|
||||
pub(crate) struct DispatchTree {
|
||||
node_stack: Vec<DispatchNodeId>,
|
||||
pub(crate) context_stack: Vec<KeyContext>,
|
||||
view_stack: Vec<EntityId>,
|
||||
nodes: Vec<DispatchNode>,
|
||||
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
|
||||
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
|
||||
@@ -81,7 +83,7 @@ pub(crate) struct DispatchNode {
|
||||
pub key_listeners: Vec<KeyListener>,
|
||||
pub action_listeners: Vec<DispatchActionListener>,
|
||||
pub context: Option<KeyContext>,
|
||||
focus_id: Option<FocusId>,
|
||||
pub focus_id: Option<FocusId>,
|
||||
view_id: Option<EntityId>,
|
||||
parent: Option<DispatchNodeId>,
|
||||
}
|
||||
@@ -99,6 +101,7 @@ impl DispatchTree {
|
||||
Self {
|
||||
node_stack: Vec::new(),
|
||||
context_stack: Vec::new(),
|
||||
view_stack: Vec::new(),
|
||||
nodes: Vec::new(),
|
||||
focusable_node_ids: FxHashMap::default(),
|
||||
view_node_ids: FxHashMap::default(),
|
||||
@@ -111,72 +114,113 @@ impl DispatchTree {
|
||||
pub fn clear(&mut self) {
|
||||
self.node_stack.clear();
|
||||
self.context_stack.clear();
|
||||
self.view_stack.clear();
|
||||
self.nodes.clear();
|
||||
self.focusable_node_ids.clear();
|
||||
self.view_node_ids.clear();
|
||||
self.keystroke_matchers.clear();
|
||||
}
|
||||
|
||||
pub fn push_node(
|
||||
&mut self,
|
||||
context: Option<KeyContext>,
|
||||
focus_id: Option<FocusId>,
|
||||
view_id: Option<EntityId>,
|
||||
) {
|
||||
pub fn len(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
|
||||
pub fn push_node(&mut self) -> DispatchNodeId {
|
||||
let parent = self.node_stack.last().copied();
|
||||
let node_id = DispatchNodeId(self.nodes.len());
|
||||
|
||||
self.nodes.push(DispatchNode {
|
||||
parent,
|
||||
focus_id,
|
||||
view_id,
|
||||
..Default::default()
|
||||
});
|
||||
self.node_stack.push(node_id);
|
||||
node_id
|
||||
}
|
||||
|
||||
if let Some(context) = context {
|
||||
self.active_node().context = Some(context.clone());
|
||||
pub fn move_to_next_node(&mut self) -> DispatchNodeId {
|
||||
let next_node_id = DispatchNodeId(self.active_node_id().0 + 1);
|
||||
let next_node_parent = self.nodes[next_node_id.0].parent;
|
||||
while self.node_stack.last().copied() != next_node_parent {
|
||||
self.pop_node();
|
||||
}
|
||||
|
||||
self.node_stack.push(next_node_id);
|
||||
let active_node = &self.nodes[next_node_id.0];
|
||||
if let Some(view_id) = active_node.view_id {
|
||||
self.view_stack.push(view_id)
|
||||
}
|
||||
if let Some(context) = active_node.context.clone() {
|
||||
self.context_stack.push(context);
|
||||
}
|
||||
|
||||
if let Some(focus_id) = focus_id {
|
||||
self.focusable_node_ids.insert(focus_id, node_id);
|
||||
}
|
||||
next_node_id
|
||||
}
|
||||
|
||||
if let Some(view_id) = view_id {
|
||||
pub fn set_key_context(&mut self, context: KeyContext) {
|
||||
self.active_node().context = Some(context.clone());
|
||||
self.context_stack.push(context);
|
||||
}
|
||||
|
||||
pub fn set_focus_id(&mut self, focus_id: FocusId) {
|
||||
let node_id = *self.node_stack.last().unwrap();
|
||||
self.nodes[node_id.0].focus_id = Some(focus_id);
|
||||
self.focusable_node_ids.insert(focus_id, node_id);
|
||||
}
|
||||
|
||||
pub fn set_view_id(&mut self, view_id: EntityId) {
|
||||
if self.parent_view_id() != Some(view_id) {
|
||||
let node_id = *self.node_stack.last().unwrap();
|
||||
self.nodes[node_id.0].view_id = Some(view_id);
|
||||
self.view_node_ids.insert(view_id, node_id);
|
||||
self.view_stack.push(view_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent_view_id(&self) -> Option<EntityId> {
|
||||
self.view_stack.last().copied()
|
||||
}
|
||||
|
||||
pub fn pop_node(&mut self) {
|
||||
let node = &self.nodes[self.active_node_id().0];
|
||||
if node.context.is_some() {
|
||||
self.context_stack.pop();
|
||||
}
|
||||
if node.view_id.is_some() {
|
||||
self.view_stack.pop();
|
||||
}
|
||||
self.node_stack.pop();
|
||||
}
|
||||
|
||||
fn move_node(&mut self, source: &mut DispatchNode) {
|
||||
self.push_node(source.context.take(), source.focus_id, source.view_id);
|
||||
self.push_node();
|
||||
if let Some(context) = source.context.take() {
|
||||
self.set_key_context(context);
|
||||
}
|
||||
if let Some(focus_id) = source.focus_id.take() {
|
||||
self.set_focus_id(focus_id);
|
||||
}
|
||||
if let Some(view_id) = source.view_id.take() {
|
||||
self.set_view_id(view_id);
|
||||
}
|
||||
|
||||
let target = self.active_node();
|
||||
target.key_listeners = mem::take(&mut source.key_listeners);
|
||||
target.action_listeners = mem::take(&mut source.action_listeners);
|
||||
}
|
||||
|
||||
pub fn reuse_view(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> {
|
||||
let view_source_node_id = source
|
||||
.view_node_ids
|
||||
.get(&view_id)
|
||||
.expect("view should exist in previous dispatch tree");
|
||||
let view_source_node = &mut source.nodes[view_source_node_id.0];
|
||||
self.move_node(view_source_node);
|
||||
|
||||
let mut grafted_view_ids = smallvec![view_id];
|
||||
let mut source_stack = vec![*view_source_node_id];
|
||||
pub fn reuse_subtree(
|
||||
&mut self,
|
||||
range: Range<usize>,
|
||||
source: &mut Self,
|
||||
) -> SmallVec<[EntityId; 8]> {
|
||||
let mut grafted_view_ids = SmallVec::new();
|
||||
let mut source_stack = vec![];
|
||||
for (source_node_id, source_node) in source
|
||||
.nodes
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.skip(view_source_node_id.0 + 1)
|
||||
.skip(range.start)
|
||||
.take(range.len())
|
||||
{
|
||||
let source_node_id = DispatchNodeId(source_node_id);
|
||||
while let Some(source_ancestor) = source_stack.last() {
|
||||
@@ -188,14 +232,10 @@ impl DispatchTree {
|
||||
}
|
||||
}
|
||||
|
||||
if source_stack.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
source_stack.push(source_node_id);
|
||||
self.move_node(source_node);
|
||||
if let Some(view_id) = source_node.view_id {
|
||||
grafted_view_ids.push(view_id);
|
||||
}
|
||||
source_stack.push(source_node_id);
|
||||
self.move_node(source_node);
|
||||
if let Some(view_id) = source_node.view_id {
|
||||
grafted_view_ids.push(view_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// todo!(linux): remove
|
||||
// todo(linux): remove
|
||||
#![cfg_attr(target_os = "linux", allow(dead_code))]
|
||||
// todo!("windows"): remove
|
||||
// todo("windows"): remove
|
||||
#![cfg_attr(windows, allow(dead_code))]
|
||||
|
||||
mod app_menu;
|
||||
@@ -63,7 +63,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
Rc::new(LinuxPlatform::new())
|
||||
}
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
unimplemented!()
|
||||
@@ -195,8 +195,8 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
|
||||
fn draw(&self, scene: &Scene);
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
|
||||
@@ -564,7 +564,7 @@ impl BladeRenderer {
|
||||
}
|
||||
PrimitiveBatch::Paths(paths) => {
|
||||
let mut encoder = pass.with(&self.pipelines.paths);
|
||||
//todo!(linux): group by texture ID
|
||||
// todo(linux): group by texture ID
|
||||
for path in paths {
|
||||
let tile = &self.path_tiles[&path.id];
|
||||
let tex_info = self.atlas.get_texture_info(tile.texture_id);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod client;
|
||||
mod client_dispatcher;
|
||||
mod dispatcher;
|
||||
mod platform;
|
||||
mod text_system;
|
||||
@@ -10,4 +9,4 @@ mod x11;
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use text_system::*;
|
||||
pub(crate) use x11::*;
|
||||
// pub(crate) use x11::*;
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::platform::PlatformWindow;
|
||||
use crate::{AnyWindowHandle, DisplayId, PlatformDisplay, WindowOptions};
|
||||
|
||||
pub trait Client {
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn open_window(
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
pub trait ClientDispatcher: Send + Sync {
|
||||
fn dispatch_on_main_thread(&self);
|
||||
}
|
||||
@@ -1,50 +1,91 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
//todo!(linux): remove
|
||||
// todo(linux): remove
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use crate::platform::linux::client_dispatcher::ClientDispatcher;
|
||||
use crate::{PlatformDispatcher, TaskLabel};
|
||||
use async_task::Runnable;
|
||||
use calloop::{
|
||||
channel::{self, Sender},
|
||||
timer::TimeoutAction,
|
||||
EventLoop,
|
||||
};
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
panic,
|
||||
sync::Arc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::{thread, time::Duration};
|
||||
use util::ResultExt;
|
||||
|
||||
struct TimerAfter {
|
||||
duration: Duration,
|
||||
runnable: Runnable,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxDispatcher {
|
||||
client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync>,
|
||||
parker: Mutex<Parker>,
|
||||
timed_tasks: Mutex<Vec<(Instant, Runnable)>>,
|
||||
main_sender: flume::Sender<Runnable>,
|
||||
main_sender: Sender<Runnable>,
|
||||
timer_sender: Sender<TimerAfter>,
|
||||
background_sender: flume::Sender<Runnable>,
|
||||
_background_thread: thread::JoinHandle<()>,
|
||||
_background_threads: Vec<thread::JoinHandle<()>>,
|
||||
main_thread_id: thread::ThreadId,
|
||||
}
|
||||
|
||||
impl LinuxDispatcher {
|
||||
pub fn new(
|
||||
main_sender: flume::Sender<Runnable>,
|
||||
client_dispatcher: &Arc<dyn ClientDispatcher + Send + Sync>,
|
||||
) -> Self {
|
||||
pub fn new(main_sender: Sender<Runnable>) -> Self {
|
||||
let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
|
||||
let background_thread = thread::spawn(move || {
|
||||
profiling::register_thread!("background");
|
||||
for runnable in background_receiver {
|
||||
let _ignore_panic = panic::catch_unwind(|| runnable.run());
|
||||
}
|
||||
let thread_count = std::thread::available_parallelism()
|
||||
.map(|i| i.get())
|
||||
.unwrap_or(1);
|
||||
|
||||
let mut background_threads = (0..thread_count)
|
||||
.map(|_| {
|
||||
let receiver = background_receiver.clone();
|
||||
std::thread::spawn(move || {
|
||||
for runnable in receiver {
|
||||
runnable.run();
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (timer_sender, timer_channel) = calloop::channel::channel::<TimerAfter>();
|
||||
let timer_thread = std::thread::spawn(|| {
|
||||
let mut event_loop: EventLoop<()> =
|
||||
EventLoop::try_new().expect("Failed to initialize timer loop!");
|
||||
|
||||
let handle = event_loop.handle();
|
||||
let timer_handle = event_loop.handle();
|
||||
handle
|
||||
.insert_source(timer_channel, move |e, _, _| {
|
||||
if let channel::Event::Msg(timer) = e {
|
||||
// This has to be in an option to satisfy the borrow checker. The callback below should only be scheduled once.
|
||||
let mut runnable = Some(timer.runnable);
|
||||
timer_handle
|
||||
.insert_source(
|
||||
calloop::timer::Timer::from_duration(timer.duration),
|
||||
move |e, _, _| {
|
||||
if let Some(runnable) = runnable.take() {
|
||||
runnable.run();
|
||||
}
|
||||
TimeoutAction::Drop
|
||||
},
|
||||
)
|
||||
.expect("Failed to start timer");
|
||||
}
|
||||
})
|
||||
.expect("Failed to start timer thread");
|
||||
|
||||
event_loop.run(None, &mut (), |_| {}).log_err();
|
||||
});
|
||||
|
||||
background_threads.push(timer_thread);
|
||||
|
||||
Self {
|
||||
client_dispatcher: Arc::clone(client_dispatcher),
|
||||
parker: Mutex::new(Parker::new()),
|
||||
timed_tasks: Mutex::new(Vec::new()),
|
||||
main_sender,
|
||||
timer_sender,
|
||||
background_sender,
|
||||
_background_thread: background_thread,
|
||||
_background_threads: background_threads,
|
||||
main_thread_id: thread::current().id(),
|
||||
}
|
||||
}
|
||||
@@ -60,29 +101,19 @@ impl PlatformDispatcher for LinuxDispatcher {
|
||||
}
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
self.main_sender.send(runnable).unwrap();
|
||||
self.client_dispatcher.dispatch_on_main_thread();
|
||||
self.main_sender
|
||||
.send(runnable)
|
||||
.expect("Main thread is gone");
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||
let moment = Instant::now() + duration;
|
||||
let mut timed_tasks = self.timed_tasks.lock();
|
||||
timed_tasks.push((moment, runnable));
|
||||
timed_tasks.sort_unstable_by(|(a, _), (b, _)| b.cmp(a));
|
||||
self.timer_sender
|
||||
.send(TimerAfter { duration, runnable })
|
||||
.expect("Timer thread has died");
|
||||
}
|
||||
|
||||
fn tick(&self, background_only: bool) -> bool {
|
||||
let mut timed_tasks = self.timed_tasks.lock();
|
||||
let old_count = timed_tasks.len();
|
||||
while let Some(&(moment, _)) = timed_tasks.last() {
|
||||
if moment <= Instant::now() {
|
||||
let (_, runnable) = timed_tasks.pop().unwrap();
|
||||
runnable.run();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
timed_tasks.len() != old_count
|
||||
false
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
@@ -10,6 +11,7 @@ use std::{
|
||||
|
||||
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
|
||||
use async_task::Runnable;
|
||||
use calloop::{EventLoop, LoopHandle, LoopSignal};
|
||||
use flume::{Receiver, Sender};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::Mutex;
|
||||
@@ -17,9 +19,7 @@ use time::UtcOffset;
|
||||
use wayland_client::Connection;
|
||||
|
||||
use crate::platform::linux::client::Client;
|
||||
use crate::platform::linux::client_dispatcher::ClientDispatcher;
|
||||
use crate::platform::linux::wayland::{WaylandClient, WaylandClientDispatcher};
|
||||
use crate::platform::{X11Client, X11ClientDispatcher, XcbAtoms};
|
||||
use crate::platform::linux::wayland::WaylandClient;
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions,
|
||||
@@ -27,12 +27,14 @@ use crate::{
|
||||
SemanticVersion, Task, WindowOptions,
|
||||
};
|
||||
|
||||
use super::x11::X11Client;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Callbacks {
|
||||
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||
become_active: Option<Box<dyn FnMut()>>,
|
||||
resign_active: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) quit: Option<Box<dyn FnMut()>>,
|
||||
quit: Option<Box<dyn FnMut()>>,
|
||||
reopen: Option<Box<dyn FnMut()>>,
|
||||
event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
||||
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
@@ -41,12 +43,13 @@ pub(crate) struct Callbacks {
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatformInner {
|
||||
pub(crate) event_loop: RefCell<EventLoop<'static, ()>>,
|
||||
pub(crate) loop_handle: Rc<LoopHandle<'static, ()>>,
|
||||
pub(crate) loop_signal: LoopSignal,
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
pub(crate) main_receiver: flume::Receiver<Runnable>,
|
||||
pub(crate) text_system: Arc<LinuxTextSystem>,
|
||||
pub(crate) callbacks: Mutex<Callbacks>,
|
||||
pub(crate) state: Mutex<LinuxPlatformState>,
|
||||
pub(crate) callbacks: RefCell<Callbacks>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatform {
|
||||
@@ -54,10 +57,6 @@ pub(crate) struct LinuxPlatform {
|
||||
inner: Rc<LinuxPlatformInner>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatformState {
|
||||
pub(crate) quit_requested: bool,
|
||||
}
|
||||
|
||||
impl Default for LinuxPlatform {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -69,90 +68,41 @@ impl LinuxPlatform {
|
||||
let wayland_display = env::var_os("WAYLAND_DISPLAY");
|
||||
let use_wayland = wayland_display.is_some() && !wayland_display.unwrap().is_empty();
|
||||
|
||||
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
|
||||
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
|
||||
let text_system = Arc::new(LinuxTextSystem::new());
|
||||
let callbacks = Mutex::new(Callbacks::default());
|
||||
let state = Mutex::new(LinuxPlatformState {
|
||||
quit_requested: false,
|
||||
let callbacks = RefCell::new(Callbacks::default());
|
||||
|
||||
let event_loop = EventLoop::try_new().unwrap();
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(main_receiver, |event, _, _| {
|
||||
if let calloop::channel::Event::Msg(runnable) = event {
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
|
||||
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender));
|
||||
|
||||
let inner = Rc::new(LinuxPlatformInner {
|
||||
loop_handle: Rc::new(event_loop.handle()),
|
||||
loop_signal: event_loop.get_signal(),
|
||||
event_loop: RefCell::new(event_loop),
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
|
||||
text_system,
|
||||
callbacks,
|
||||
});
|
||||
|
||||
if use_wayland {
|
||||
Self::new_wayland(main_sender, main_receiver, text_system, callbacks, state)
|
||||
Self {
|
||||
client: Rc::new(WaylandClient::new(Rc::clone(&inner))),
|
||||
inner,
|
||||
}
|
||||
} else {
|
||||
Self::new_x11(main_sender, main_receiver, text_system, callbacks, state)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_wayland(
|
||||
main_sender: Sender<Runnable>,
|
||||
main_receiver: Receiver<Runnable>,
|
||||
text_system: Arc<LinuxTextSystem>,
|
||||
callbacks: Mutex<Callbacks>,
|
||||
state: Mutex<LinuxPlatformState>,
|
||||
) -> Self {
|
||||
let conn = Arc::new(Connection::connect_to_env().unwrap());
|
||||
let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
|
||||
Arc::new(WaylandClientDispatcher::new(&conn));
|
||||
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
|
||||
let inner = Rc::new(LinuxPlatformInner {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
|
||||
main_receiver,
|
||||
text_system,
|
||||
callbacks,
|
||||
state,
|
||||
});
|
||||
let client = Rc::new(WaylandClient::new(Rc::clone(&inner), Arc::clone(&conn)));
|
||||
Self {
|
||||
client,
|
||||
inner: Rc::clone(&inner),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_x11(
|
||||
main_sender: Sender<Runnable>,
|
||||
main_receiver: Receiver<Runnable>,
|
||||
text_system: Arc<LinuxTextSystem>,
|
||||
callbacks: Mutex<Callbacks>,
|
||||
state: Mutex<LinuxPlatformState>,
|
||||
) -> Self {
|
||||
let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
|
||||
None,
|
||||
&[xcb::Extension::Present, xcb::Extension::Xkb],
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let xkb_ver = xcb_connection
|
||||
.wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
|
||||
wanted_major: xcb::xkb::MAJOR_VERSION as u16,
|
||||
wanted_minor: xcb::xkb::MINOR_VERSION as u16,
|
||||
}))
|
||||
.unwrap();
|
||||
assert!(xkb_ver.supported());
|
||||
|
||||
let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
|
||||
let xcb_connection = Arc::new(xcb_connection);
|
||||
let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
|
||||
Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index));
|
||||
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
|
||||
let inner = Rc::new(LinuxPlatformInner {
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
|
||||
main_receiver,
|
||||
text_system,
|
||||
callbacks,
|
||||
state,
|
||||
});
|
||||
let client = Rc::new(X11Client::new(
|
||||
Rc::clone(&inner),
|
||||
xcb_connection,
|
||||
x_root_index,
|
||||
atoms,
|
||||
));
|
||||
Self {
|
||||
client,
|
||||
inner: Rc::clone(&inner),
|
||||
Self {
|
||||
client: X11Client::new(Rc::clone(&inner)),
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,26 +121,39 @@ impl Platform for LinuxPlatform {
|
||||
}
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
self.client.run(on_finish_launching)
|
||||
on_finish_launching();
|
||||
self.inner
|
||||
.event_loop
|
||||
.borrow_mut()
|
||||
.run(None, &mut (), |data| {})
|
||||
.expect("Run loop failed");
|
||||
|
||||
let mut lock = self.inner.callbacks.borrow_mut();
|
||||
if let Some(mut fun) = lock.quit.take() {
|
||||
drop(lock);
|
||||
fun();
|
||||
let mut lock = self.inner.callbacks.borrow_mut();
|
||||
lock.quit = Some(fun);
|
||||
}
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
self.inner.state.lock().quit_requested = true;
|
||||
self.inner.loop_signal.stop();
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn restart(&self) {}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn activate(&self, ignoring_other_apps: bool) {}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn hide(&self) {}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn hide_other_apps(&self) {}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn unhide_other_apps(&self) {}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
@@ -201,7 +164,7 @@ impl Platform for LinuxPlatform {
|
||||
self.client.display(id)
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||
None
|
||||
}
|
||||
@@ -219,7 +182,7 @@ impl Platform for LinuxPlatform {
|
||||
}
|
||||
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
|
||||
self.inner.callbacks.lock().open_urls = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().open_urls = Some(callback);
|
||||
}
|
||||
|
||||
fn prompt_for_paths(
|
||||
@@ -306,35 +269,35 @@ impl Platform for LinuxPlatform {
|
||||
}
|
||||
|
||||
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().become_active = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().become_active = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().resign_active = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().resign_active = Some(callback);
|
||||
}
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().quit = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().quit = Some(callback);
|
||||
}
|
||||
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().reopen = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().reopen = Some(callback);
|
||||
}
|
||||
|
||||
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
||||
self.inner.callbacks.lock().event = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().event = Some(callback);
|
||||
}
|
||||
|
||||
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
||||
self.inner.callbacks.lock().app_menu_action = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().app_menu_action = Some(callback);
|
||||
}
|
||||
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().will_open_app_menu = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().will_open_app_menu = Some(callback);
|
||||
}
|
||||
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
||||
self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
|
||||
self.inner.callbacks.borrow_mut().validate_app_menu_command = Some(callback);
|
||||
}
|
||||
|
||||
fn os_name(&self) -> &'static str {
|
||||
@@ -365,7 +328,7 @@ impl Platform for LinuxPlatform {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
|
||||
|
||||
fn local_timezone(&self) -> UtcOffset {
|
||||
@@ -376,18 +339,18 @@ impl Platform for LinuxPlatform {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn set_cursor_style(&self, style: CursorStyle) {}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn write_to_clipboard(&self, item: ClipboardItem) {}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ impl LinuxTextSystem {
|
||||
pub(crate) fn new() -> Self {
|
||||
let mut font_system = FontSystem::new();
|
||||
|
||||
// todo!(linux) make font loading non-blocking
|
||||
// todo(linux) make font loading non-blocking
|
||||
font_system.db_mut().load_system_fonts();
|
||||
|
||||
Self(RwLock::new(LinuxTextSystemState {
|
||||
@@ -59,7 +59,7 @@ impl PlatformTextSystem for LinuxTextSystem {
|
||||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
// todo!(linux) ensure that this integrates with platform font loading
|
||||
// todo(linux) ensure that this integrates with platform font loading
|
||||
// do we need to do more than call load_system_fonts()?
|
||||
fn all_font_names(&self) -> Vec<String> {
|
||||
self.0
|
||||
@@ -71,13 +71,13 @@ impl PlatformTextSystem for LinuxTextSystem {
|
||||
.collect()
|
||||
}
|
||||
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn all_font_families(&self) -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn font_id(&self, font: &Font) -> Result<FontId> {
|
||||
// todo!(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit?
|
||||
// todo(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit?
|
||||
let lock = self.0.upgradable_read();
|
||||
if let Some(font_id) = lock.font_selections.get(font) {
|
||||
Ok(*font_id)
|
||||
@@ -127,13 +127,13 @@ impl PlatformTextSystem for LinuxTextSystem {
|
||||
FontMetrics {
|
||||
units_per_em: metrics.units_per_em as u32,
|
||||
ascent: metrics.ascent,
|
||||
descent: -metrics.descent, // todo!(linux) confirm this is correct
|
||||
descent: -metrics.descent, // todo(linux) confirm this is correct
|
||||
line_gap: metrics.leading,
|
||||
underline_position: metrics.underline_offset,
|
||||
underline_thickness: metrics.stroke_size,
|
||||
cap_height: metrics.cap_height,
|
||||
x_height: metrics.x_height,
|
||||
// todo!(linux): Compute this correctly
|
||||
// todo(linux): Compute this correctly
|
||||
bounding_box: Bounds {
|
||||
origin: point(0.0, 0.0),
|
||||
size: size(metrics.max_width, metrics.ascent + metrics.descent),
|
||||
@@ -146,7 +146,7 @@ impl PlatformTextSystem for LinuxTextSystem {
|
||||
let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]);
|
||||
let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]);
|
||||
let glyph_id = glyph_id.0 as u16;
|
||||
// todo!(linux): Compute this correctly
|
||||
// todo(linux): Compute this correctly
|
||||
// see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620
|
||||
Ok(Bounds {
|
||||
origin: point(0.0, 0.0),
|
||||
@@ -181,7 +181,7 @@ impl PlatformTextSystem for LinuxTextSystem {
|
||||
self.0.write().layout_line(text, font_size, runs)
|
||||
}
|
||||
|
||||
// todo!(linux) Confirm that this has been superseded by the LineWrapper
|
||||
// todo(linux) Confirm that this has been superseded by the LineWrapper
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
@@ -256,7 +256,7 @@ impl LinuxTextSystemState {
|
||||
}
|
||||
|
||||
fn is_emoji(&self, font_id: FontId) -> bool {
|
||||
// todo!(linux): implement this correctly
|
||||
// todo(linux): implement this correctly
|
||||
self.postscript_names_by_font_id
|
||||
.get(&font_id)
|
||||
.map_or(false, |postscript_name| {
|
||||
@@ -264,7 +264,7 @@ impl LinuxTextSystemState {
|
||||
})
|
||||
}
|
||||
|
||||
// todo!(linux) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system
|
||||
// todo(linux) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system
|
||||
fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let font = &self.fonts[params.font_id.0];
|
||||
let font_system = &mut self.font_system;
|
||||
@@ -297,7 +297,7 @@ impl LinuxTextSystemState {
|
||||
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
|
||||
Err(anyhow!("glyph bounds are empty"))
|
||||
} else {
|
||||
// todo!(linux) handle subpixel variants
|
||||
// todo(linux) handle subpixel variants
|
||||
let bitmap_size = glyph_bounds.size;
|
||||
let font = &self.fonts[params.font_id.0];
|
||||
let font_system = &mut self.font_system;
|
||||
@@ -320,13 +320,13 @@ impl LinuxTextSystemState {
|
||||
}
|
||||
}
|
||||
|
||||
// todo!(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer
|
||||
// todo(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer
|
||||
#[profiling::function]
|
||||
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
let mut attrs_list = AttrsList::new(Attrs::new());
|
||||
let mut offs = 0;
|
||||
for run in font_runs {
|
||||
// todo!(linux) We need to check we are doing utf properly
|
||||
// todo(linux) We need to check we are doing utf properly
|
||||
let font = &self.fonts[run.font_id.0];
|
||||
let font = self.font_system.db().face(font.id()).unwrap();
|
||||
attrs_list.add_span(
|
||||
@@ -343,11 +343,11 @@ impl LinuxTextSystemState {
|
||||
let layout = line.layout(
|
||||
&mut self.font_system,
|
||||
font_size.0,
|
||||
f32::MAX, // todo!(linux) we don't have a width cause this should technically not be wrapped I believe
|
||||
f32::MAX, // todo(linux) we don't have a width cause this should technically not be wrapped I believe
|
||||
cosmic_text::Wrap::None,
|
||||
);
|
||||
let mut runs = Vec::new();
|
||||
// todo!(linux) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering
|
||||
// todo(linux) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering
|
||||
let layout = layout.first().unwrap();
|
||||
for glyph in &layout.glyphs {
|
||||
let font_id = glyph.font_id;
|
||||
@@ -358,7 +358,7 @@ impl LinuxTextSystemState {
|
||||
.unwrap(),
|
||||
);
|
||||
let mut glyphs = SmallVec::new();
|
||||
// todo!(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
|
||||
// todo(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
|
||||
glyphs.push(ShapedGlyph {
|
||||
id: GlyphId(glyph.glyph_id as u32),
|
||||
position: point((glyph.x).into(), glyph.y.into()),
|
||||
|
||||
@@ -4,15 +4,66 @@ use crate::{Keystroke, Modifiers};
|
||||
|
||||
impl Keystroke {
|
||||
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
|
||||
let mut modifiers = modifiers;
|
||||
|
||||
let key_utf32 = state.key_get_utf32(keycode);
|
||||
let key_utf8 = state.key_get_utf8(keycode);
|
||||
let key_sym = state.key_get_one_sym(keycode);
|
||||
|
||||
// The logic here tries to replicate the logic in `../mac/events.rs`
|
||||
// "Consumed" modifiers are modifiers that have been used to translate a key, for example
|
||||
// pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
|
||||
// Notes:
|
||||
// - macOS gets the key character directly ("."), xkb gives us the key name ("period")
|
||||
// - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
|
||||
// - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
|
||||
|
||||
let mut handle_consumed_modifiers = true;
|
||||
let key = match key_sym {
|
||||
Keysym::Return => "enter".to_owned(),
|
||||
Keysym::Prior => "pageup".to_owned(),
|
||||
Keysym::Next => "pagedown".to_owned(),
|
||||
_ => xkb::keysym_get_name(key_sym).to_lowercase(),
|
||||
|
||||
Keysym::comma => ",".to_owned(),
|
||||
Keysym::period => ".".to_owned(),
|
||||
Keysym::less => "<".to_owned(),
|
||||
Keysym::greater => ">".to_owned(),
|
||||
Keysym::slash => "/".to_owned(),
|
||||
Keysym::question => "?".to_owned(),
|
||||
|
||||
Keysym::semicolon => ";".to_owned(),
|
||||
Keysym::colon => ":".to_owned(),
|
||||
Keysym::apostrophe => "'".to_owned(),
|
||||
Keysym::quotedbl => "\"".to_owned(),
|
||||
|
||||
Keysym::bracketleft => "[".to_owned(),
|
||||
Keysym::braceleft => "{".to_owned(),
|
||||
Keysym::bracketright => "]".to_owned(),
|
||||
Keysym::braceright => "}".to_owned(),
|
||||
Keysym::backslash => "\\".to_owned(),
|
||||
Keysym::bar => "|".to_owned(),
|
||||
|
||||
Keysym::grave => "`".to_owned(),
|
||||
Keysym::asciitilde => "~".to_owned(),
|
||||
Keysym::exclam => "!".to_owned(),
|
||||
Keysym::at => "@".to_owned(),
|
||||
Keysym::numbersign => "#".to_owned(),
|
||||
Keysym::dollar => "$".to_owned(),
|
||||
Keysym::percent => "%".to_owned(),
|
||||
Keysym::asciicircum => "^".to_owned(),
|
||||
Keysym::ampersand => "&".to_owned(),
|
||||
Keysym::asterisk => "*".to_owned(),
|
||||
Keysym::parenleft => "(".to_owned(),
|
||||
Keysym::parenright => ")".to_owned(),
|
||||
Keysym::minus => "-".to_owned(),
|
||||
Keysym::underscore => "_".to_owned(),
|
||||
Keysym::equal => "=".to_owned(),
|
||||
Keysym::plus => "+".to_owned(),
|
||||
|
||||
_ => {
|
||||
handle_consumed_modifiers = false;
|
||||
xkb::keysym_get_name(key_sym).to_lowercase()
|
||||
}
|
||||
};
|
||||
|
||||
// Ignore control characters (and DEL) for the purposes of ime_key,
|
||||
@@ -21,6 +72,15 @@ impl Keystroke {
|
||||
&& !key_utf8.is_empty())
|
||||
.then_some(key_utf8);
|
||||
|
||||
if handle_consumed_modifiers {
|
||||
let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
|
||||
let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
|
||||
|
||||
if modifiers.shift && is_shift_consumed {
|
||||
modifiers.shift = false;
|
||||
}
|
||||
}
|
||||
|
||||
Keystroke {
|
||||
modifiers,
|
||||
key,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
//todo!(linux): remove this once the relevant functionality has been implemented
|
||||
// todo(linux): remove this once the relevant functionality has been implemented
|
||||
#![allow(unused_variables)]
|
||||
|
||||
pub(crate) use client::*;
|
||||
pub(crate) use client_dispatcher::*;
|
||||
|
||||
mod client;
|
||||
mod client_dispatcher;
|
||||
mod display;
|
||||
mod window;
|
||||
|
||||
@@ -3,10 +3,12 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use smol::Timer;
|
||||
use calloop::timer::{TimeoutAction, Timer};
|
||||
use calloop::LoopHandle;
|
||||
use calloop_wayland_source::WaylandSource;
|
||||
use wayland_backend::client::ObjectId;
|
||||
use wayland_backend::protocol::WEnum;
|
||||
use wayland_client::globals::{registry_queue_init, GlobalListContents};
|
||||
use wayland_client::protocol::wl_callback::WlCallback;
|
||||
use wayland_client::protocol::wl_pointer::AxisRelativeDirection;
|
||||
use wayland_client::{
|
||||
@@ -16,7 +18,7 @@ use wayland_client::{
|
||||
wl_shm, wl_shm_pool,
|
||||
wl_surface::{self, WlSurface},
|
||||
},
|
||||
Connection, Dispatch, EventQueue, Proxy, QueueHandle,
|
||||
Connection, Dispatch, Proxy, QueueHandle,
|
||||
};
|
||||
use wayland_protocols::wp::fractional_scale::v1::client::{
|
||||
wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
|
||||
@@ -39,18 +41,17 @@ use crate::{
|
||||
ScrollWheelEvent, TouchPhase, WindowOptions,
|
||||
};
|
||||
|
||||
const MIN_KEYCODE: u32 = 8; // used to convert evdev scancode to xkb scancode
|
||||
/// Used to convert evdev scancode to xkb scancode
|
||||
const MIN_KEYCODE: u32 = 8;
|
||||
|
||||
pub(crate) struct WaylandClientStateInner {
|
||||
compositor: Option<wl_compositor::WlCompositor>,
|
||||
buffer: Option<wl_buffer::WlBuffer>,
|
||||
wm_base: Option<xdg_wm_base::XdgWmBase>,
|
||||
compositor: wl_compositor::WlCompositor,
|
||||
wm_base: xdg_wm_base::XdgWmBase,
|
||||
viewporter: Option<wp_viewporter::WpViewporter>,
|
||||
fractional_scale_manager: Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
|
||||
decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
|
||||
windows: Vec<(xdg_surface::XdgSurface, Rc<WaylandWindowState>)>,
|
||||
platform_inner: Rc<LinuxPlatformInner>,
|
||||
wl_seat: Option<wl_seat::WlSeat>,
|
||||
keymap_state: Option<xkb::State>,
|
||||
repeat: KeyRepeat,
|
||||
modifiers: Modifiers,
|
||||
@@ -59,6 +60,7 @@ pub(crate) struct WaylandClientStateInner {
|
||||
button_pressed: Option<MouseButton>,
|
||||
mouse_focused_window: Option<Rc<WaylandWindowState>>,
|
||||
keyboard_focused_window: Option<Rc<WaylandWindowState>>,
|
||||
loop_handle: Rc<LoopHandle<'static, ()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -73,24 +75,40 @@ pub(crate) struct KeyRepeat {
|
||||
|
||||
pub(crate) struct WaylandClient {
|
||||
platform_inner: Rc<LinuxPlatformInner>,
|
||||
conn: Arc<Connection>,
|
||||
state: WaylandClientState,
|
||||
event_queue: Mutex<EventQueue<WaylandClientState>>,
|
||||
qh: Arc<QueueHandle<WaylandClientState>>,
|
||||
}
|
||||
|
||||
const WL_SEAT_VERSION: u32 = 4;
|
||||
|
||||
impl WaylandClient {
|
||||
pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>, conn: Arc<Connection>) -> Self {
|
||||
let state = WaylandClientState(Rc::new(RefCell::new(WaylandClientStateInner {
|
||||
compositor: None,
|
||||
buffer: None,
|
||||
wm_base: None,
|
||||
viewporter: None,
|
||||
fractional_scale_manager: None,
|
||||
decoration_manager: None,
|
||||
pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>) -> Self {
|
||||
let conn = Connection::connect_to_env().unwrap();
|
||||
|
||||
let (globals, mut event_queue) = registry_queue_init::<WaylandClientState>(&conn).unwrap();
|
||||
let qh = event_queue.handle();
|
||||
|
||||
globals.contents().with_list(|list| {
|
||||
for global in list {
|
||||
if global.interface == "wl_seat" {
|
||||
globals.registry().bind::<wl_seat::WlSeat, _, _>(
|
||||
global.name,
|
||||
WL_SEAT_VERSION,
|
||||
&qh,
|
||||
(),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner {
|
||||
compositor: globals.bind(&qh, 1..=1, ()).unwrap(),
|
||||
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
|
||||
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
|
||||
fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
|
||||
decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
|
||||
windows: Vec::new(),
|
||||
platform_inner: Rc::clone(&linux_platform_inner),
|
||||
wl_seat: None,
|
||||
keymap_state: None,
|
||||
repeat: KeyRepeat {
|
||||
characters_per_second: 16,
|
||||
@@ -110,41 +128,28 @@ impl WaylandClient {
|
||||
button_pressed: None,
|
||||
mouse_focused_window: None,
|
||||
keyboard_focused_window: None,
|
||||
})));
|
||||
let event_queue: EventQueue<WaylandClientState> = conn.new_event_queue();
|
||||
let qh = event_queue.handle();
|
||||
loop_handle: Rc::clone(&linux_platform_inner.loop_handle),
|
||||
}));
|
||||
|
||||
let source = WaylandSource::new(conn, event_queue);
|
||||
|
||||
let mut state = WaylandClientState(Rc::clone(&state_inner));
|
||||
linux_platform_inner
|
||||
.loop_handle
|
||||
.insert_source(source, move |_, queue, _| {
|
||||
queue.dispatch_pending(&mut state)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
platform_inner: linux_platform_inner,
|
||||
conn,
|
||||
state,
|
||||
event_queue: Mutex::new(event_queue),
|
||||
state: WaylandClientState(state_inner),
|
||||
qh: Arc::new(qh),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client for WaylandClient {
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
let display = self.conn.display();
|
||||
let mut eq = self.event_queue.lock();
|
||||
let _registry = display.get_registry(&self.qh, ());
|
||||
|
||||
eq.roundtrip(&mut self.state.clone()).unwrap();
|
||||
|
||||
on_finish_launching();
|
||||
while !self.platform_inner.state.lock().quit_requested {
|
||||
eq.flush().unwrap();
|
||||
eq.dispatch_pending(&mut self.state.clone()).unwrap();
|
||||
if let Some(guard) = self.conn.prepare_read() {
|
||||
guard.read().unwrap();
|
||||
eq.dispatch_pending(&mut self.state.clone()).unwrap();
|
||||
}
|
||||
if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
Vec::new()
|
||||
}
|
||||
@@ -160,10 +165,8 @@ impl Client for WaylandClient {
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
let mut state = self.state.0.borrow_mut();
|
||||
|
||||
let wm_base = state.wm_base.as_ref().unwrap();
|
||||
let compositor = state.compositor.as_ref().unwrap();
|
||||
let wl_surface = compositor.create_surface(&self.qh, ());
|
||||
let xdg_surface = wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
|
||||
let wl_surface = state.compositor.create_surface(&self.qh, ());
|
||||
let xdg_surface = state.wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
|
||||
let toplevel = xdg_surface.get_toplevel(&self.qh, ());
|
||||
let wl_surface = Arc::new(wl_surface);
|
||||
|
||||
@@ -180,7 +183,7 @@ impl Client for WaylandClient {
|
||||
let decoration =
|
||||
decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, xdg_surface.id());
|
||||
|
||||
// todo!(linux) - options.titlebar is lacking information required for wayland.
|
||||
// todo(linux) - options.titlebar is lacking information required for wayland.
|
||||
// Especially, whether a titlebar is wanted in itself.
|
||||
//
|
||||
// Removing the titlebar also removes the entire window frame (ie. the ability to
|
||||
@@ -200,8 +203,7 @@ impl Client for WaylandClient {
|
||||
wl_surface.frame(&self.qh, wl_surface.clone());
|
||||
wl_surface.commit();
|
||||
|
||||
let window_state = Rc::new(WaylandWindowState::new(
|
||||
&self.conn,
|
||||
let window_state: Rc<WaylandWindowState> = Rc::new(WaylandWindowState::new(
|
||||
wl_surface.clone(),
|
||||
viewport,
|
||||
Arc::new(toplevel),
|
||||
@@ -217,62 +219,27 @@ impl Client for WaylandClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for WaylandClientState {
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_: &(),
|
||||
_: &GlobalListContents,
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
let mut state = state.0.borrow_mut();
|
||||
if let wl_registry::Event::Global {
|
||||
name, interface, ..
|
||||
} = event
|
||||
{
|
||||
match &interface[..] {
|
||||
"wl_compositor" => {
|
||||
let compositor =
|
||||
registry.bind::<wl_compositor::WlCompositor, _, _>(name, 1, qh, ());
|
||||
state.compositor = Some(compositor);
|
||||
match event {
|
||||
wl_registry::Event::Global {
|
||||
name,
|
||||
interface,
|
||||
version: _,
|
||||
} => {
|
||||
if interface.as_str() == "wl_seat" {
|
||||
registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qh, ());
|
||||
}
|
||||
"xdg_wm_base" => {
|
||||
let wm_base = registry.bind::<xdg_wm_base::XdgWmBase, _, _>(name, 1, qh, ());
|
||||
state.wm_base = Some(wm_base);
|
||||
}
|
||||
"wl_seat" => {
|
||||
let seat = registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qh, ());
|
||||
state.wl_seat = Some(seat);
|
||||
}
|
||||
"wp_fractional_scale_manager_v1" => {
|
||||
let manager = registry
|
||||
.bind::<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, _, _>(
|
||||
name,
|
||||
1,
|
||||
qh,
|
||||
(),
|
||||
);
|
||||
state.fractional_scale_manager = Some(manager);
|
||||
}
|
||||
"wp_viewporter" => {
|
||||
let view_porter =
|
||||
registry.bind::<wp_viewporter::WpViewporter, _, _>(name, 1, qh, ());
|
||||
state.viewporter = Some(view_porter);
|
||||
}
|
||||
"zxdg_decoration_manager_v1" => {
|
||||
// Unstable and optional
|
||||
let decoration_manager = registry
|
||||
.bind::<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, _, _>(
|
||||
name,
|
||||
1,
|
||||
qh,
|
||||
(),
|
||||
);
|
||||
state.decoration_manager = Some(decoration_manager);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
wl_registry::Event::GlobalRemove { name: _ } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,7 +334,7 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ()> for WaylandClientState {
|
||||
true
|
||||
}
|
||||
});
|
||||
state.platform_inner.state.lock().quit_requested |= state.windows.is_empty();
|
||||
state.platform_inner.loop_signal.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +482,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
|
||||
wl_keyboard::KeyState::Pressed => {
|
||||
let input = PlatformInput::KeyDown(KeyDownEvent {
|
||||
keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
|
||||
is_held: false, // todo!(linux)
|
||||
is_held: false, // todo(linux)
|
||||
});
|
||||
|
||||
focused_window.handle_input(input.clone());
|
||||
@@ -529,34 +496,29 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
|
||||
let id = state.repeat.current_id;
|
||||
let this = this.clone();
|
||||
|
||||
let timer = Timer::from_duration(delay);
|
||||
let state_ = Rc::clone(&this.0);
|
||||
state
|
||||
.platform_inner
|
||||
.foreground_executor
|
||||
.spawn(async move {
|
||||
let mut wait_time = delay;
|
||||
.loop_handle
|
||||
.insert_source(timer, move |event, _metadata, shared_data| {
|
||||
let state_ = state_.borrow_mut();
|
||||
let is_repeating = id == state_.repeat.current_id
|
||||
&& state_.repeat.current_keysym.is_some()
|
||||
&& state_.keyboard_focused_window.is_some();
|
||||
|
||||
loop {
|
||||
Timer::after(wait_time).await;
|
||||
|
||||
let state = this.0.borrow_mut();
|
||||
let is_repeating = id == state.repeat.current_id
|
||||
&& state.repeat.current_keysym.is_some()
|
||||
&& state.keyboard_focused_window.is_some();
|
||||
|
||||
if !is_repeating {
|
||||
return;
|
||||
}
|
||||
|
||||
state
|
||||
.keyboard_focused_window
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.handle_input(input.clone());
|
||||
|
||||
wait_time = Duration::from_secs(1) / rate;
|
||||
if !is_repeating {
|
||||
return TimeoutAction::Drop;
|
||||
}
|
||||
|
||||
state_
|
||||
.keyboard_focused_window
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.handle_input(input.clone());
|
||||
|
||||
TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
|
||||
})
|
||||
.detach();
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
wl_keyboard::KeyState::Released => {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use wayland_client::{Connection, EventQueue};
|
||||
|
||||
use crate::platform::linux::client_dispatcher::ClientDispatcher;
|
||||
|
||||
pub(crate) struct WaylandClientDispatcher {
|
||||
conn: Arc<Connection>,
|
||||
event_queue: Arc<EventQueue<Connection>>,
|
||||
}
|
||||
|
||||
impl WaylandClientDispatcher {
|
||||
pub(crate) fn new(conn: &Arc<Connection>) -> Self {
|
||||
let event_queue = conn.new_event_queue();
|
||||
Self {
|
||||
conn: Arc::clone(conn),
|
||||
event_queue: Arc::new(event_queue),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WaylandClientDispatcher {
|
||||
fn drop(&mut self) {
|
||||
//todo!(linux)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientDispatcher for WaylandClientDispatcher {
|
||||
fn dispatch_on_main_thread(&self) {}
|
||||
}
|
||||
@@ -8,17 +8,17 @@ use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
|
||||
pub(crate) struct WaylandDisplay {}
|
||||
|
||||
impl PlatformDisplay for WaylandDisplay {
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn id(&self) -> DisplayId {
|
||||
DisplayId(123) // return some fake data so it doesn't panic
|
||||
}
|
||||
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn uuid(&self) -> anyhow::Result<Uuid> {
|
||||
Ok(Uuid::from_bytes([0; 16])) // return some fake data so it doesn't panic
|
||||
}
|
||||
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
Bounds {
|
||||
origin: Default::default(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::c_void;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
@@ -6,7 +7,6 @@ use std::sync::Arc;
|
||||
use blade_graphics as gpu;
|
||||
use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle};
|
||||
use futures::channel::oneshot::Receiver;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::{
|
||||
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
|
||||
};
|
||||
@@ -66,14 +66,15 @@ unsafe impl HasRawDisplayHandle for RawWindow {
|
||||
}
|
||||
|
||||
impl WaylandWindowInner {
|
||||
fn new(
|
||||
conn: &Arc<wayland_client::Connection>,
|
||||
wl_surf: &Arc<wl_surface::WlSurface>,
|
||||
bounds: Bounds<i32>,
|
||||
) -> Self {
|
||||
fn new(wl_surf: &Arc<wl_surface::WlSurface>, bounds: Bounds<i32>) -> Self {
|
||||
let raw = RawWindow {
|
||||
window: wl_surf.id().as_ptr().cast::<c_void>(),
|
||||
display: conn.backend().display_ptr().cast::<c_void>(),
|
||||
display: wl_surf
|
||||
.backend()
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.display_ptr()
|
||||
.cast::<c_void>(),
|
||||
};
|
||||
let gpu = Arc::new(
|
||||
unsafe {
|
||||
@@ -105,9 +106,8 @@ impl WaylandWindowInner {
|
||||
}
|
||||
|
||||
pub(crate) struct WaylandWindowState {
|
||||
conn: Arc<wayland_client::Connection>,
|
||||
inner: Mutex<WaylandWindowInner>,
|
||||
pub(crate) callbacks: Mutex<Callbacks>,
|
||||
inner: RefCell<WaylandWindowInner>,
|
||||
pub(crate) callbacks: RefCell<Callbacks>,
|
||||
pub(crate) surface: Arc<wl_surface::WlSurface>,
|
||||
pub(crate) toplevel: Arc<xdg_toplevel::XdgToplevel>,
|
||||
viewport: Option<wp_viewport::WpViewport>,
|
||||
@@ -115,7 +115,6 @@ pub(crate) struct WaylandWindowState {
|
||||
|
||||
impl WaylandWindowState {
|
||||
pub(crate) fn new(
|
||||
conn: &Arc<wayland_client::Connection>,
|
||||
wl_surf: Arc<wl_surface::WlSurface>,
|
||||
viewport: Option<wp_viewport::WpViewport>,
|
||||
toplevel: Arc<xdg_toplevel::XdgToplevel>,
|
||||
@@ -133,40 +132,39 @@ impl WaylandWindowState {
|
||||
size: Size {
|
||||
width: 500,
|
||||
height: 500,
|
||||
}, //todo!(implement)
|
||||
}, // todo(implement)
|
||||
},
|
||||
WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32),
|
||||
};
|
||||
|
||||
Self {
|
||||
conn: Arc::clone(conn),
|
||||
surface: Arc::clone(&wl_surf),
|
||||
inner: Mutex::new(WaylandWindowInner::new(&Arc::clone(conn), &wl_surf, bounds)),
|
||||
callbacks: Mutex::new(Callbacks::default()),
|
||||
inner: RefCell::new(WaylandWindowInner::new(&wl_surf, bounds)),
|
||||
callbacks: RefCell::new(Callbacks::default()),
|
||||
toplevel,
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&self) {
|
||||
let mut cb = self.callbacks.lock();
|
||||
let mut cb = self.callbacks.borrow_mut();
|
||||
if let Some(mut fun) = cb.request_frame.take() {
|
||||
drop(cb);
|
||||
fun();
|
||||
self.callbacks.lock().request_frame = Some(fun);
|
||||
self.callbacks.borrow_mut().request_frame = Some(fun);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size_and_scale(&self, width: i32, height: i32, scale: f32) {
|
||||
self.inner.lock().scale = scale;
|
||||
self.inner.lock().bounds.size.width = width;
|
||||
self.inner.lock().bounds.size.height = height;
|
||||
self.inner.lock().renderer.update_drawable_size(size(
|
||||
self.inner.borrow_mut().scale = scale;
|
||||
self.inner.borrow_mut().bounds.size.width = width;
|
||||
self.inner.borrow_mut().bounds.size.height = height;
|
||||
self.inner.borrow_mut().renderer.update_drawable_size(size(
|
||||
width as f64 * scale as f64,
|
||||
height as f64 * scale as f64,
|
||||
));
|
||||
|
||||
if let Some(ref mut fun) = self.callbacks.lock().resize {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().resize {
|
||||
fun(
|
||||
Size {
|
||||
width: px(width as f32),
|
||||
@@ -182,12 +180,12 @@ impl WaylandWindowState {
|
||||
}
|
||||
|
||||
pub fn resize(&self, width: i32, height: i32) {
|
||||
let scale = self.inner.lock().scale;
|
||||
let scale = self.inner.borrow_mut().scale;
|
||||
self.set_size_and_scale(width, height, scale);
|
||||
}
|
||||
|
||||
pub fn rescale(&self, scale: f32) {
|
||||
let bounds = self.inner.lock().bounds;
|
||||
let bounds = self.inner.borrow_mut().bounds;
|
||||
self.set_size_and_scale(bounds.size.width, bounds.size.height, scale)
|
||||
}
|
||||
|
||||
@@ -200,13 +198,13 @@ impl WaylandWindowState {
|
||||
/// of the decorations. This is because the state of the decorations
|
||||
/// is managed by the compositor and not the client.
|
||||
pub fn set_decoration_state(&self, state: WaylandDecorationState) {
|
||||
self.inner.lock().decoration_state = state;
|
||||
self.inner.borrow_mut().decoration_state = state;
|
||||
log::trace!("Window decorations are now handled by {:?}", state);
|
||||
// todo!(linux) - Handle this properly
|
||||
// todo(linux) - Handle this properly
|
||||
}
|
||||
|
||||
pub fn close(&self) {
|
||||
let mut callbacks = self.callbacks.lock();
|
||||
let mut callbacks = self.callbacks.borrow_mut();
|
||||
if let Some(fun) = callbacks.close.take() {
|
||||
fun()
|
||||
}
|
||||
@@ -214,13 +212,13 @@ impl WaylandWindowState {
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, input: PlatformInput) {
|
||||
if let Some(ref mut fun) = self.callbacks.lock().input {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
||||
if fun(input.clone()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
let mut inner = self.inner.lock();
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if let Some(ref mut input_handler) = inner.input_handler {
|
||||
if let Some(ime_key) = &event.keystroke.ime_key {
|
||||
input_handler.replace_text_in_range(None, ime_key);
|
||||
@@ -230,7 +228,7 @@ impl WaylandWindowState {
|
||||
}
|
||||
|
||||
pub fn set_focused(&self, focus: bool) {
|
||||
if let Some(ref mut fun) = self.callbacks.lock().active_status_change {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
|
||||
fun(focus);
|
||||
}
|
||||
}
|
||||
@@ -252,13 +250,13 @@ impl HasDisplayHandle for WaylandWindow {
|
||||
}
|
||||
|
||||
impl PlatformWindow for WaylandWindow {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
WindowBounds::Maximized
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
let inner = self.0.inner.lock();
|
||||
let inner = self.0.inner.borrow_mut();
|
||||
Size {
|
||||
width: Pixels(inner.bounds.size.width as f32),
|
||||
height: Pixels(inner.bounds.size.height as f32),
|
||||
@@ -266,48 +264,48 @@ impl PlatformWindow for WaylandWindow {
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
self.0.inner.lock().scale
|
||||
self.0.inner.borrow_mut().scale
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn titlebar_height(&self) -> Pixels {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn appearance(&self) -> WindowAppearance {
|
||||
WindowAppearance::Light
|
||||
}
|
||||
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn display(&self) -> Rc<dyn PlatformDisplay> {
|
||||
Rc::new(WaylandDisplay {})
|
||||
}
|
||||
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn mouse_position(&self) -> Point<Pixels> {
|
||||
Point::default()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn modifiers(&self) -> Modifiers {
|
||||
crate::Modifiers::default()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
|
||||
self.0.inner.lock().input_handler = Some(input_handler);
|
||||
self.0.inner.borrow_mut().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
|
||||
self.0.inner.lock().input_handler.take()
|
||||
self.0.inner.borrow_mut().input_handler.take()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn prompt(
|
||||
&self,
|
||||
level: PromptLevel,
|
||||
@@ -319,7 +317,7 @@ impl PlatformWindow for WaylandWindow {
|
||||
}
|
||||
|
||||
fn activate(&self) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
@@ -327,79 +325,75 @@ impl PlatformWindow for WaylandWindow {
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, edited: bool) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn show_character_palette(&self) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn minimize(&self) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn zoom(&self) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn toggle_full_screen(&self) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.lock().request_frame = Some(callback);
|
||||
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
||||
}
|
||||
|
||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
||||
self.0.callbacks.lock().input = Some(callback);
|
||||
self.0.callbacks.borrow_mut().input = Some(callback);
|
||||
}
|
||||
|
||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.lock().active_status_change = Some(callback);
|
||||
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.callbacks.lock().resize = Some(callback);
|
||||
self.0.callbacks.borrow_mut().resize = Some(callback);
|
||||
}
|
||||
|
||||
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.lock().moved = Some(callback);
|
||||
self.0.callbacks.borrow_mut().moved = Some(callback);
|
||||
}
|
||||
|
||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
|
||||
self.0.callbacks.lock().should_close = Some(callback);
|
||||
self.0.callbacks.borrow_mut().should_close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_close(&self, callback: Box<dyn FnOnce()>) {
|
||||
self.0.callbacks.lock().close = Some(callback);
|
||||
self.0.callbacks.borrow_mut().close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
// todo!(linux)
|
||||
// todo(linux)
|
||||
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
let mut inner = self.0.inner.lock();
|
||||
let mut inner = self.0.inner.borrow_mut();
|
||||
inner.renderer.draw(scene);
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
let inner = self.0.inner.lock();
|
||||
let inner = self.0.inner.borrow_mut();
|
||||
inner.renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool) {
|
||||
//todo!(linux)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
mod client;
|
||||
mod client_dispatcher;
|
||||
mod display;
|
||||
mod event;
|
||||
mod window;
|
||||
|
||||
pub(crate) use client::*;
|
||||
pub(crate) use client_dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use event::*;
|
||||
pub(crate) use window::*;
|
||||
|
||||
@@ -1,40 +1,60 @@
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use xcb::{x, Xid as _};
|
||||
use xkbcommon::xkb;
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
|
||||
use crate::platform::linux::client::Client;
|
||||
use crate::platform::{
|
||||
LinuxPlatformInner, PlatformWindow, X11Display, X11Window, X11WindowState, XcbAtoms,
|
||||
};
|
||||
use crate::platform::{LinuxPlatformInner, PlatformWindow};
|
||||
use crate::{
|
||||
AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size,
|
||||
TouchPhase, WindowOptions,
|
||||
};
|
||||
|
||||
use super::{X11Display, X11Window, X11WindowState, XcbAtoms};
|
||||
use calloop::generic::{FdWrapper, Generic};
|
||||
|
||||
pub(crate) struct X11ClientState {
|
||||
pub(crate) windows: HashMap<x::Window, Rc<X11WindowState>>,
|
||||
pub(crate) windows_to_refresh: HashSet<x::Window>,
|
||||
xkb: xkbcommon::xkb::State,
|
||||
}
|
||||
|
||||
pub(crate) struct X11Client {
|
||||
platform_inner: Rc<LinuxPlatformInner>,
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
xcb_connection: Rc<xcb::Connection>,
|
||||
x_root_index: i32,
|
||||
atoms: XcbAtoms,
|
||||
state: Mutex<X11ClientState>,
|
||||
refresh_millis: Cell<u64>,
|
||||
state: RefCell<X11ClientState>,
|
||||
}
|
||||
|
||||
impl X11Client {
|
||||
pub(crate) fn new(
|
||||
inner: Rc<LinuxPlatformInner>,
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
x_root_index: i32,
|
||||
atoms: XcbAtoms,
|
||||
) -> Self {
|
||||
pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
|
||||
let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
|
||||
None,
|
||||
&[
|
||||
xcb::Extension::Present,
|
||||
xcb::Extension::Xkb,
|
||||
xcb::Extension::RandR,
|
||||
],
|
||||
&[],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let xkb_ver = xcb_connection
|
||||
.wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
|
||||
wanted_major: xcb::xkb::MAJOR_VERSION as u16,
|
||||
wanted_minor: xcb::xkb::MINOR_VERSION as u16,
|
||||
}))
|
||||
.unwrap();
|
||||
assert!(xkb_ver.supported());
|
||||
|
||||
let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
|
||||
let xcb_connection = Rc::new(xcb_connection);
|
||||
let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
|
||||
let xkb_device_id = xkb::x11::get_core_keyboard_device_id(&xcb_connection);
|
||||
let xkb_keymap = xkb::x11::keymap_new_from_device(
|
||||
@@ -43,195 +63,235 @@ impl X11Client {
|
||||
xkb_device_id,
|
||||
xkb::KEYMAP_COMPILE_NO_FLAGS,
|
||||
);
|
||||
|
||||
let xkb_state =
|
||||
xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id);
|
||||
|
||||
Self {
|
||||
platform_inner: inner,
|
||||
xcb_connection,
|
||||
let client: Rc<X11Client> = Rc::new(Self {
|
||||
platform_inner: inner.clone(),
|
||||
xcb_connection: xcb_connection.clone(),
|
||||
x_root_index,
|
||||
atoms,
|
||||
state: Mutex::new(X11ClientState {
|
||||
refresh_millis: Cell::new(16),
|
||||
state: RefCell::new(X11ClientState {
|
||||
windows: HashMap::default(),
|
||||
windows_to_refresh: HashSet::default(),
|
||||
xkb: xkb_state,
|
||||
}),
|
||||
});
|
||||
|
||||
// Safety: Safe if xcb::Connection always returns a valid fd
|
||||
let fd = unsafe { FdWrapper::new(xcb_connection.clone()) };
|
||||
|
||||
inner
|
||||
.loop_handle
|
||||
.insert_source(
|
||||
Generic::new_with_error::<xcb::Error>(
|
||||
fd,
|
||||
calloop::Interest::READ,
|
||||
calloop::Mode::Level,
|
||||
),
|
||||
{
|
||||
let client = client.clone();
|
||||
move |readiness, _, _| {
|
||||
if readiness.readable || readiness.error {
|
||||
while let Some(event) = xcb_connection.poll_for_event()? {
|
||||
client.handle_event(event);
|
||||
}
|
||||
}
|
||||
Ok(calloop::PostAction::Continue)
|
||||
}
|
||||
},
|
||||
)
|
||||
.expect("Failed to initialize x11 event source");
|
||||
|
||||
inner
|
||||
.loop_handle
|
||||
.insert_source(
|
||||
calloop::timer::Timer::from_duration(Duration::from_millis(
|
||||
client.refresh_millis.get(),
|
||||
)),
|
||||
{
|
||||
let client = client.clone();
|
||||
move |_, _, _| {
|
||||
client.present();
|
||||
calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(
|
||||
client.refresh_millis.get(),
|
||||
))
|
||||
}
|
||||
},
|
||||
)
|
||||
.expect("Failed to initialize refresh timer");
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
fn get_window(&self, win: x::Window) -> Option<Rc<X11WindowState>> {
|
||||
let state = self.state.borrow();
|
||||
state.windows.get(&win).cloned()
|
||||
}
|
||||
|
||||
fn present(&self) {
|
||||
let state = self.state.borrow_mut();
|
||||
for window_state in state.windows.values() {
|
||||
window_state.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_window(&self, win: x::Window) -> Rc<X11WindowState> {
|
||||
let state = self.state.lock();
|
||||
Rc::clone(&state.windows[&win])
|
||||
fn handle_event(&self, event: xcb::Event) -> Option<()> {
|
||||
match event {
|
||||
xcb::Event::X(x::Event::ClientMessage(event)) => {
|
||||
if let x::ClientMessageData::Data32([atom, ..]) = event.data() {
|
||||
if atom == self.atoms.wm_del_window.resource_id() {
|
||||
self.state
|
||||
.borrow_mut()
|
||||
.windows_to_refresh
|
||||
.remove(&event.window());
|
||||
// window "x" button clicked by user, we gracefully exit
|
||||
let window = self
|
||||
.state
|
||||
.borrow_mut()
|
||||
.windows
|
||||
.remove(&event.window())
|
||||
.unwrap();
|
||||
window.destroy();
|
||||
let state = self.state.borrow();
|
||||
if state.windows.is_empty() {
|
||||
self.platform_inner.loop_signal.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureNotify(event)) => {
|
||||
let bounds = Bounds {
|
||||
origin: Point {
|
||||
x: event.x().into(),
|
||||
y: event.y().into(),
|
||||
},
|
||||
size: Size {
|
||||
width: event.width().into(),
|
||||
height: event.height().into(),
|
||||
},
|
||||
};
|
||||
let window = self.get_window(event.window())?;
|
||||
window.configure(bounds);
|
||||
}
|
||||
xcb::Event::X(x::Event::FocusIn(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
window.set_focused(true);
|
||||
}
|
||||
xcb::Event::X(x::Event::FocusOut(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
window.set_focused(false);
|
||||
}
|
||||
xcb::Event::X(x::Event::KeyPress(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
let modifiers = super::modifiers_from_state(event.state());
|
||||
let keystroke = {
|
||||
let code = event.detail().into();
|
||||
let mut state = self.state.borrow_mut();
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkb::KeyDirection::Down);
|
||||
keystroke
|
||||
};
|
||||
|
||||
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
}));
|
||||
}
|
||||
xcb::Event::X(x::Event::KeyRelease(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
let modifiers = super::modifiers_from_state(event.state());
|
||||
let keystroke = {
|
||||
let code = event.detail().into();
|
||||
let mut state = self.state.borrow_mut();
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkb::KeyDirection::Up);
|
||||
keystroke
|
||||
};
|
||||
|
||||
window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
|
||||
}
|
||||
xcb::Event::X(x::Event::ButtonPress(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
let modifiers = super::modifiers_from_state(event.state());
|
||||
let position = Point::new(
|
||||
(event.event_x() as f32).into(),
|
||||
(event.event_y() as f32).into(),
|
||||
);
|
||||
if let Some(button) = super::button_of_key(event.detail()) {
|
||||
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
|
||||
button,
|
||||
position,
|
||||
modifiers,
|
||||
click_count: 1,
|
||||
}));
|
||||
} else if event.detail() >= 4 && event.detail() <= 5 {
|
||||
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
|
||||
let delta_x = if event.detail() == 4 { 1.0 } else { -1.0 };
|
||||
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
|
||||
position,
|
||||
delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
|
||||
modifiers,
|
||||
touch_phase: TouchPhase::default(),
|
||||
}));
|
||||
} else {
|
||||
log::warn!("Unknown button press: {event:?}");
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::ButtonRelease(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
let modifiers = super::modifiers_from_state(event.state());
|
||||
let position = Point::new(
|
||||
(event.event_x() as f32).into(),
|
||||
(event.event_y() as f32).into(),
|
||||
);
|
||||
if let Some(button) = super::button_of_key(event.detail()) {
|
||||
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
|
||||
button,
|
||||
position,
|
||||
modifiers,
|
||||
click_count: 1,
|
||||
}));
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::MotionNotify(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
let pressed_button = super::button_from_state(event.state());
|
||||
let position = Point::new(
|
||||
(event.event_x() as f32).into(),
|
||||
(event.event_y() as f32).into(),
|
||||
);
|
||||
let modifiers = super::modifiers_from_state(event.state());
|
||||
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
|
||||
pressed_button,
|
||||
position,
|
||||
modifiers,
|
||||
}));
|
||||
}
|
||||
xcb::Event::X(x::Event::LeaveNotify(event)) => {
|
||||
let window = self.get_window(event.event())?;
|
||||
let pressed_button = super::button_from_state(event.state());
|
||||
let position = Point::new(
|
||||
(event.event_x() as f32).into(),
|
||||
(event.event_y() as f32).into(),
|
||||
);
|
||||
let modifiers = super::modifiers_from_state(event.state());
|
||||
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
|
||||
pressed_button,
|
||||
position,
|
||||
modifiers,
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Client for X11Client {
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
on_finish_launching();
|
||||
let mut windows_to_refresh = HashSet::<x::Window>::default();
|
||||
while !self.platform_inner.state.lock().quit_requested {
|
||||
// We prioritize work in the following order:
|
||||
// 1. input events from X11
|
||||
// 2. runnables for the main thread
|
||||
// 3. drawing/presentation
|
||||
let event = if let Some(event) = self.xcb_connection.poll_for_event().unwrap() {
|
||||
event
|
||||
} else if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
|
||||
runnable.run();
|
||||
continue;
|
||||
} else if let Some(x_window) = windows_to_refresh.iter().next().cloned() {
|
||||
windows_to_refresh.remove(&x_window);
|
||||
let window = self.get_window(x_window);
|
||||
window.refresh();
|
||||
window.request_refresh();
|
||||
continue;
|
||||
} else {
|
||||
profiling::scope!("Wait for event");
|
||||
self.xcb_connection.wait_for_event().unwrap()
|
||||
};
|
||||
|
||||
match event {
|
||||
xcb::Event::X(x::Event::ClientMessage(ev)) => {
|
||||
if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
|
||||
if atom == self.atoms.wm_del_window.resource_id() {
|
||||
windows_to_refresh.remove(&ev.window());
|
||||
// window "x" button clicked by user, we gracefully exit
|
||||
let window = self.state.lock().windows.remove(&ev.window()).unwrap();
|
||||
window.destroy();
|
||||
let state = self.state.lock();
|
||||
self.platform_inner.state.lock().quit_requested |=
|
||||
state.windows.is_empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::Expose(ev)) => {
|
||||
windows_to_refresh.insert(ev.window());
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
|
||||
let bounds = Bounds {
|
||||
origin: Point {
|
||||
x: ev.x().into(),
|
||||
y: ev.y().into(),
|
||||
},
|
||||
size: Size {
|
||||
width: ev.width().into(),
|
||||
height: ev.height().into(),
|
||||
},
|
||||
};
|
||||
self.get_window(ev.window()).configure(bounds)
|
||||
}
|
||||
xcb::Event::Present(xcb::present::Event::CompleteNotify(ev)) => {
|
||||
windows_to_refresh.insert(ev.window());
|
||||
}
|
||||
xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
|
||||
xcb::Event::X(x::Event::FocusIn(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
window.set_focused(true);
|
||||
}
|
||||
xcb::Event::X(x::Event::FocusOut(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
window.set_focused(false);
|
||||
}
|
||||
xcb::Event::X(x::Event::KeyPress(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
let modifiers = super::modifiers_from_state(ev.state());
|
||||
let keystroke = {
|
||||
let code = ev.detail().into();
|
||||
let mut state = self.state.lock();
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkb::KeyDirection::Down);
|
||||
keystroke
|
||||
};
|
||||
|
||||
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
|
||||
keystroke,
|
||||
is_held: false,
|
||||
}));
|
||||
}
|
||||
xcb::Event::X(x::Event::KeyRelease(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
let modifiers = super::modifiers_from_state(ev.state());
|
||||
let keystroke = {
|
||||
let code = ev.detail().into();
|
||||
let mut state = self.state.lock();
|
||||
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
|
||||
state.xkb.update_key(code, xkb::KeyDirection::Up);
|
||||
keystroke
|
||||
};
|
||||
|
||||
window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
|
||||
}
|
||||
xcb::Event::X(x::Event::ButtonPress(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
let modifiers = super::modifiers_from_state(ev.state());
|
||||
let position =
|
||||
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
|
||||
if let Some(button) = super::button_of_key(ev.detail()) {
|
||||
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
|
||||
button,
|
||||
position,
|
||||
modifiers,
|
||||
click_count: 1,
|
||||
}));
|
||||
} else if ev.detail() >= 4 && ev.detail() <= 5 {
|
||||
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
|
||||
let delta_x = if ev.detail() == 4 { 1.0 } else { -1.0 };
|
||||
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
|
||||
position,
|
||||
delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
|
||||
modifiers,
|
||||
touch_phase: TouchPhase::default(),
|
||||
}));
|
||||
} else {
|
||||
log::warn!("Unknown button press: {ev:?}");
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::ButtonRelease(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
let modifiers = super::modifiers_from_state(ev.state());
|
||||
let position =
|
||||
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
|
||||
if let Some(button) = super::button_of_key(ev.detail()) {
|
||||
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
|
||||
button,
|
||||
position,
|
||||
modifiers,
|
||||
click_count: 1,
|
||||
}));
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::MotionNotify(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
let pressed_button = super::button_from_state(ev.state());
|
||||
let position =
|
||||
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
|
||||
let modifiers = super::modifiers_from_state(ev.state());
|
||||
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
|
||||
pressed_button,
|
||||
position,
|
||||
modifiers,
|
||||
}));
|
||||
}
|
||||
xcb::Event::X(x::Event::LeaveNotify(ev)) => {
|
||||
let window = self.get_window(ev.event());
|
||||
let pressed_button = super::button_from_state(ev.state());
|
||||
let position =
|
||||
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
|
||||
let modifiers = super::modifiers_from_state(ev.state());
|
||||
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
|
||||
pressed_button,
|
||||
position,
|
||||
modifiers,
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut fun) = self.platform_inner.callbacks.lock().quit {
|
||||
fun();
|
||||
}
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
let setup = self.xcb_connection.get_setup();
|
||||
setup
|
||||
@@ -264,10 +324,40 @@ impl Client for X11Client {
|
||||
));
|
||||
window_ptr.request_refresh();
|
||||
|
||||
let cookie = self
|
||||
.xcb_connection
|
||||
.send_request(&xcb::randr::GetScreenResourcesCurrent { window: x_window });
|
||||
let screen_resources = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
|
||||
let crtc = screen_resources.crtcs().first().expect("TODO");
|
||||
|
||||
let cookie = self.xcb_connection.send_request(&xcb::randr::GetCrtcInfo {
|
||||
crtc: crtc.to_owned(),
|
||||
config_timestamp: xcb::x::Time::CurrentTime as u32,
|
||||
});
|
||||
let crtc_info = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
|
||||
|
||||
let mode_id = crtc_info.mode().resource_id();
|
||||
let mode = screen_resources
|
||||
.modes()
|
||||
.iter()
|
||||
.find(|m| m.id == mode_id)
|
||||
.expect("Missing screen mode for crtc specified mode id");
|
||||
|
||||
let refresh_millies = mode_refresh_rate_millis(mode);
|
||||
|
||||
self.refresh_millis.set(refresh_millies);
|
||||
|
||||
self.state
|
||||
.lock()
|
||||
.borrow_mut()
|
||||
.windows
|
||||
.insert(x_window, Rc::clone(&window_ptr));
|
||||
Box::new(X11Window(window_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
// Adatpted from:
|
||||
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
|
||||
pub fn mode_refresh_rate_millis(mode: &xcb::randr::ModeInfo) -> u64 {
|
||||
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
|
||||
(millihertz as f64 / 1_000_000.) as u64
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use xcb::x;
|
||||
|
||||
use crate::platform::linux::client_dispatcher::ClientDispatcher;
|
||||
|
||||
pub(crate) struct X11ClientDispatcher {
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
x_listener_window: x::Window,
|
||||
}
|
||||
|
||||
impl X11ClientDispatcher {
|
||||
pub fn new(xcb_connection: &Arc<xcb::Connection>, x_root_index: i32) -> Self {
|
||||
let x_listener_window = xcb_connection.generate_id();
|
||||
let screen = xcb_connection
|
||||
.get_setup()
|
||||
.roots()
|
||||
.nth(x_root_index as usize)
|
||||
.unwrap();
|
||||
xcb_connection.send_request(&x::CreateWindow {
|
||||
depth: 0,
|
||||
wid: x_listener_window,
|
||||
parent: screen.root(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
border_width: 0,
|
||||
class: x::WindowClass::InputOnly,
|
||||
visual: screen.root_visual(),
|
||||
value_list: &[],
|
||||
});
|
||||
|
||||
Self {
|
||||
xcb_connection: Arc::clone(xcb_connection),
|
||||
x_listener_window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for X11ClientDispatcher {
|
||||
fn drop(&mut self) {
|
||||
self.xcb_connection.send_request(&x::DestroyWindow {
|
||||
window: self.x_listener_window,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientDispatcher for X11ClientDispatcher {
|
||||
fn dispatch_on_main_thread(&self) {
|
||||
// Send a message to the invisible window, forcing
|
||||
// the main loop to wake up and dispatch the runnable.
|
||||
self.xcb_connection.send_request(&x::SendEvent {
|
||||
propagate: false,
|
||||
destination: x::SendEventDest::Window(self.x_listener_window),
|
||||
event_mask: x::EventMask::NO_EVENT,
|
||||
event: &x::VisibilityNotifyEvent::new(
|
||||
self.x_listener_window,
|
||||
x::Visibility::Unobscured,
|
||||
),
|
||||
});
|
||||
self.xcb_connection.flush().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
//todo!(linux): remove
|
||||
// todo(linux): remove
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::{
|
||||
platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
|
||||
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
|
||||
Scene, Size, WindowAppearance, WindowBounds, WindowOptions, X11Display,
|
||||
Scene, Size, WindowAppearance, WindowBounds, WindowOptions,
|
||||
};
|
||||
use blade_graphics as gpu;
|
||||
use parking_lot::Mutex;
|
||||
@@ -16,6 +16,7 @@ use xcb::{
|
||||
};
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::c_void,
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
@@ -24,6 +25,8 @@ use std::{
|
||||
sync::{self, Arc},
|
||||
};
|
||||
|
||||
use super::X11Display;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Callbacks {
|
||||
request_frame: Option<Box<dyn FnMut()>>,
|
||||
@@ -85,18 +88,18 @@ struct RawWindow {
|
||||
}
|
||||
|
||||
pub(crate) struct X11WindowState {
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
xcb_connection: Rc<xcb::Connection>,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
raw: RawWindow,
|
||||
x_window: x::Window,
|
||||
callbacks: Mutex<Callbacks>,
|
||||
inner: Mutex<LinuxWindowInner>,
|
||||
callbacks: RefCell<Callbacks>,
|
||||
inner: RefCell<LinuxWindowInner>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct X11Window(pub(crate) Rc<X11WindowState>);
|
||||
|
||||
//todo!(linux): Remove other RawWindowHandle implementation
|
||||
// todo(linux): Remove other RawWindowHandle implementation
|
||||
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
|
||||
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
|
||||
let mut wh = blade_rwh::XcbWindowHandle::empty();
|
||||
@@ -136,7 +139,7 @@ impl rwh::HasDisplayHandle for X11Window {
|
||||
impl X11WindowState {
|
||||
pub fn new(
|
||||
options: WindowOptions,
|
||||
xcb_connection: &Arc<xcb::Connection>,
|
||||
xcb_connection: &Rc<xcb::Connection>,
|
||||
x_main_screen_index: i32,
|
||||
x_window: x::Window,
|
||||
atoms: &XcbAtoms,
|
||||
@@ -253,12 +256,12 @@ impl X11WindowState {
|
||||
let gpu_extent = query_render_extent(xcb_connection, x_window);
|
||||
|
||||
Self {
|
||||
xcb_connection: Arc::clone(xcb_connection),
|
||||
xcb_connection: xcb_connection.clone(),
|
||||
display: Rc::new(X11Display::new(xcb_connection, x_screen_index)),
|
||||
raw,
|
||||
x_window,
|
||||
callbacks: Mutex::new(Callbacks::default()),
|
||||
inner: Mutex::new(LinuxWindowInner {
|
||||
callbacks: RefCell::new(Callbacks::default()),
|
||||
inner: RefCell::new(LinuxWindowInner {
|
||||
bounds,
|
||||
scale_factor: 1.0,
|
||||
renderer: BladeRenderer::new(gpu, gpu_extent),
|
||||
@@ -268,23 +271,26 @@ impl X11WindowState {
|
||||
}
|
||||
|
||||
pub fn destroy(&self) {
|
||||
self.inner.lock().renderer.destroy();
|
||||
self.inner.borrow_mut().renderer.destroy();
|
||||
self.xcb_connection.send_request(&x::UnmapWindow {
|
||||
window: self.x_window,
|
||||
});
|
||||
self.xcb_connection.send_request(&x::DestroyWindow {
|
||||
window: self.x_window,
|
||||
});
|
||||
if let Some(fun) = self.callbacks.lock().close.take() {
|
||||
if let Some(fun) = self.callbacks.borrow_mut().close.take() {
|
||||
fun();
|
||||
}
|
||||
self.xcb_connection.flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn refresh(&self) {
|
||||
let mut cb = self.callbacks.lock();
|
||||
if let Some(ref mut fun) = cb.request_frame {
|
||||
let mut cb = self.callbacks.borrow_mut();
|
||||
if let Some(mut fun) = cb.request_frame.take() {
|
||||
drop(cb);
|
||||
fun();
|
||||
let mut cb = self.callbacks.borrow_mut();
|
||||
cb.request_frame = Some(fun);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,10 +298,10 @@ impl X11WindowState {
|
||||
let mut resize_args = None;
|
||||
let do_move;
|
||||
{
|
||||
let mut inner = self.inner.lock();
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let old_bounds = mem::replace(&mut inner.bounds, bounds);
|
||||
do_move = old_bounds.origin != bounds.origin;
|
||||
//todo!(linux): use normal GPUI types here, refactor out the double
|
||||
// todo(linux): use normal GPUI types here, refactor out the double
|
||||
// viewport check and extra casts ( )
|
||||
let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
|
||||
if inner.renderer.viewport_size() != gpu_size {
|
||||
@@ -306,7 +312,7 @@ impl X11WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
let mut callbacks = self.callbacks.lock();
|
||||
let mut callbacks = self.callbacks.borrow_mut();
|
||||
if let Some((content_size, scale_factor)) = resize_args {
|
||||
if let Some(ref mut fun) = callbacks.resize {
|
||||
fun(content_size, scale_factor)
|
||||
@@ -330,13 +336,13 @@ impl X11WindowState {
|
||||
}
|
||||
|
||||
pub fn handle_input(&self, input: PlatformInput) {
|
||||
if let Some(ref mut fun) = self.callbacks.lock().input {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
|
||||
if fun(input.clone()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let PlatformInput::KeyDown(event) = input {
|
||||
let mut inner = self.inner.lock();
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if let Some(ref mut input_handler) = inner.input_handler {
|
||||
if let Some(ime_key) = &event.keystroke.ime_key {
|
||||
input_handler.replace_text_in_range(None, ime_key);
|
||||
@@ -346,7 +352,7 @@ impl X11WindowState {
|
||||
}
|
||||
|
||||
pub fn set_focused(&self, focus: bool) {
|
||||
if let Some(ref mut fun) = self.callbacks.lock().active_status_change {
|
||||
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
|
||||
fun(focus);
|
||||
}
|
||||
}
|
||||
@@ -354,23 +360,29 @@ impl X11WindowState {
|
||||
|
||||
impl PlatformWindow for X11Window {
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
WindowBounds::Fixed(self.0.inner.lock().bounds.map(|v| GlobalPixels(v as f32)))
|
||||
WindowBounds::Fixed(
|
||||
self.0
|
||||
.inner
|
||||
.borrow_mut()
|
||||
.bounds
|
||||
.map(|v| GlobalPixels(v as f32)),
|
||||
)
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.0.inner.lock().content_size()
|
||||
self.0.inner.borrow_mut().content_size()
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
self.0.inner.lock().scale_factor
|
||||
self.0.inner.borrow_mut().scale_factor
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn titlebar_height(&self) -> Pixels {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn appearance(&self) -> WindowAppearance {
|
||||
WindowAppearance::Light
|
||||
}
|
||||
@@ -390,7 +402,7 @@ impl PlatformWindow for X11Window {
|
||||
)
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn modifiers(&self) -> Modifiers {
|
||||
Modifiers::default()
|
||||
}
|
||||
@@ -400,14 +412,14 @@ impl PlatformWindow for X11Window {
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
|
||||
self.0.inner.lock().input_handler = Some(input_handler);
|
||||
self.0.inner.borrow_mut().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
|
||||
self.0.inner.lock().input_handler.take()
|
||||
self.0.inner.borrow_mut().input_handler.take()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn prompt(
|
||||
&self,
|
||||
_level: PromptLevel,
|
||||
@@ -435,10 +447,10 @@ impl PlatformWindow for X11Window {
|
||||
});
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn set_edited(&mut self, edited: bool) {}
|
||||
|
||||
//todo!(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
|
||||
// todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
|
||||
// but it looks like the equivalent for Linux is GTK specific:
|
||||
//
|
||||
// https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
|
||||
@@ -448,73 +460,69 @@ impl PlatformWindow for X11Window {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn minimize(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn zoom(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn toggle_full_screen(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.lock().request_frame = Some(callback);
|
||||
self.0.callbacks.borrow_mut().request_frame = Some(callback);
|
||||
}
|
||||
|
||||
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
||||
self.0.callbacks.lock().input = Some(callback);
|
||||
self.0.callbacks.borrow_mut().input = Some(callback);
|
||||
}
|
||||
|
||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.lock().active_status_change = Some(callback);
|
||||
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.0.callbacks.lock().resize = Some(callback);
|
||||
self.0.callbacks.borrow_mut().resize = Some(callback);
|
||||
}
|
||||
|
||||
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.0.callbacks.lock().fullscreen = Some(callback);
|
||||
self.0.callbacks.borrow_mut().fullscreen = Some(callback);
|
||||
}
|
||||
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.lock().moved = Some(callback);
|
||||
self.0.callbacks.borrow_mut().moved = Some(callback);
|
||||
}
|
||||
|
||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
|
||||
self.0.callbacks.lock().should_close = Some(callback);
|
||||
self.0.callbacks.borrow_mut().should_close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_close(&self, callback: Box<dyn FnOnce()>) {
|
||||
self.0.callbacks.lock().close = Some(callback);
|
||||
self.0.callbacks.borrow_mut().close = Some(callback);
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.callbacks.lock().appearance_changed = Some(callback);
|
||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
let mut inner = self.0.inner.lock();
|
||||
let mut inner = self.0.inner.borrow_mut();
|
||||
inner.renderer.draw(scene);
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
|
||||
let inner = self.0.inner.lock();
|
||||
let inner = self.0.inner.borrow_mut();
|
||||
inner.renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool) {
|
||||
unimplemented!("linux")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +293,7 @@ impl MetalRenderer {
|
||||
znear: 0.0,
|
||||
zfar: 1.0,
|
||||
});
|
||||
|
||||
for batch in scene.batches() {
|
||||
let ok = match batch {
|
||||
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(
|
||||
|
||||
@@ -1060,27 +1060,6 @@ impl PlatformWindow for MacWindow {
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
self.0.lock().renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
/// Enables or disables the Metal HUD for debugging purposes. Note that this only works
|
||||
/// when the app is bundled and it has the `MetalHudEnabled` key set to true in Info.plist.
|
||||
fn set_graphics_profiler_enabled(&self, enabled: bool) {
|
||||
let this_lock = self.0.lock();
|
||||
let layer = this_lock.renderer.layer();
|
||||
|
||||
unsafe {
|
||||
if enabled {
|
||||
let hud_properties = NSDictionary::dictionaryWithObject_forKey_(
|
||||
nil,
|
||||
ns_string("default"),
|
||||
ns_string("mode"),
|
||||
);
|
||||
let _: () = msg_send![layer, setDeveloperHUDProperties: hud_properties];
|
||||
} else {
|
||||
let _: () =
|
||||
msg_send![layer, setDeveloperHUDProperties: NSDictionary::dictionary(nil)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWindowHandle for MacWindow {
|
||||
|
||||
@@ -126,7 +126,7 @@ impl Platform for TestPlatform {
|
||||
#[cfg(target_os = "macos")]
|
||||
return Arc::new(crate::platform::mac::MacTextSystem::new());
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::borrow::Cow;
|
||||
|
||||
pub(crate) struct TestTextSystem {}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
#[allow(unused)]
|
||||
impl PlatformTextSystem for TestTextSystem {
|
||||
fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
|
||||
|
||||
@@ -251,8 +251,6 @@ impl PlatformWindow for TestWindow {
|
||||
self.0.lock().sprite_atlas.clone()
|
||||
}
|
||||
|
||||
fn set_graphics_profiler_enabled(&self, _enabled: bool) {}
|
||||
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
@@ -1,48 +1,21 @@
|
||||
// todo!("windows"): remove
|
||||
// todo("windows"): remove
|
||||
#![cfg_attr(windows, allow(dead_code))]
|
||||
|
||||
use crate::{
|
||||
point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels,
|
||||
Point, ScaledPixels, StackingOrder,
|
||||
bounds_tree::BoundsTree, point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges,
|
||||
Hsla, Pixels, Point, ScaledPixels,
|
||||
};
|
||||
use collections::{BTreeMap, FxHashSet};
|
||||
use std::{fmt::Debug, iter::Peekable, slice};
|
||||
|
||||
#[allow(non_camel_case_types, unused)]
|
||||
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
|
||||
|
||||
pub(crate) type LayerId = u32;
|
||||
pub(crate) type DrawOrder = u32;
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct ViewId {
|
||||
low_bits: u32,
|
||||
high_bits: u32,
|
||||
}
|
||||
|
||||
impl From<EntityId> for ViewId {
|
||||
fn from(value: EntityId) -> Self {
|
||||
let value = value.as_u64();
|
||||
Self {
|
||||
low_bits: value as u32,
|
||||
high_bits: (value >> 32) as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ViewId> for EntityId {
|
||||
fn from(value: ViewId) -> Self {
|
||||
let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32);
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Scene {
|
||||
last_layer: Option<(StackingOrder, LayerId)>,
|
||||
layers_by_order: BTreeMap<StackingOrder, LayerId>,
|
||||
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
|
||||
pub(crate) primitives: Vec<Primitive>,
|
||||
primitive_bounds: BoundsTree<ScaledPixels, ()>,
|
||||
pub(crate) shadows: Vec<Shadow>,
|
||||
pub(crate) quads: Vec<Quad>,
|
||||
pub(crate) paths: Vec<Path<ScaledPixels>>,
|
||||
@@ -54,12 +27,11 @@ pub(crate) struct Scene {
|
||||
|
||||
impl Scene {
|
||||
pub fn clear(&mut self) {
|
||||
self.last_layer = None;
|
||||
self.layers_by_order.clear();
|
||||
self.orders_by_layer.clear();
|
||||
self.primitives.clear();
|
||||
self.primitive_bounds.clear();
|
||||
self.paths.clear();
|
||||
self.shadows.clear();
|
||||
self.quads.clear();
|
||||
self.paths.clear();
|
||||
self.underlines.clear();
|
||||
self.monochrome_sprites.clear();
|
||||
self.polychrome_sprites.clear();
|
||||
@@ -70,6 +42,66 @@ impl Scene {
|
||||
&self.paths
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.primitives.len()
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, primitive: impl Into<Primitive>) {
|
||||
let mut primitive = primitive.into();
|
||||
let clipped_bounds = primitive
|
||||
.bounds()
|
||||
.intersect(&primitive.content_mask().bounds);
|
||||
if clipped_bounds.size.width <= ScaledPixels(0.)
|
||||
|| clipped_bounds.size.height <= ScaledPixels(0.)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let order = self.primitive_bounds.insert(clipped_bounds, ());
|
||||
match &mut primitive {
|
||||
Primitive::Shadow(shadow) => {
|
||||
shadow.order = order;
|
||||
self.shadows.push(shadow.clone());
|
||||
}
|
||||
Primitive::Quad(quad) => {
|
||||
quad.order = order;
|
||||
self.quads.push(quad.clone());
|
||||
}
|
||||
Primitive::Path(path) => {
|
||||
path.order = order;
|
||||
path.id = PathId(self.paths.len());
|
||||
self.paths.push(path.clone());
|
||||
}
|
||||
Primitive::Underline(underline) => {
|
||||
underline.order = order;
|
||||
self.underlines.push(underline.clone());
|
||||
}
|
||||
Primitive::MonochromeSprite(sprite) => {
|
||||
sprite.order = order;
|
||||
self.monochrome_sprites.push(sprite.clone());
|
||||
}
|
||||
Primitive::PolychromeSprite(sprite) => {
|
||||
sprite.order = order;
|
||||
self.polychrome_sprites.push(sprite.clone());
|
||||
}
|
||||
Primitive::Surface(surface) => {
|
||||
surface.order = order;
|
||||
self.surfaces.push(surface.clone());
|
||||
}
|
||||
}
|
||||
self.primitives.push(primitive);
|
||||
}
|
||||
|
||||
pub fn finish(&mut self) {
|
||||
self.shadows.sort_unstable();
|
||||
self.quads.sort_unstable();
|
||||
self.paths.sort_unstable();
|
||||
self.underlines.sort_unstable();
|
||||
self.monochrome_sprites.sort_unstable();
|
||||
self.polychrome_sprites.sort_unstable();
|
||||
self.surfaces.sort_unstable();
|
||||
}
|
||||
|
||||
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||
BatchIterator {
|
||||
shadows: &self.shadows,
|
||||
@@ -95,162 +127,54 @@ impl Scene {
|
||||
surfaces_iter: self.surfaces.iter().peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
|
||||
let primitive = primitive.into();
|
||||
let clipped_bounds = primitive
|
||||
.bounds()
|
||||
.intersect(&primitive.content_mask().bounds);
|
||||
if clipped_bounds.size.width <= ScaledPixels(0.)
|
||||
|| clipped_bounds.size.height <= ScaledPixels(0.)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
pub(crate) enum PrimitiveKind {
|
||||
Shadow,
|
||||
#[default]
|
||||
Quad,
|
||||
Path,
|
||||
Underline,
|
||||
MonochromeSprite,
|
||||
PolychromeSprite,
|
||||
Surface,
|
||||
}
|
||||
|
||||
let layer_id = self.layer_id_for_order(order);
|
||||
match primitive {
|
||||
Primitive::Shadow(mut shadow) => {
|
||||
shadow.layer_id = layer_id;
|
||||
self.shadows.push(shadow);
|
||||
}
|
||||
Primitive::Quad(mut quad) => {
|
||||
quad.layer_id = layer_id;
|
||||
self.quads.push(quad);
|
||||
}
|
||||
Primitive::Path(mut path) => {
|
||||
path.layer_id = layer_id;
|
||||
path.id = PathId(self.paths.len());
|
||||
self.paths.push(path);
|
||||
}
|
||||
Primitive::Underline(mut underline) => {
|
||||
underline.layer_id = layer_id;
|
||||
self.underlines.push(underline);
|
||||
}
|
||||
Primitive::MonochromeSprite(mut sprite) => {
|
||||
sprite.layer_id = layer_id;
|
||||
self.monochrome_sprites.push(sprite);
|
||||
}
|
||||
Primitive::PolychromeSprite(mut sprite) => {
|
||||
sprite.layer_id = layer_id;
|
||||
self.polychrome_sprites.push(sprite);
|
||||
}
|
||||
Primitive::Surface(mut surface) => {
|
||||
surface.layer_id = layer_id;
|
||||
self.surfaces.push(surface);
|
||||
}
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub(crate) enum Primitive {
|
||||
Shadow(Shadow),
|
||||
Quad(Quad),
|
||||
Path(Path<ScaledPixels>),
|
||||
Underline(Underline),
|
||||
MonochromeSprite(MonochromeSprite),
|
||||
PolychromeSprite(PolychromeSprite),
|
||||
Surface(Surface),
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
|
||||
match self {
|
||||
Primitive::Shadow(shadow) => &shadow.bounds,
|
||||
Primitive::Quad(quad) => &quad.bounds,
|
||||
Primitive::Path(path) => &path.bounds,
|
||||
Primitive::Underline(underline) => &underline.bounds,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::Surface(surface) => &surface.bounds,
|
||||
}
|
||||
}
|
||||
|
||||
fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId {
|
||||
if let Some((last_order, last_layer_id)) = self.last_layer.as_ref() {
|
||||
if order == last_order {
|
||||
return *last_layer_id;
|
||||
}
|
||||
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
|
||||
match self {
|
||||
Primitive::Shadow(shadow) => &shadow.content_mask,
|
||||
Primitive::Quad(quad) => &quad.content_mask,
|
||||
Primitive::Path(path) => &path.content_mask,
|
||||
Primitive::Underline(underline) => &underline.content_mask,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::Surface(surface) => &surface.content_mask,
|
||||
}
|
||||
|
||||
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
|
||||
*layer_id
|
||||
} else {
|
||||
let next_id = self.layers_by_order.len() as LayerId;
|
||||
self.layers_by_order.insert(order.clone(), next_id);
|
||||
self.orders_by_layer.insert(next_id, order.clone());
|
||||
next_id
|
||||
};
|
||||
self.last_layer = Some((order.clone(), layer_id));
|
||||
layer_id
|
||||
}
|
||||
|
||||
pub fn reuse_views(&mut self, views: &FxHashSet<EntityId>, prev_scene: &mut Self) {
|
||||
for shadow in prev_scene.shadows.drain(..) {
|
||||
if views.contains(&shadow.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&shadow.layer_id];
|
||||
self.insert(order, shadow);
|
||||
}
|
||||
}
|
||||
|
||||
for quad in prev_scene.quads.drain(..) {
|
||||
if views.contains(&quad.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&quad.layer_id];
|
||||
self.insert(order, quad);
|
||||
}
|
||||
}
|
||||
|
||||
for path in prev_scene.paths.drain(..) {
|
||||
if views.contains(&path.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&path.layer_id];
|
||||
self.insert(order, path);
|
||||
}
|
||||
}
|
||||
|
||||
for underline in prev_scene.underlines.drain(..) {
|
||||
if views.contains(&underline.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&underline.layer_id];
|
||||
self.insert(order, underline);
|
||||
}
|
||||
}
|
||||
|
||||
for sprite in prev_scene.monochrome_sprites.drain(..) {
|
||||
if views.contains(&sprite.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
||||
self.insert(order, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
for sprite in prev_scene.polychrome_sprites.drain(..) {
|
||||
if views.contains(&sprite.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
|
||||
self.insert(order, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
for surface in prev_scene.surfaces.drain(..) {
|
||||
if views.contains(&surface.view_id.into()) {
|
||||
let order = &prev_scene.orders_by_layer[&surface.layer_id];
|
||||
self.insert(order, surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(&mut self) {
|
||||
let mut orders = vec![0; self.layers_by_order.len()];
|
||||
for (ix, layer_id) in self.layers_by_order.values().enumerate() {
|
||||
orders[*layer_id as usize] = ix as u32;
|
||||
}
|
||||
|
||||
for shadow in &mut self.shadows {
|
||||
shadow.order = orders[shadow.layer_id as usize];
|
||||
}
|
||||
self.shadows.sort_by_key(|shadow| shadow.order);
|
||||
|
||||
for quad in &mut self.quads {
|
||||
quad.order = orders[quad.layer_id as usize];
|
||||
}
|
||||
self.quads.sort_by_key(|quad| quad.order);
|
||||
|
||||
for path in &mut self.paths {
|
||||
path.order = orders[path.layer_id as usize];
|
||||
}
|
||||
self.paths.sort_by_key(|path| path.order);
|
||||
|
||||
for underline in &mut self.underlines {
|
||||
underline.order = orders[underline.layer_id as usize];
|
||||
}
|
||||
self.underlines.sort_by_key(|underline| underline.order);
|
||||
|
||||
for monochrome_sprite in &mut self.monochrome_sprites {
|
||||
monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize];
|
||||
}
|
||||
self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
|
||||
|
||||
for polychrome_sprite in &mut self.polychrome_sprites {
|
||||
polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize];
|
||||
}
|
||||
self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
|
||||
|
||||
for surface in &mut self.surfaces {
|
||||
surface.order = orders[surface.layer_id as usize];
|
||||
}
|
||||
self.surfaces.sort_by_key(|surface| surface.order);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,54 +363,6 @@ impl<'a> Iterator for BatchIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
|
||||
pub(crate) enum PrimitiveKind {
|
||||
Shadow,
|
||||
#[default]
|
||||
Quad,
|
||||
Path,
|
||||
Underline,
|
||||
MonochromeSprite,
|
||||
PolychromeSprite,
|
||||
Surface,
|
||||
}
|
||||
|
||||
pub(crate) enum Primitive {
|
||||
Shadow(Shadow),
|
||||
Quad(Quad),
|
||||
Path(Path<ScaledPixels>),
|
||||
Underline(Underline),
|
||||
MonochromeSprite(MonochromeSprite),
|
||||
PolychromeSprite(PolychromeSprite),
|
||||
Surface(Surface),
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
|
||||
match self {
|
||||
Primitive::Shadow(shadow) => &shadow.bounds,
|
||||
Primitive::Quad(quad) => &quad.bounds,
|
||||
Primitive::Path(path) => &path.bounds,
|
||||
Primitive::Underline(underline) => &underline.bounds,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
|
||||
Primitive::Surface(surface) => &surface.bounds,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
|
||||
match self {
|
||||
Primitive::Shadow(shadow) => &shadow.content_mask,
|
||||
Primitive::Quad(quad) => &quad.content_mask,
|
||||
Primitive::Path(path) => &path.content_mask,
|
||||
Primitive::Underline(underline) => &underline.content_mask,
|
||||
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
|
||||
Primitive::Surface(surface) => &surface.content_mask,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PrimitiveBatch<'a> {
|
||||
Shadows(&'a [Shadow]),
|
||||
@@ -507,8 +383,6 @@ pub(crate) enum PrimitiveBatch<'a> {
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Quad {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
@@ -539,8 +413,6 @@ impl From<Quad> for Primitive {
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Underline {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
@@ -570,8 +442,6 @@ impl From<Underline> for Primitive {
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct Shadow {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub corner_radii: Corners<ScaledPixels>,
|
||||
@@ -602,8 +472,6 @@ impl From<Shadow> for Primitive {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct MonochromeSprite {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
@@ -635,8 +503,6 @@ impl From<MonochromeSprite> for Primitive {
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub(crate) struct PolychromeSprite {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
@@ -669,8 +535,6 @@ impl From<PolychromeSprite> for Primitive {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct Surface {
|
||||
pub view_id: ViewId,
|
||||
pub layer_id: LayerId,
|
||||
pub order: DrawOrder,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
@@ -700,11 +564,9 @@ impl From<Surface> for Primitive {
|
||||
pub(crate) struct PathId(pub(crate) usize);
|
||||
|
||||
/// A line made up of a series of vertices and control points.
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Path<P: Clone + Default + Debug> {
|
||||
pub(crate) id: PathId,
|
||||
pub(crate) view_id: ViewId,
|
||||
layer_id: LayerId,
|
||||
order: DrawOrder,
|
||||
pub(crate) bounds: Bounds<P>,
|
||||
pub(crate) content_mask: ContentMask<P>,
|
||||
@@ -720,8 +582,6 @@ impl Path<Pixels> {
|
||||
pub fn new(start: Point<Pixels>) -> Self {
|
||||
Self {
|
||||
id: PathId(0),
|
||||
view_id: ViewId::default(),
|
||||
layer_id: LayerId::default(),
|
||||
order: DrawOrder::default(),
|
||||
vertices: Vec::new(),
|
||||
start,
|
||||
@@ -740,8 +600,6 @@ impl Path<Pixels> {
|
||||
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
|
||||
Path {
|
||||
id: self.id,
|
||||
view_id: self.view_id,
|
||||
layer_id: self.layer_id,
|
||||
order: self.order,
|
||||
bounds: self.bounds.scale(factor),
|
||||
content_mask: self.content_mask.scale(factor),
|
||||
|
||||
@@ -115,9 +115,6 @@ pub struct Style {
|
||||
/// The mouse cursor style shown when the mouse pointer is over an element.
|
||||
pub mouse_cursor: Option<CursorStyle>,
|
||||
|
||||
/// The z-index to set for this element
|
||||
pub z_index: Option<u16>,
|
||||
|
||||
/// Whether to draw a red debugging outline around this element
|
||||
#[cfg(debug_assertions)]
|
||||
pub debug: bool,
|
||||
@@ -208,7 +205,7 @@ impl Default for TextStyle {
|
||||
fn default() -> Self {
|
||||
TextStyle {
|
||||
color: black(),
|
||||
// todo!(linux) make this configurable or choose better default
|
||||
// todo(linux) make this configurable or choose better default
|
||||
font_family: if cfg!(target_os = "linux") {
|
||||
"FreeMono".into()
|
||||
} else {
|
||||
@@ -323,6 +320,13 @@ pub struct HighlightStyle {
|
||||
impl Eq for HighlightStyle {}
|
||||
|
||||
impl Style {
|
||||
/// Returns true if the style is visible and the background is opaque.
|
||||
pub fn has_opaque_background(&self) -> bool {
|
||||
self.background
|
||||
.as_ref()
|
||||
.is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
|
||||
}
|
||||
|
||||
/// Get the text style in this element style.
|
||||
pub fn text_style(&self) -> Option<&TextStyleRefinement> {
|
||||
if self.text.is_some() {
|
||||
@@ -402,97 +406,87 @@ impl Style {
|
||||
|
||||
let rem_size = cx.rem_size();
|
||||
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.paint_shadows(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
&self.box_shadow,
|
||||
);
|
||||
});
|
||||
cx.paint_shadows(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
&self.box_shadow,
|
||||
);
|
||||
|
||||
let background_color = self.background.as_ref().and_then(Fill::color);
|
||||
if background_color.map_or(false, |color| !color.is_transparent()) {
|
||||
cx.with_z_index(1, |cx| {
|
||||
let mut border_color = background_color.unwrap_or_default();
|
||||
border_color.a = 0.;
|
||||
cx.paint_quad(quad(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
background_color.unwrap_or_default(),
|
||||
Edges::default(),
|
||||
border_color,
|
||||
));
|
||||
});
|
||||
let mut border_color = background_color.unwrap_or_default();
|
||||
border_color.a = 0.;
|
||||
cx.paint_quad(quad(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
background_color.unwrap_or_default(),
|
||||
Edges::default(),
|
||||
border_color,
|
||||
));
|
||||
}
|
||||
|
||||
cx.with_z_index(2, |cx| {
|
||||
continuation(cx);
|
||||
});
|
||||
continuation(cx);
|
||||
|
||||
if self.is_border_visible() {
|
||||
cx.with_z_index(3, |cx| {
|
||||
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
|
||||
let border_widths = self.border_widths.to_pixels(rem_size);
|
||||
let max_border_width = border_widths.max();
|
||||
let max_corner_radius = corner_radii.max();
|
||||
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
|
||||
let border_widths = self.border_widths.to_pixels(rem_size);
|
||||
let max_border_width = border_widths.max();
|
||||
let max_corner_radius = corner_radii.max();
|
||||
|
||||
let top_bounds = Bounds::from_corners(
|
||||
bounds.origin,
|
||||
bounds.upper_right()
|
||||
+ point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
);
|
||||
let bottom_bounds = Bounds::from_corners(
|
||||
bounds.lower_left()
|
||||
- point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
bounds.lower_right(),
|
||||
);
|
||||
let left_bounds = Bounds::from_corners(
|
||||
top_bounds.lower_left(),
|
||||
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
|
||||
);
|
||||
let right_bounds = Bounds::from_corners(
|
||||
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
|
||||
bottom_bounds.upper_right(),
|
||||
);
|
||||
let top_bounds = Bounds::from_corners(
|
||||
bounds.origin,
|
||||
bounds.upper_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
);
|
||||
let bottom_bounds = Bounds::from_corners(
|
||||
bounds.lower_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
|
||||
bounds.lower_right(),
|
||||
);
|
||||
let left_bounds = Bounds::from_corners(
|
||||
top_bounds.lower_left(),
|
||||
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
|
||||
);
|
||||
let right_bounds = Bounds::from_corners(
|
||||
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
|
||||
bottom_bounds.upper_right(),
|
||||
);
|
||||
|
||||
let mut background = self.border_color.unwrap_or_default();
|
||||
background.a = 0.;
|
||||
let quad = quad(
|
||||
bounds,
|
||||
corner_radii,
|
||||
background,
|
||||
border_widths,
|
||||
self.border_color.unwrap_or_default(),
|
||||
);
|
||||
let mut background = self.border_color.unwrap_or_default();
|
||||
background.a = 0.;
|
||||
let quad = quad(
|
||||
bounds,
|
||||
corner_radii,
|
||||
background,
|
||||
border_widths,
|
||||
self.border_color.unwrap_or_default(),
|
||||
);
|
||||
|
||||
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
|
||||
cx.paint_quad(quad.clone());
|
||||
});
|
||||
cx.with_content_mask(
|
||||
Some(ContentMask {
|
||||
bounds: right_bounds,
|
||||
}),
|
||||
|cx| {
|
||||
cx.paint_quad(quad.clone());
|
||||
},
|
||||
);
|
||||
cx.with_content_mask(
|
||||
Some(ContentMask {
|
||||
bounds: bottom_bounds,
|
||||
}),
|
||||
|cx| {
|
||||
cx.paint_quad(quad.clone());
|
||||
},
|
||||
);
|
||||
cx.with_content_mask(
|
||||
Some(ContentMask {
|
||||
bounds: left_bounds,
|
||||
}),
|
||||
|cx| {
|
||||
cx.paint_quad(quad);
|
||||
},
|
||||
);
|
||||
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
|
||||
cx.paint_quad(quad.clone());
|
||||
});
|
||||
cx.with_content_mask(
|
||||
Some(ContentMask {
|
||||
bounds: right_bounds,
|
||||
}),
|
||||
|cx| {
|
||||
cx.paint_quad(quad.clone());
|
||||
},
|
||||
);
|
||||
cx.with_content_mask(
|
||||
Some(ContentMask {
|
||||
bounds: bottom_bounds,
|
||||
}),
|
||||
|cx| {
|
||||
cx.paint_quad(quad.clone());
|
||||
},
|
||||
);
|
||||
cx.with_content_mask(
|
||||
Some(ContentMask {
|
||||
bounds: left_bounds,
|
||||
}),
|
||||
|cx| {
|
||||
cx.paint_quad(quad);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -545,7 +539,6 @@ impl Default for Style {
|
||||
box_shadow: Default::default(),
|
||||
text: TextStyleRefinement::default(),
|
||||
mouse_cursor: None,
|
||||
z_index: None,
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
debug: false,
|
||||
|
||||
@@ -15,12 +15,6 @@ pub trait Styled: Sized {
|
||||
|
||||
gpui_macros::style_helpers!();
|
||||
|
||||
/// Set the z-index of this element.
|
||||
fn z_index(mut self, z_index: u16) -> Self {
|
||||
self.style().z_index = Some(z_index);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the position of the element to `relative`.
|
||||
/// [Docs](https://tailwindcss.com/docs/position)
|
||||
fn relative(mut self) -> Self {
|
||||
|
||||
@@ -51,7 +51,7 @@ impl TaffyLayoutEngine {
|
||||
self.styles.get(&layout_id)
|
||||
}
|
||||
|
||||
pub fn request_layout(
|
||||
pub fn before_layout(
|
||||
&mut self,
|
||||
style: &Style,
|
||||
rem_size: Pixels,
|
||||
@@ -447,6 +447,27 @@ pub enum AvailableSpace {
|
||||
MaxContent,
|
||||
}
|
||||
|
||||
impl AvailableSpace {
|
||||
/// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
|
||||
///
|
||||
/// This function is useful when you want to create a `Size` with the minimum content constraints
|
||||
/// for both dimensions.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let min_content_size = AvailableSpace::min_size();
|
||||
/// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
|
||||
/// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
|
||||
/// ```
|
||||
pub const fn min_size() -> Size<Self> {
|
||||
Size {
|
||||
width: Self::MinContent,
|
||||
height: Self::MinContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AvailableSpace> for TaffyAvailableSpace {
|
||||
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
|
||||
match space {
|
||||
|
||||
@@ -291,8 +291,8 @@ impl WindowTextSystem {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
|
||||
self.line_layout_cache.with_view(view_id, f)
|
||||
pub(crate) fn set_parent_view_id(&self, view_id: Option<EntityId>) {
|
||||
self.line_layout_cache.set_parent_view_id(view_id);
|
||||
}
|
||||
|
||||
/// Shape the given line, at the given font_size, for painting to the screen.
|
||||
|
||||
@@ -118,11 +118,13 @@ fn paint_line(
|
||||
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
|
||||
let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
|
||||
let text_system = cx.text_system().clone();
|
||||
let mut glyph_origin = origin;
|
||||
let mut prev_glyph_position = Point::default();
|
||||
for (run_ix, run) in layout.runs.iter().enumerate() {
|
||||
let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
|
||||
let max_glyph_size = cx
|
||||
.text_system()
|
||||
.bounding_box(run.font_id, layout.font_size)
|
||||
.size;
|
||||
|
||||
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
|
||||
|
||||
@@ -4,6 +4,7 @@ use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
cell::Cell,
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -276,7 +277,7 @@ impl WrappedLineLayout {
|
||||
}
|
||||
|
||||
pub(crate) struct LineLayoutCache {
|
||||
view_stack: Mutex<Vec<EntityId>>,
|
||||
parent_view_id: Cell<Option<EntityId>>,
|
||||
previous_frame: Mutex<FxHashMap<CacheKey, Arc<LineLayout>>>,
|
||||
current_frame: RwLock<FxHashMap<CacheKey, Arc<LineLayout>>>,
|
||||
previous_frame_wrapped: Mutex<FxHashMap<CacheKey, Arc<WrappedLineLayout>>>,
|
||||
@@ -287,7 +288,7 @@ pub(crate) struct LineLayoutCache {
|
||||
impl LineLayoutCache {
|
||||
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
||||
Self {
|
||||
view_stack: Mutex::default(),
|
||||
parent_view_id: Cell::default(),
|
||||
previous_frame: Mutex::default(),
|
||||
current_frame: RwLock::default(),
|
||||
previous_frame_wrapped: Mutex::default(),
|
||||
@@ -297,8 +298,6 @@ impl LineLayoutCache {
|
||||
}
|
||||
|
||||
pub fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
|
||||
debug_assert_eq!(self.view_stack.lock().len(), 0);
|
||||
|
||||
let mut prev_frame = self.previous_frame.lock();
|
||||
let mut curr_frame = self.current_frame.write();
|
||||
for (key, layout) in prev_frame.drain() {
|
||||
@@ -324,15 +323,8 @@ impl LineLayoutCache {
|
||||
std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped);
|
||||
}
|
||||
|
||||
pub fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
|
||||
self.view_stack.lock().push(view_id);
|
||||
let result = f();
|
||||
self.view_stack.lock().pop();
|
||||
result
|
||||
}
|
||||
|
||||
fn parent_view_id(&self) -> Option<EntityId> {
|
||||
self.view_stack.lock().last().copied()
|
||||
pub fn set_parent_view_id(&self, view_id: Option<EntityId>) {
|
||||
self.parent_view_id.replace(view_id);
|
||||
}
|
||||
|
||||
pub fn layout_wrapped_line(
|
||||
@@ -347,7 +339,7 @@ impl LineLayoutCache {
|
||||
font_size,
|
||||
runs,
|
||||
wrap_width,
|
||||
parent_view_id: self.parent_view_id(),
|
||||
parent_view_id: self.parent_view_id.get(),
|
||||
} as &dyn AsCacheKeyRef;
|
||||
|
||||
let current_frame = self.current_frame_wrapped.upgradable_read();
|
||||
@@ -376,7 +368,7 @@ impl LineLayoutCache {
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
wrap_width,
|
||||
parent_view_id: self.parent_view_id(),
|
||||
parent_view_id: self.parent_view_id.get(),
|
||||
};
|
||||
current_frame.insert(key, layout.clone());
|
||||
layout
|
||||
@@ -389,7 +381,7 @@ impl LineLayoutCache {
|
||||
font_size,
|
||||
runs,
|
||||
wrap_width: None,
|
||||
parent_view_id: self.parent_view_id(),
|
||||
parent_view_id: self.parent_view_id.get(),
|
||||
} as &dyn AsCacheKeyRef;
|
||||
|
||||
let current_frame = self.current_frame.upgradable_read();
|
||||
@@ -408,7 +400,7 @@ impl LineLayoutCache {
|
||||
font_size,
|
||||
runs: SmallVec::from(runs),
|
||||
wrap_width: None,
|
||||
parent_view_id: self.parent_view_id(),
|
||||
parent_view_id: self.parent_view_id.get(),
|
||||
};
|
||||
current_frame.insert(key, layout.clone());
|
||||
layout
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use crate::{
|
||||
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds,
|
||||
seal::Sealed, AfterLayoutIndex, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds,
|
||||
ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
|
||||
FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style,
|
||||
TextStyle, ViewContext, VisualContext, WeakModel,
|
||||
FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render, Style, TextStyle,
|
||||
ViewContext, VisualContext, WeakModel,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
any::{type_name, TypeId},
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
ops::Range,
|
||||
};
|
||||
|
||||
/// A view is a piece of state that can be presented on screen by implementing the [Render] trait.
|
||||
@@ -20,17 +21,16 @@ pub struct View<V> {
|
||||
|
||||
impl<V> Sealed for View<V> {}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AnyViewState {
|
||||
struct AnyViewState {
|
||||
root_style: Style,
|
||||
next_stacking_order_id: u16,
|
||||
cache_key: Option<ViewCacheKey>,
|
||||
element: Option<AnyElement>,
|
||||
after_layout_range: Range<AfterLayoutIndex>,
|
||||
paint_range: Range<PaintIndex>,
|
||||
cache_key: ViewCacheKey,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ViewCacheKey {
|
||||
bounds: Bounds<Pixels>,
|
||||
stacking_order: StackingOrder,
|
||||
content_mask: ContentMask<Pixels>,
|
||||
text_style: TextStyle,
|
||||
}
|
||||
@@ -90,22 +90,39 @@ impl<V: 'static> View<V> {
|
||||
}
|
||||
|
||||
impl<V: Render> Element for View<V> {
|
||||
type State = Option<AnyElement>;
|
||||
type BeforeLayout = AnyElement;
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_state: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
cx.with_view_id(self.entity_id(), |cx| {
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
|
||||
let layout_id = element.request_layout(cx);
|
||||
(layout_id, Some(element))
|
||||
let layout_id = element.before_layout(cx);
|
||||
(layout_id, element)
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
||||
cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx));
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.set_view_id(self.entity_id());
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
element.after_layout(cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
element.paint(cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +220,7 @@ impl<V> Eq for WeakView<V> {}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyView {
|
||||
model: AnyModel,
|
||||
request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
|
||||
cache: bool,
|
||||
}
|
||||
|
||||
@@ -220,7 +237,7 @@ impl AnyView {
|
||||
pub fn downgrade(&self) -> AnyWeakView {
|
||||
AnyWeakView {
|
||||
model: self.model.downgrade(),
|
||||
layout: self.request_layout,
|
||||
render: self.render,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +248,7 @@ impl AnyView {
|
||||
Ok(model) => Ok(View { model }),
|
||||
Err(model) => Err(Self {
|
||||
model,
|
||||
request_layout: self.request_layout,
|
||||
render: self.render,
|
||||
cache: self.cache,
|
||||
}),
|
||||
}
|
||||
@@ -246,113 +263,154 @@ impl AnyView {
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.model.entity_id()
|
||||
}
|
||||
|
||||
pub(crate) fn draw(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.paint_view(self.entity_id(), |cx| {
|
||||
cx.with_absolute_element_offset(origin, |cx| {
|
||||
let (layout_id, mut rendered_element) = (self.request_layout)(self, cx);
|
||||
cx.compute_layout(layout_id, available_space);
|
||||
rendered_element.paint(cx)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Render> From<View<V>> for AnyView {
|
||||
fn from(value: View<V>) -> Self {
|
||||
AnyView {
|
||||
model: value.model.into_any(),
|
||||
request_layout: any_view::request_layout::<V>,
|
||||
render: any_view::render::<V>,
|
||||
cache: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for AnyView {
|
||||
type State = AnyViewState;
|
||||
type BeforeLayout = Option<AnyElement>;
|
||||
type AfterLayout = Option<AnyElement>;
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State) {
|
||||
cx.with_view_id(self.entity_id(), |cx| {
|
||||
if self.cache
|
||||
&& !cx.window.dirty_views.contains(&self.entity_id())
|
||||
&& !cx.window.refreshing
|
||||
{
|
||||
if let Some(state) = state {
|
||||
let layout_id = cx.request_layout(&state.root_style, None);
|
||||
return (layout_id, state);
|
||||
}
|
||||
}
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
|
||||
if self.cache {
|
||||
cx.with_element_state::<AnyViewState, _>(
|
||||
Some(ElementId::View(self.entity_id())),
|
||||
|element_state, cx| {
|
||||
let mut element_state = element_state.unwrap();
|
||||
|
||||
let (layout_id, element) = (self.request_layout)(self, cx);
|
||||
let root_style = cx.layout_style(layout_id).unwrap().clone();
|
||||
let state = AnyViewState {
|
||||
root_style,
|
||||
next_stacking_order_id: 0,
|
||||
cache_key: None,
|
||||
element: Some(element),
|
||||
};
|
||||
(layout_id, state)
|
||||
})
|
||||
if !cx.window.dirty_views.contains(&self.entity_id()) && !cx.window.refreshing {
|
||||
if let Some(root_style) = element_state
|
||||
.as_ref()
|
||||
.map(|element_state| &element_state.root_style)
|
||||
{
|
||||
let layout_id = cx.request_layout(root_style, None);
|
||||
return ((layout_id, None), element_state);
|
||||
}
|
||||
}
|
||||
|
||||
let mut element = (self.render)(self, cx);
|
||||
let layout_id = element.before_layout(cx);
|
||||
let element_state = Some(AnyViewState {
|
||||
root_style: cx.layout_style(layout_id).unwrap().clone(),
|
||||
cache_key: ViewCacheKey::default(),
|
||||
after_layout_range: AfterLayoutIndex::default()
|
||||
..AfterLayoutIndex::default(),
|
||||
paint_range: PaintIndex::default()..PaintIndex::default(),
|
||||
});
|
||||
((layout_id, Some(element)), element_state)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
let mut element = (self.render)(self, cx);
|
||||
let layout_id = element.before_layout(cx);
|
||||
(layout_id, Some(element))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
cx.paint_view(self.entity_id(), |cx| {
|
||||
if !self.cache {
|
||||
state.element.take().unwrap().paint(cx);
|
||||
return;
|
||||
}
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<AnyElement> {
|
||||
cx.set_view_id(self.entity_id());
|
||||
if self.cache {
|
||||
cx.with_element_state::<AnyViewState, _>(
|
||||
Some(ElementId::View(self.entity_id())),
|
||||
|element_state, cx| {
|
||||
let mut element_state = element_state.unwrap().unwrap();
|
||||
|
||||
if let Some(cache_key) = state.cache_key.as_mut() {
|
||||
if cache_key.bounds == bounds
|
||||
&& cache_key.content_mask == cx.content_mask()
|
||||
&& cache_key.stacking_order == *cx.stacking_order()
|
||||
&& cache_key.text_style == cx.text_style()
|
||||
{
|
||||
cx.reuse_view(state.next_stacking_order_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let after_layout_start = cx.window.next_frame.after_layout_index();
|
||||
let content_mask = cx.content_mask();
|
||||
let text_style = cx.text_style();
|
||||
|
||||
if let Some(mut element) = state.element.take() {
|
||||
element.paint(cx);
|
||||
} else {
|
||||
let mut element = (self.request_layout)(self, cx).1;
|
||||
element.draw(bounds.origin, bounds.size.into(), cx);
|
||||
}
|
||||
let element = if let Some(mut element) = element.take() {
|
||||
element.after_layout(cx);
|
||||
Some(element)
|
||||
} else if element_state.cache_key.bounds == bounds
|
||||
&& element_state.cache_key.content_mask == content_mask
|
||||
&& element_state.cache_key.text_style == text_style
|
||||
{
|
||||
cx.reuse_after_layout(element_state.after_layout_range.clone());
|
||||
None
|
||||
} else {
|
||||
let mut element = (self.render)(self, cx);
|
||||
let layout_id = element.before_layout(cx);
|
||||
cx.compute_layout(layout_id, bounds.size.into());
|
||||
element_state.root_style = cx.layout_style(layout_id).unwrap().clone();
|
||||
cx.with_absolute_element_offset(bounds.origin, |cx| {
|
||||
element.after_layout(cx)
|
||||
});
|
||||
|
||||
state.next_stacking_order_id = cx
|
||||
.window
|
||||
.next_frame
|
||||
.next_stacking_order_ids
|
||||
.last()
|
||||
.copied()
|
||||
.unwrap();
|
||||
state.cache_key = Some(ViewCacheKey {
|
||||
bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
content_mask: cx.content_mask(),
|
||||
text_style: cx.text_style(),
|
||||
});
|
||||
})
|
||||
Some(element)
|
||||
};
|
||||
|
||||
let after_layout_end = cx.window.next_frame.after_layout_index();
|
||||
element_state.after_layout_range = after_layout_start..after_layout_end;
|
||||
element_state.cache_key.bounds = bounds;
|
||||
element_state.cache_key.content_mask = content_mask;
|
||||
element_state.cache_key.text_style = text_style;
|
||||
|
||||
(element, Some(element_state))
|
||||
},
|
||||
)
|
||||
} else {
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
let mut element = element.take().unwrap();
|
||||
element.after_layout(cx);
|
||||
Some(element)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_: &mut Self::BeforeLayout,
|
||||
element: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if self.cache {
|
||||
cx.with_element_state::<AnyViewState, _>(
|
||||
Some(ElementId::View(self.entity_id())),
|
||||
|element_state, cx| {
|
||||
let mut element_state = element_state.unwrap().unwrap();
|
||||
|
||||
let paint_start = cx.window.next_frame.paint_index();
|
||||
|
||||
if let Some(element) = element {
|
||||
element.paint(cx);
|
||||
} else {
|
||||
cx.reuse_paint(element_state.paint_range.clone());
|
||||
}
|
||||
|
||||
let paint_end = cx.window.next_frame.paint_index();
|
||||
element_state.paint_range = paint_start..paint_end;
|
||||
|
||||
((), Some(element_state))
|
||||
},
|
||||
)
|
||||
} else {
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
element.as_mut().unwrap().paint(cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static + Render> IntoElement for View<V> {
|
||||
type Element = View<V>;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
Some(ElementId::from_entity_id(self.model.entity_id))
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -361,10 +419,6 @@ impl<V: 'static + Render> IntoElement for View<V> {
|
||||
impl IntoElement for AnyView {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
Some(ElementId::from_entity_id(self.model.entity_id))
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
@@ -373,7 +427,7 @@ impl IntoElement for AnyView {
|
||||
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
|
||||
pub struct AnyWeakView {
|
||||
model: AnyWeakModel,
|
||||
layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
|
||||
}
|
||||
|
||||
impl AnyWeakView {
|
||||
@@ -382,7 +436,7 @@ impl AnyWeakView {
|
||||
let model = self.model.upgrade()?;
|
||||
Some(AnyView {
|
||||
model,
|
||||
request_layout: self.layout,
|
||||
render: self.render,
|
||||
cache: false,
|
||||
})
|
||||
}
|
||||
@@ -392,7 +446,7 @@ impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
|
||||
fn from(view: WeakView<V>) -> Self {
|
||||
Self {
|
||||
model: view.model.into(),
|
||||
layout: any_view::request_layout::<V>,
|
||||
render: any_view::render::<V>,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,15 +466,13 @@ impl std::fmt::Debug for AnyWeakView {
|
||||
}
|
||||
|
||||
mod any_view {
|
||||
use crate::{AnyElement, AnyView, ElementContext, IntoElement, LayoutId, Render};
|
||||
use crate::{AnyElement, AnyView, ElementContext, IntoElement, Render};
|
||||
|
||||
pub(crate) fn request_layout<V: 'static + Render>(
|
||||
pub(crate) fn render<V: 'static + Render>(
|
||||
view: &AnyView,
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, AnyElement) {
|
||||
) -> AnyElement {
|
||||
let view = view.clone().downcast::<V>().unwrap();
|
||||
let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element());
|
||||
let layout_id = element.request_layout(cx);
|
||||
(layout_id, element)
|
||||
view.update(cx, |view, cx| view.render(cx).into_any_element())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::{
|
||||
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
|
||||
AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId,
|
||||
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
|
||||
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
|
||||
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
|
||||
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
|
||||
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
|
||||
WindowOptions, WindowTextSystem,
|
||||
px, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, Bounds,
|
||||
Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId,
|
||||
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId,
|
||||
Hsla, KeyBinding, KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model,
|
||||
ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
|
||||
PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels,
|
||||
SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, View, VisualContext,
|
||||
WeakView, WindowAppearance, WindowBounds, WindowOptions, WindowTextSystem,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::FxHashSet;
|
||||
@@ -37,8 +36,6 @@ use util::{measure, ResultExt};
|
||||
mod element_cx;
|
||||
pub use element_cx::*;
|
||||
|
||||
const ACTIVE_DRAG_Z_INDEX: u16 = 1;
|
||||
|
||||
/// A global stacking order, which is created by stacking successive z-index values.
|
||||
/// Each z-index will always be interpreted in the context of its parent z-index.
|
||||
#[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
|
||||
@@ -257,6 +254,7 @@ pub struct Window {
|
||||
pub(crate) element_id_stack: GlobalElementId,
|
||||
pub(crate) rendered_frame: Frame,
|
||||
pub(crate) next_frame: Frame,
|
||||
pub(crate) next_hitbox_id: HitboxId,
|
||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
@@ -264,6 +262,7 @@ pub struct Window {
|
||||
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
|
||||
default_prevented: bool,
|
||||
mouse_position: Point<Pixels>,
|
||||
mouse_hit_test: HitTest,
|
||||
modifiers: Modifiers,
|
||||
scale_factor: f32,
|
||||
bounds: WindowBounds,
|
||||
@@ -280,7 +279,6 @@ pub struct Window {
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
focus_enabled: bool,
|
||||
pending_input: Option<PendingInput>,
|
||||
graphics_profiler_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
@@ -452,12 +450,14 @@ impl Window {
|
||||
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame_callbacks,
|
||||
next_hitbox_id: HitboxId::default(),
|
||||
dirty_views: FxHashSet::default(),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
focus_listeners: SubscriberSet::new(),
|
||||
focus_lost_listeners: SubscriberSet::new(),
|
||||
default_prevented: true,
|
||||
mouse_position,
|
||||
mouse_hit_test: HitTest::default(),
|
||||
modifiers,
|
||||
scale_factor,
|
||||
bounds,
|
||||
@@ -474,7 +474,6 @@ impl Window {
|
||||
focus: None,
|
||||
focus_enabled: true,
|
||||
pending_input: None,
|
||||
graphics_profiler_enabled: false,
|
||||
}
|
||||
}
|
||||
fn new_focus_listener(
|
||||
@@ -585,7 +584,7 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
/// Accessor for the text system.
|
||||
pub fn text_system(&self) -> &Arc<WindowTextSystem> {
|
||||
pub fn text_system(&self) -> &WindowTextSystem {
|
||||
&self.window.text_system
|
||||
}
|
||||
|
||||
@@ -859,95 +858,6 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.modifiers
|
||||
}
|
||||
|
||||
/// Returns true if there is no opaque layer containing the given point
|
||||
/// on top of the given level. Layers who are extensions of the queried layer
|
||||
/// are not considered to be on top of queried layer.
|
||||
pub fn was_top_layer(&self, point: &Point<Pixels>, layer: &StackingOrder) -> bool {
|
||||
// Precondition: the depth map is ordered from topmost to bottomost.
|
||||
|
||||
for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() {
|
||||
if layer >= opaque_layer {
|
||||
// The queried layer is either above or is the same as the this opaque layer.
|
||||
// Anything after this point is guaranteed to be below the queried layer.
|
||||
return true;
|
||||
}
|
||||
|
||||
if !bounds.contains(point) {
|
||||
// This opaque layer is above the queried layer but it doesn't contain
|
||||
// the given position, so we can ignore it even if it's above.
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, we've established that this opaque layer is on top of the queried layer
|
||||
// and contains the position:
|
||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
||||
// we know they are on different stacking orders, and return false.
|
||||
let is_on_same_layer = opaque_layer
|
||||
.iter()
|
||||
.zip(layer.iter())
|
||||
.all(|(a, b)| a.z_index == b.z_index);
|
||||
|
||||
if !is_on_same_layer {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn was_top_layer_under_active_drag(
|
||||
&self,
|
||||
point: &Point<Pixels>,
|
||||
layer: &StackingOrder,
|
||||
) -> bool {
|
||||
// Precondition: the depth map is ordered from topmost to bottomost.
|
||||
|
||||
for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() {
|
||||
if layer >= opaque_layer {
|
||||
// The queried layer is either above or is the same as the this opaque layer.
|
||||
// Anything after this point is guaranteed to be below the queried layer.
|
||||
return true;
|
||||
}
|
||||
|
||||
if !bounds.contains(point) {
|
||||
// This opaque layer is above the queried layer but it doesn't contain
|
||||
// the given position, so we can ignore it even if it's above.
|
||||
continue;
|
||||
}
|
||||
|
||||
// All normal content is rendered with a base z-index of 0, we know that if the root of this opaque layer
|
||||
// equals `ACTIVE_DRAG_Z_INDEX` then it must be the drag layer and we can ignore it as we are
|
||||
// looking to see if the queried layer was the topmost underneath the drag layer.
|
||||
if opaque_layer
|
||||
.first()
|
||||
.map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, we've established that this opaque layer is on top of the queried layer
|
||||
// and contains the position:
|
||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
||||
// we know they are on different stacking orders, and return false.
|
||||
let is_on_same_layer = opaque_layer
|
||||
.iter()
|
||||
.zip(layer.iter())
|
||||
.all(|(a, b)| a.z_index == b.z_index);
|
||||
|
||||
if !is_on_same_layer {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Called during painting to get the current stacking order.
|
||||
pub fn stacking_order(&self) -> &StackingOrder {
|
||||
&self.window.next_frame.z_index_stack
|
||||
}
|
||||
|
||||
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
||||
/// the contents of the new [Scene], use [present].
|
||||
#[profiling::function]
|
||||
@@ -961,54 +871,7 @@ impl<'a> WindowContext<'a> {
|
||||
requested_handler.handler = input_handler;
|
||||
}
|
||||
|
||||
let root_view = self.window.root_view.take().unwrap();
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
|
||||
// We need to use cx.cx here so we can utilize borrow splitting
|
||||
for (action_type, action_listeners) in &cx.cx.app.global_action_listeners {
|
||||
for action_listener in action_listeners.iter().cloned() {
|
||||
cx.cx.window.next_frame.dispatch_tree.on_action(
|
||||
*action_type,
|
||||
Rc::new(
|
||||
move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| {
|
||||
action_listener(action, phase, cx)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let available_space = cx.window.viewport_size.map(Into::into);
|
||||
root_view.draw(Point::default(), available_space, cx);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(active_drag) = self.app.active_drag.take() {
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| {
|
||||
let offset = cx.mouse_position() - active_drag.cursor_offset;
|
||||
let available_space =
|
||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
active_drag.view.draw(offset, available_space, cx);
|
||||
})
|
||||
});
|
||||
self.active_drag = Some(active_drag);
|
||||
} else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() {
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(1, |cx| {
|
||||
let available_space =
|
||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
tooltip_request.tooltip.view.draw(
|
||||
tooltip_request.tooltip.cursor_offset,
|
||||
available_space,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
});
|
||||
self.window.next_frame.tooltip_request = Some(tooltip_request);
|
||||
}
|
||||
self.with_element_context(|cx| cx.draw_roots());
|
||||
self.window.dirty_views.clear();
|
||||
|
||||
self.window
|
||||
@@ -1020,16 +883,10 @@ impl<'a> WindowContext<'a> {
|
||||
);
|
||||
self.window.next_frame.focus = self.window.focus;
|
||||
self.window.next_frame.window_active = self.window.active.get();
|
||||
self.window.root_view = Some(root_view);
|
||||
|
||||
// Set the cursor only if we're the active window.
|
||||
let cursor_style = self
|
||||
.window
|
||||
.next_frame
|
||||
.requested_cursor_style
|
||||
.take()
|
||||
.unwrap_or(CursorStyle::Arrow);
|
||||
if self.is_window_active() {
|
||||
let cursor_style = self.compute_cursor_style().unwrap_or(CursorStyle::Arrow);
|
||||
self.platform.set_cursor_style(cursor_style);
|
||||
}
|
||||
|
||||
@@ -1102,6 +959,18 @@ impl<'a> WindowContext<'a> {
|
||||
profiling::finish_frame!();
|
||||
}
|
||||
|
||||
fn compute_cursor_style(&mut self) -> Option<CursorStyle> {
|
||||
// TODO: maybe we should have a HashMap keyed by HitboxId.
|
||||
let request = self
|
||||
.window
|
||||
.next_frame
|
||||
.cursor_styles
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|request| request.hitbox_id.is_hovered(self))?;
|
||||
Some(request.style)
|
||||
}
|
||||
|
||||
/// Dispatch a given keystroke as though the user had typed it.
|
||||
/// You can create a keystroke with Keystroke::parse("").
|
||||
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool {
|
||||
@@ -1236,43 +1105,32 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
|
||||
if let Some(mut handlers) = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.mouse_listeners
|
||||
.remove(&event.type_id())
|
||||
{
|
||||
// Because handlers may add other handlers, we sort every time.
|
||||
handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
|
||||
self.window.mouse_hit_test = self.window.rendered_frame.hit_test(self.mouse_position());
|
||||
|
||||
let mut mouse_listeners = mem::take(&mut self.window.rendered_frame.mouse_listeners);
|
||||
self.with_element_context(|cx| {
|
||||
// Capture phase, events bubble from back to front. Handlers for this phase are used for
|
||||
// special purposes, such as detecting events outside of a given Bounds.
|
||||
for (_, _, handler) in &mut handlers {
|
||||
self.with_element_context(|cx| {
|
||||
handler(event, DispatchPhase::Capture, cx);
|
||||
});
|
||||
if !self.app.propagate_event {
|
||||
for listener in &mut mouse_listeners {
|
||||
let listener = listener.as_mut().unwrap();
|
||||
listener(event, DispatchPhase::Capture, cx);
|
||||
if !cx.app.propagate_event {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Bubble phase, where most normal handlers do their work.
|
||||
if self.app.propagate_event {
|
||||
for (_, _, handler) in handlers.iter_mut().rev() {
|
||||
self.with_element_context(|cx| {
|
||||
handler(event, DispatchPhase::Bubble, cx);
|
||||
});
|
||||
if !self.app.propagate_event {
|
||||
if cx.app.propagate_event {
|
||||
for listener in mouse_listeners.iter_mut().rev() {
|
||||
let listener = listener.as_mut().unwrap();
|
||||
listener(event, DispatchPhase::Bubble, cx);
|
||||
if !cx.app.propagate_event {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.window
|
||||
.rendered_frame
|
||||
.mouse_listeners
|
||||
.insert(event.type_id(), handlers);
|
||||
}
|
||||
});
|
||||
self.window.rendered_frame.mouse_listeners = mouse_listeners;
|
||||
|
||||
if self.app.propagate_event && self.has_active_drag() {
|
||||
if event.is::<MouseMoveEvent>() {
|
||||
@@ -1342,6 +1200,7 @@ impl<'a> WindowContext<'a> {
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
|
||||
self.window.pending_input = Some(currently_pending);
|
||||
|
||||
self.propagate_event = false;
|
||||
@@ -1522,14 +1381,6 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle the graphics profiler to debug your application's rendering performance.
|
||||
pub fn toggle_graphics_profiler(&mut self) {
|
||||
self.window.graphics_profiler_enabled = !self.window.graphics_profiler_enabled;
|
||||
self.window
|
||||
.platform_window
|
||||
.set_graphics_profiler_enabled(self.window.graphics_profiler_enabled);
|
||||
}
|
||||
|
||||
/// Register the given handler to be invoked whenever the global of the given type
|
||||
/// is updated.
|
||||
pub fn observe_global<G: Global>(
|
||||
@@ -1658,12 +1509,11 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
pub(crate) fn parent_view_id(&self) -> EntityId {
|
||||
*self
|
||||
.window
|
||||
self.window
|
||||
.next_frame
|
||||
.view_stack
|
||||
.last()
|
||||
.expect("a view should always be on the stack while drawing")
|
||||
.dispatch_tree
|
||||
.parent_view_id()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Register an action listener on the window for the next frame. The type of action
|
||||
@@ -2694,12 +2544,6 @@ impl Display for ElementId {
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementId {
|
||||
pub(crate) fn from_entity_id(entity_id: EntityId) -> Self {
|
||||
ElementId::View(entity_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<SharedString> for ElementId {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
|
||||
@@ -13,10 +13,6 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
|
||||
{
|
||||
type Element = gpui::Component<Self>;
|
||||
|
||||
fn element_id(&self) -> Option<gpui::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
gpui::Component::new(self)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
|
||||
#where_clause
|
||||
{
|
||||
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::Element {
|
||||
()
|
||||
gpui::Empty
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||