Compare commits
42 commits
main
...
move-to-en
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ce45e1cb4c | ||
![]() |
78aaaa8b1e | ||
![]() |
2045c83099 | ||
![]() |
f57f6344b4 | ||
![]() |
022fada0f2 | ||
![]() |
ba45a04045 | ||
![]() |
f52d9f8c24 | ||
![]() |
8847f73af5 | ||
![]() |
3a7b4de0f5 | ||
![]() |
82ec8405d6 | ||
![]() |
7b7a046b94 | ||
![]() |
1ba395a33a | ||
![]() |
a165e997ba | ||
![]() |
8444b11e76 | ||
![]() |
609370f9d6 | ||
![]() |
2108c764ad | ||
![]() |
bdedeab7af | ||
![]() |
542fb5c89a | ||
![]() |
0fde56909c | ||
![]() |
d75ef8e62d | ||
![]() |
f5cd8247d1 | ||
![]() |
821960bf14 | ||
![]() |
0ab1e6f451 | ||
![]() |
17d736b23d | ||
![]() |
6006b171f7 | ||
![]() |
5790d9ba27 | ||
![]() |
d5766dc69f | ||
![]() |
3e5c11dc41 | ||
![]() |
ed1a256f99 | ||
![]() |
28e795f2fd | ||
![]() |
98374a70d3 | ||
![]() |
33abbcb535 | ||
![]() |
7481c0d556 | ||
![]() |
a1d5249b8e | ||
![]() |
21bde9b653 | ||
![]() |
5183dbb5be | ||
![]() |
7c6e6971da | ||
![]() |
81b5c74b0e | ||
![]() |
47e48e20d3 | ||
![]() |
4584c5e2b3 | ||
![]() |
e07c53c27f | ||
![]() |
e917d1d251 |
89 changed files with 5566 additions and 5242 deletions
4
.github/workflows/ci.yml
vendored
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
|
||||
|
|
114
Cargo.lock
generated
114
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",
|
||||
|
@ -2447,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",
|
||||
|
@ -2477,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",
|
||||
|
@ -2511,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",
|
||||
|
@ -2523,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",
|
||||
|
@ -2540,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",
|
||||
|
@ -7599,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",
|
||||
|
@ -7610,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",
|
||||
|
@ -7623,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",
|
||||
|
@ -8346,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"
|
||||
|
@ -9922,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",
|
||||
|
@ -10798,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",
|
||||
|
@ -10828,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",
|
||||
|
@ -10851,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",
|
||||
|
@ -10861,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",
|
||||
|
@ -10886,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",
|
||||
|
@ -10902,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",
|
||||
|
@ -10923,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",
|
||||
|
@ -10934,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",
|
||||
|
@ -10961,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",
|
||||
|
@ -10974,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",
|
||||
|
@ -10985,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"
|
||||
|
|
|
@ -310,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"
|
||||
|
|
|
@ -73,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",
|
||||
|
@ -388,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": {
|
||||
|
@ -412,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": {
|
||||
|
|
|
@ -31,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,
|
||||
};
|
||||
|
@ -774,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,
|
||||
);
|
||||
}
|
||||
|
@ -1277,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()
|
||||
}),
|
||||
|
|
|
@ -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!();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})?,
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
|
@ -10137,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);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||
|
|
|
@ -537,13 +537,13 @@ impl Render for ExtensionsPage {
|
|||
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,
|
||||
|
@ -552,14 +552,12 @@ impl Render for ExtensionsPage {
|
|||
.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(),
|
||||
)
|
||||
}))
|
||||
|
|
|
@ -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,7 +96,7 @@ 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"] }
|
||||
|
|
|
@ -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
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 {}
|
||||
|
@ -188,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)
|
||||
}
|
||||
}
|
||||
|
@ -213,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
|
||||
}
|
||||
|
@ -227,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);
|
||||
|
||||
|
@ -238,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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,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(
|
||||
|
@ -418,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,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`.
|
||||
|
@ -466,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)
|
||||
}
|
||||
}
|
||||
|
@ -502,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
|
||||
}
|
||||
|
@ -521,30 +500,32 @@ pub struct Empty;
|
|||
impl IntoElement for Empty {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Empty {
|
||||
type State = ();
|
||||
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
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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))
|
||||
{
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
cx.set_cursor_style(crate::CursorStyle::PointingHand, stacking_order);
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -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!()
|
||||
|
|
|
@ -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,7 +1,7 @@
|
|||
#![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::{PlatformDispatcher, TaskLabel};
|
||||
|
|
|
@ -141,19 +141,19 @@ impl Platform for LinuxPlatform {
|
|||
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>> {
|
||||
|
@ -164,7 +164,7 @@ impl Platform for LinuxPlatform {
|
|||
self.client.display(id)
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||
None
|
||||
}
|
||||
|
@ -328,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 {
|
||||
|
@ -339,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()),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//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::*;
|
||||
|
|
|
@ -183,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
|
||||
|
@ -482,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());
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -132,7 +132,7 @@ impl WaylandWindowState {
|
|||
size: Size {
|
||||
width: 500,
|
||||
height: 500,
|
||||
}, //todo!(implement)
|
||||
}, // todo(implement)
|
||||
},
|
||||
WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32),
|
||||
};
|
||||
|
@ -200,7 +200,7 @@ impl WaylandWindowState {
|
|||
pub fn set_decoration_state(&self, state: WaylandDecorationState) {
|
||||
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) {
|
||||
|
@ -250,7 +250,7 @@ impl HasDisplayHandle for WaylandWindow {
|
|||
}
|
||||
|
||||
impl PlatformWindow for WaylandWindow {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
WindowBounds::Maximized
|
||||
}
|
||||
|
@ -267,32 +267,32 @@ impl PlatformWindow for WaylandWindow {
|
|||
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!()
|
||||
}
|
||||
|
@ -305,7 +305,7 @@ impl PlatformWindow for WaylandWindow {
|
|||
self.0.inner.borrow_mut().input_handler.take()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn prompt(
|
||||
&self,
|
||||
level: PromptLevel,
|
||||
|
@ -317,7 +317,7 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
fn activate(&self) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
|
@ -325,23 +325,23 @@ 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()>) {
|
||||
|
@ -361,7 +361,7 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
}
|
||||
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
||||
|
@ -377,10 +377,10 @@ impl PlatformWindow for WaylandWindow {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//todo!(linux): remove
|
||||
// todo(linux): remove
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::{
|
||||
|
@ -99,7 +99,7 @@ pub(crate) struct X11WindowState {
|
|||
#[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();
|
||||
|
@ -301,7 +301,7 @@ impl X11WindowState {
|
|||
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 {
|
||||
|
@ -377,12 +377,12 @@ impl PlatformWindow for X11Window {
|
|||
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
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ impl PlatformWindow for X11Window {
|
|||
)
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn modifiers(&self) -> Modifiers {
|
||||
Modifiers::default()
|
||||
}
|
||||
|
@ -419,7 +419,7 @@ impl PlatformWindow for X11Window {
|
|||
self.0.inner.borrow_mut().input_handler.take()
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn prompt(
|
||||
&self,
|
||||
_level: PromptLevel,
|
||||
|
@ -447,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
|
||||
|
@ -460,17 +460,17 @@ 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!()
|
||||
}
|
||||
|
@ -511,7 +511,7 @@ impl PlatformWindow for X11Window {
|
|||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
// todo(linux)
|
||||
fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
|
@ -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,
|
||||
|
@ -451,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,
|
||||
|
@ -583,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
|
||||
}
|
||||
|
||||
|
@ -857,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]
|
||||
|
@ -959,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
|
||||
|
@ -1018,13 +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_request = self.window.next_frame.requested_cursor_style.take();
|
||||
if self.is_window_active() {
|
||||
let cursor_style =
|
||||
cursor_style_request.map_or(CursorStyle::Arrow, |request| request.style);
|
||||
let cursor_style = self.compute_cursor_style().unwrap_or(CursorStyle::Arrow);
|
||||
self.platform.set_cursor_style(cursor_style);
|
||||
}
|
||||
|
||||
|
@ -1097,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 {
|
||||
|
@ -1231,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>() {
|
||||
|
@ -1337,6 +1200,7 @@ impl<'a> WindowContext<'a> {
|
|||
})
|
||||
.log_err();
|
||||
}));
|
||||
|
||||
self.window.pending_input = Some(currently_pending);
|
||||
|
||||
self.propagate_event = false;
|
||||
|
@ -1645,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
|
||||
|
@ -2681,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;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> {
|
|||
// If the symlink is not there or is outdated, first try replacing it
|
||||
// without escalating.
|
||||
smol::fs::remove_file(link_path).await.log_err();
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
if smol::fs::unix::symlink(&cli_path, link_path)
|
||||
|
|
|
@ -807,7 +807,7 @@ impl Render for LspLogToolbarItemView {
|
|||
.justify_between()
|
||||
.child(Label::new(RPC_MESSAGES))
|
||||
.child(
|
||||
div().z_index(120).child(
|
||||
div().child(
|
||||
Checkbox::new(
|
||||
ix,
|
||||
if row.rpc_trace_enabled {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId};
|
||||
use gpui::{
|
||||
actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div,
|
||||
EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
|
||||
actions, canvas, div, rems, uniform_list, AnyElement, AppContext, Div, EventEmitter,
|
||||
FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::{Buffer, OwnedSyntaxLayer};
|
||||
use std::{mem, ops::Range};
|
||||
|
@ -277,7 +277,7 @@ impl Render for SyntaxTreeView {
|
|||
.and_then(|buffer| buffer.active_layer.as_ref())
|
||||
{
|
||||
let layer = layer.clone();
|
||||
let list = uniform_list(
|
||||
let mut list = uniform_list(
|
||||
cx.view().clone(),
|
||||
"SyntaxTreeView",
|
||||
layer.node().descendant_count(),
|
||||
|
@ -356,16 +356,16 @@ impl Render for SyntaxTreeView {
|
|||
)
|
||||
.size_full()
|
||||
.track_scroll(self.list_scroll_handle.clone())
|
||||
.text_bg(cx.theme().colors().background);
|
||||
.text_bg(cx.theme().colors().background).into_any_element();
|
||||
|
||||
rendered = rendered.child(
|
||||
canvas(move |bounds, cx| {
|
||||
list.into_any_element().draw(
|
||||
bounds.origin,
|
||||
bounds.size.map(AvailableSpace::Definite),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
canvas(
|
||||
move |bounds, cx| {
|
||||
list.layout(bounds.origin, bounds.size.into(), cx);
|
||||
list
|
||||
},
|
||||
|_, mut list, cx| list.paint(cx),
|
||||
)
|
||||
.size_full(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ impl super::LspAdapter for OmniSharpAdapter {
|
|||
archive.unpack(container_dir).await?;
|
||||
}
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
|
|
|
@ -358,7 +358,7 @@ impl LspAdapter for NextLspAdapter {
|
|||
}
|
||||
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
|
|
|
@ -83,7 +83,7 @@ impl super::LspAdapter for LuaLspAdapter {
|
|||
archive.unpack(container_dir).await?;
|
||||
}
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
|
|
|
@ -26,7 +26,7 @@ pub struct PurescriptLspAdapter {
|
|||
}
|
||||
|
||||
impl PurescriptLspAdapter {
|
||||
// todo!(linux): remove
|
||||
// todo(linux): remove
|
||||
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
Self { node }
|
||||
|
|
|
@ -74,7 +74,7 @@ impl LspAdapter for RustLspAdapter {
|
|||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
let mut file = File::create(&destination_path).await?;
|
||||
futures::io::copy(decompressed_bytes, &mut file).await?;
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
|
|
|
@ -72,7 +72,7 @@ impl LspAdapter for TaploLspAdapter {
|
|||
|
||||
futures::io::copy(decompressed_bytes, &mut file).await?;
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
|
|
|
@ -86,7 +86,7 @@ impl LspAdapter for ZlsAdapter {
|
|||
archive.unpack(container_dir).await?;
|
||||
}
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
|
|
|
@ -73,7 +73,7 @@ impl TestServer {
|
|||
}
|
||||
|
||||
pub async fn create_room(&self, room: String) -> Result<()> {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
let mut server_rooms = self.rooms.lock();
|
||||
|
@ -87,7 +87,7 @@ impl TestServer {
|
|||
|
||||
async fn delete_room(&self, room: String) -> Result<()> {
|
||||
// TODO: clear state associated with all `Room`s.
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
let mut server_rooms = self.rooms.lock();
|
||||
|
@ -98,7 +98,7 @@ impl TestServer {
|
|||
}
|
||||
|
||||
async fn join_room(&self, token: String, client_room: Arc<Room>) -> Result<()> {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
|
||||
|
@ -147,7 +147,7 @@ impl TestServer {
|
|||
}
|
||||
|
||||
async fn leave_room(&self, token: String) -> Result<()> {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
|
||||
|
@ -169,7 +169,7 @@ impl TestServer {
|
|||
|
||||
async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> {
|
||||
// TODO: clear state associated with the `Room`.
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
|
||||
|
@ -193,7 +193,7 @@ impl TestServer {
|
|||
identity: String,
|
||||
permission: proto::ParticipantPermission,
|
||||
) -> Result<()> {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
let mut server_rooms = self.rooms.lock();
|
||||
|
@ -205,7 +205,7 @@ impl TestServer {
|
|||
}
|
||||
|
||||
pub async fn disconnect_client(&self, client_identity: String) {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
let mut server_rooms = self.rooms.lock();
|
||||
|
@ -221,7 +221,7 @@ impl TestServer {
|
|||
token: String,
|
||||
local_track: LocalVideoTrack,
|
||||
) -> Result<Sid> {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
let claims = live_kit_server::token::validate(&token, &self.secret_key)?;
|
||||
|
@ -276,7 +276,7 @@ impl TestServer {
|
|||
token: String,
|
||||
_local_track: &LocalAudioTrack,
|
||||
) -> Result<Sid> {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
self.executor.simulate_random_delay().await;
|
||||
|
||||
|
@ -559,7 +559,7 @@ impl Room {
|
|||
pub fn display_sources(self: &Arc<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
|
||||
let this = self.clone();
|
||||
async move {
|
||||
//todo!(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
// todo(linux): Remove this once the cross-platform LiveKit implementation is merged
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
{
|
||||
let server = this.test_server();
|
||||
|
|
|
@ -7,7 +7,6 @@ mod picker;
|
|||
mod scroll;
|
||||
mod text;
|
||||
mod viewport_units;
|
||||
mod z_index;
|
||||
|
||||
pub use auto_height_editor::*;
|
||||
pub use cursor::*;
|
||||
|
@ -18,4 +17,3 @@ pub use picker::*;
|
|||
pub use scroll::*;
|
||||
pub use text::*;
|
||||
pub use viewport_units::*;
|
||||
pub use z_index::*;
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
use gpui::{px, rgb, Div, IntoElement, Render, RenderOnce};
|
||||
use story::Story;
|
||||
use ui::prelude::*;
|
||||
|
||||
/// A reimplementation of the MDN `z-index` example, found here:
|
||||
/// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
|
||||
pub struct ZIndexStory;
|
||||
|
||||
impl Render for ZIndexStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
Story::container().child(Story::title("z-index")).child(
|
||||
div()
|
||||
.flex()
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: auto"))
|
||||
.child(ZIndexExample::new(0)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 1"))
|
||||
.child(ZIndexExample::new(1)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 3"))
|
||||
.child(ZIndexExample::new(3)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 5"))
|
||||
.child(ZIndexExample::new(5)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 7"))
|
||||
.child(ZIndexExample::new(7)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
trait Styles: Styled + Sized {
|
||||
// Trailing `_` is so we don't collide with `block` style `StyleHelpers`.
|
||||
fn block_(self) -> Self {
|
||||
self.absolute()
|
||||
.w(px(150.))
|
||||
.h(px(50.))
|
||||
.text_color(rgb(0x000000))
|
||||
}
|
||||
|
||||
fn blue(self) -> Self {
|
||||
self.bg(rgb(0xe5e8fc))
|
||||
.border_5()
|
||||
.border_color(rgb(0x112382))
|
||||
.line_height(px(55.))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(24.))
|
||||
}
|
||||
|
||||
fn red(self) -> Self {
|
||||
self.bg(rgb(0xfce5e7))
|
||||
.border_5()
|
||||
.border_color(rgb(0xe3a1a7))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(8.))
|
||||
}
|
||||
}
|
||||
|
||||
impl Styles for Div {}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct ZIndexExample {
|
||||
z_index: u16,
|
||||
}
|
||||
|
||||
impl RenderOnce for ZIndexExample {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
div()
|
||||
.relative()
|
||||
.size_full()
|
||||
// Example element.
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top(px(15.))
|
||||
.left(px(15.))
|
||||
.w(px(180.))
|
||||
.h(px(230.))
|
||||
.bg(rgb(0xfcfbe5))
|
||||
.text_color(rgb(0x000000))
|
||||
.border_5()
|
||||
.border_color(rgb(0xe3e0a1))
|
||||
.line_height(px(215.))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(24.))
|
||||
.z_index(self.z_index)
|
||||
.child(SharedString::from(format!(
|
||||
"z-index: {}",
|
||||
if self.z_index == 0 {
|
||||
"auto".to_string()
|
||||
} else {
|
||||
self.z_index.to_string()
|
||||
}
|
||||
))),
|
||||
)
|
||||
// Blue blocks.
|
||||
.child(
|
||||
div()
|
||||
.blue()
|
||||
.block_()
|
||||
.top(px(0.))
|
||||
.left(px(0.))
|
||||
.z_index(6)
|
||||
.child("z-index: 6"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.blue()
|
||||
.block_()
|
||||
.top(px(30.))
|
||||
.left(px(30.))
|
||||
.z_index(4)
|
||||
.child("z-index: 4"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.blue()
|
||||
.block_()
|
||||
.top(px(60.))
|
||||
.left(px(60.))
|
||||
.z_index(2)
|
||||
.child("z-index: 2"),
|
||||
)
|
||||
// Red blocks.
|
||||
.child(
|
||||
div()
|
||||
.red()
|
||||
.block_()
|
||||
.top(px(150.))
|
||||
.left(px(0.))
|
||||
.child("z-index: auto"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.red()
|
||||
.block_()
|
||||
.top(px(180.))
|
||||
.left(px(30.))
|
||||
.child("z-index: auto"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.red()
|
||||
.block_()
|
||||
.top(px(210.))
|
||||
.left(px(60.))
|
||||
.child("z-index: auto"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZIndexExample {
|
||||
pub fn new(z_index: u16) -> Self {
|
||||
Self { z_index }
|
||||
}
|
||||
}
|
|
@ -35,7 +35,6 @@ pub enum ComponentStory {
|
|||
ToggleButton,
|
||||
Text,
|
||||
ViewportUnits,
|
||||
ZIndex,
|
||||
Picker,
|
||||
}
|
||||
|
||||
|
@ -67,7 +66,6 @@ impl ComponentStory {
|
|||
Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(),
|
||||
Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(),
|
||||
Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
|
||||
Self::ZIndex => cx.new_view(|_| ZIndexStory).into(),
|
||||
Self::Picker => PickerStory::new(cx).into(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -399,7 +399,7 @@ impl TerminalBuilder {
|
|||
#[cfg(unix)]
|
||||
let (fd, shell_pid) = (pty.file().as_raw_fd(), pty.child().id());
|
||||
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(windows)]
|
||||
let (fd, shell_pid) = (-1, 0);
|
||||
|
||||
|
@ -667,7 +667,7 @@ impl Terminal {
|
|||
fn update_process_info(&mut self) -> bool {
|
||||
#[cfg(unix)]
|
||||
let mut pid = unsafe { libc::tcgetpgrp(self.shell_fd as i32) };
|
||||
// todo!("windows")
|
||||
// todo("windows")
|
||||
#[cfg(windows)]
|
||||
let mut pid = -1;
|
||||
if pid < 0 {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -100,7 +100,7 @@ pub(crate) fn one_dark() -> Theme {
|
|||
editor_document_highlight_write_background: gpui::red(),
|
||||
|
||||
terminal_background: bg,
|
||||
// todo!("Use one colors for terminal")
|
||||
// todo("Use one colors for terminal")
|
||||
terminal_foreground: crate::white().dark().step_12(),
|
||||
terminal_bright_foreground: crate::white().dark().step_11(),
|
||||
terminal_dim_foreground: crate::white().dark().step_10(),
|
||||
|
|
|
@ -130,7 +130,7 @@ pub struct ThemeColors {
|
|||
/// The border color of the scrollbar track.
|
||||
pub scrollbar_track_border: Hsla,
|
||||
// /// The opacity of the scrollbar status marks, like diagnostic states and git status.
|
||||
// todo!()
|
||||
// todo()
|
||||
// pub scrollbar_status_opacity: Hsla,
|
||||
|
||||
// ===
|
||||
|
|
|
@ -25,8 +25,8 @@ pub fn format_localized_timestamp(
|
|||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
//todo!(linux) respect user's date/time preferences
|
||||
//todo!(windows) respect user's date/time preferences
|
||||
// todo(linux) respect user's date/time preferences
|
||||
// todo(windows) respect user's date/time preferences
|
||||
format_timestamp_fallback(reference, timestamp, timezone)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,9 +122,6 @@ impl RenderOnce for Avatar {
|
|||
.size(image_size)
|
||||
.bg(cx.theme().colors().ghost_element_background),
|
||||
)
|
||||
.children(
|
||||
self.indicator
|
||||
.map(|indicator| div().z_index(1).child(indicator)),
|
||||
)
|
||||
.children(self.indicator.map(|indicator| div().child(indicator)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
|
|||
|
||||
use gpui::{
|
||||
overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds,
|
||||
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, InteractiveBounds,
|
||||
IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
|
||||
VisualContext, WindowContext,
|
||||
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, HitboxId, IntoElement,
|
||||
LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
use crate::{Clickable, Selectable};
|
||||
|
@ -107,6 +107,21 @@ impl<M: ManagedView> PopoverMenu<M> {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn with_element_state<R>(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&mut Self, &mut PopoverMenuElementState<M>, &mut ElementContext) -> R,
|
||||
) -> R {
|
||||
cx.with_element_state::<PopoverMenuElementState<M>, _>(
|
||||
Some(self.id.clone()),
|
||||
|element_state, cx| {
|
||||
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||
let result = f(self, &mut element_state, cx);
|
||||
(result, Some(element_state))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`PopoverMenu`]
|
||||
|
@ -121,114 +136,136 @@ pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M>
|
|||
}
|
||||
}
|
||||
|
||||
pub struct PopoverMenuState<M> {
|
||||
pub struct PopoverMenuElementState<M> {
|
||||
menu: Rc<RefCell<Option<View<M>>>>,
|
||||
child_bounds: Option<Bounds<Pixels>>,
|
||||
}
|
||||
|
||||
impl<M> Clone for PopoverMenuElementState<M> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
menu: Rc::clone(&self.menu),
|
||||
child_bounds: self.child_bounds.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Default for PopoverMenuElementState<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
menu: Rc::default(),
|
||||
child_bounds: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PopoverMenuFrameState {
|
||||
child_layout_id: Option<LayoutId>,
|
||||
child_element: Option<AnyElement>,
|
||||
child_bounds: Option<Bounds<Pixels>>,
|
||||
menu_element: Option<AnyElement>,
|
||||
menu: Rc<RefCell<Option<View<M>>>>,
|
||||
}
|
||||
|
||||
impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
type State = PopoverMenuState<M>;
|
||||
type BeforeLayout = PopoverMenuFrameState;
|
||||
type AfterLayout = Option<HitboxId>;
|
||||
|
||||
fn request_layout(
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
|
||||
self.with_element_state(cx, |this, element_state, cx| {
|
||||
let mut menu_layout_id = None;
|
||||
|
||||
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
|
||||
let mut overlay = overlay().snap_to_window().anchor(this.anchor);
|
||||
|
||||
if let Some(child_bounds) = element_state.child_bounds {
|
||||
overlay = overlay.position(
|
||||
this.resolved_attach().corner(child_bounds) + this.resolved_offset(cx),
|
||||
);
|
||||
}
|
||||
|
||||
let mut element = overlay.child(menu.clone()).into_any();
|
||||
menu_layout_id = Some(element.before_layout(cx));
|
||||
element
|
||||
});
|
||||
|
||||
let mut child_element = this.child_builder.take().map(|child_builder| {
|
||||
(child_builder)(element_state.menu.clone(), this.menu_builder.clone())
|
||||
});
|
||||
|
||||
let child_layout_id = child_element
|
||||
.as_mut()
|
||||
.map(|child_element| child_element.before_layout(cx));
|
||||
|
||||
let layout_id = cx.request_layout(
|
||||
&gpui::Style::default(),
|
||||
menu_layout_id.into_iter().chain(child_layout_id),
|
||||
);
|
||||
|
||||
(
|
||||
layout_id,
|
||||
PopoverMenuFrameState {
|
||||
child_element,
|
||||
child_layout_id,
|
||||
menu_element,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
element_state: Option<Self::State>,
|
||||
_bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> (gpui::LayoutId, Self::State) {
|
||||
let mut menu_layout_id = None;
|
||||
|
||||
let (menu, child_bounds) = if let Some(element_state) = element_state {
|
||||
(element_state.menu, element_state.child_bounds)
|
||||
} else {
|
||||
(Rc::default(), None)
|
||||
};
|
||||
|
||||
let menu_element = menu.borrow_mut().as_mut().map(|menu| {
|
||||
let mut overlay = overlay().snap_to_window().anchor(self.anchor);
|
||||
|
||||
if let Some(child_bounds) = child_bounds {
|
||||
overlay = overlay.position(
|
||||
self.resolved_attach().corner(child_bounds) + self.resolved_offset(cx),
|
||||
);
|
||||
) -> Option<HitboxId> {
|
||||
self.with_element_state(cx, |_this, element_state, cx| {
|
||||
if let Some(child) = before_layout.child_element.as_mut() {
|
||||
child.after_layout(cx);
|
||||
}
|
||||
|
||||
let mut element = overlay.child(menu.clone()).into_any();
|
||||
menu_layout_id = Some(element.request_layout(cx));
|
||||
element
|
||||
});
|
||||
if let Some(menu) = before_layout.menu_element.as_mut() {
|
||||
menu.after_layout(cx);
|
||||
}
|
||||
|
||||
let mut child_element = self
|
||||
.child_builder
|
||||
.take()
|
||||
.map(|child_builder| (child_builder)(menu.clone(), self.menu_builder.clone()));
|
||||
|
||||
let child_layout_id = child_element
|
||||
.as_mut()
|
||||
.map(|child_element| child_element.request_layout(cx));
|
||||
|
||||
let layout_id = cx.request_layout(
|
||||
&gpui::Style::default(),
|
||||
menu_layout_id.into_iter().chain(child_layout_id),
|
||||
);
|
||||
|
||||
(
|
||||
layout_id,
|
||||
PopoverMenuState {
|
||||
menu,
|
||||
child_element,
|
||||
child_layout_id,
|
||||
menu_element,
|
||||
child_bounds,
|
||||
},
|
||||
)
|
||||
before_layout.child_layout_id.map(|layout_id| {
|
||||
let bounds = cx.layout_bounds(layout_id);
|
||||
element_state.child_bounds = Some(bounds);
|
||||
cx.insert_hitbox(bounds, false).id
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Bounds<gpui::Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
child_hitbox: &mut Option<HitboxId>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if let Some(mut child) = element_state.child_element.take() {
|
||||
child.paint(cx);
|
||||
}
|
||||
|
||||
if let Some(child_layout_id) = element_state.child_layout_id.take() {
|
||||
element_state.child_bounds = Some(cx.layout_bounds(child_layout_id));
|
||||
}
|
||||
|
||||
if let Some(mut menu) = element_state.menu_element.take() {
|
||||
menu.paint(cx);
|
||||
|
||||
if let Some(child_bounds) = element_state.child_bounds {
|
||||
let interactive_bounds = InteractiveBounds {
|
||||
bounds: child_bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
|
||||
// Mouse-downing outside the menu dismisses it, so we don't
|
||||
// want a click on the toggle to re-open it.
|
||||
cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.visibly_contains(&e.position, cx)
|
||||
{
|
||||
cx.stop_propagation()
|
||||
}
|
||||
})
|
||||
self.with_element_state(cx, |_this, _element_state, cx| {
|
||||
if let Some(mut child) = before_layout.child_element.take() {
|
||||
child.paint(cx);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut menu) = before_layout.menu_element.take() {
|
||||
menu.paint(cx);
|
||||
|
||||
if let Some(child_hitbox) = *child_hitbox {
|
||||
// Mouse-downing outside the menu dismisses it, so we don't
|
||||
// want a click on the toggle to re-open it.
|
||||
cx.on_mouse_event(move |_: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && child_hitbox.is_hovered(cx) {
|
||||
cx.stop_propagation()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: ManagedView> IntoElement for PopoverMenu<M> {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<gpui::ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc};
|
|||
|
||||
use gpui::{
|
||||
overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element,
|
||||
ElementContext, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton,
|
||||
ElementContext, ElementId, Hitbox, IntoElement, LayoutId, ManagedView, MouseButton,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext,
|
||||
};
|
||||
|
||||
|
@ -37,6 +37,21 @@ impl<M: ManagedView> RightClickMenu<M> {
|
|||
self.attach = Some(attach);
|
||||
self
|
||||
}
|
||||
|
||||
fn with_element_state<R>(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut ElementContext) -> R,
|
||||
) -> R {
|
||||
cx.with_element_state::<MenuHandleElementState<M>, _>(
|
||||
Some(self.id.clone()),
|
||||
|element_state, cx| {
|
||||
let mut element_state = element_state.unwrap().unwrap_or_default();
|
||||
let result = f(self, &mut element_state, cx);
|
||||
(result, Some(element_state))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`RightClickMenu`]
|
||||
|
@ -50,139 +65,169 @@ pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickM
|
|||
}
|
||||
}
|
||||
|
||||
pub struct MenuHandleState<M> {
|
||||
pub struct MenuHandleElementState<M> {
|
||||
menu: Rc<RefCell<Option<View<M>>>>,
|
||||
position: Rc<RefCell<Point<Pixels>>>,
|
||||
}
|
||||
|
||||
impl<M> Clone for MenuHandleElementState<M> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
menu: Rc::clone(&self.menu),
|
||||
position: Rc::clone(&self.position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Default for MenuHandleElementState<M> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
menu: Rc::default(),
|
||||
position: Rc::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MenuHandleFrameState {
|
||||
child_layout_id: Option<LayoutId>,
|
||||
child_element: Option<AnyElement>,
|
||||
menu_element: Option<AnyElement>,
|
||||
}
|
||||
|
||||
impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
type State = MenuHandleState<M>;
|
||||
type BeforeLayout = MenuHandleFrameState;
|
||||
type AfterLayout = Hitbox;
|
||||
|
||||
fn request_layout(
|
||||
fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
|
||||
self.with_element_state(cx, |this, element_state, cx| {
|
||||
let mut menu_layout_id = None;
|
||||
|
||||
let menu_element = element_state.menu.borrow_mut().as_mut().map(|menu| {
|
||||
let mut overlay = overlay().snap_to_window();
|
||||
if let Some(anchor) = this.anchor {
|
||||
overlay = overlay.anchor(anchor);
|
||||
}
|
||||
overlay = overlay.position(*element_state.position.borrow());
|
||||
|
||||
let mut element = overlay.child(menu.clone()).into_any();
|
||||
menu_layout_id = Some(element.before_layout(cx));
|
||||
element
|
||||
});
|
||||
|
||||
let mut child_element = this
|
||||
.child_builder
|
||||
.take()
|
||||
.map(|child_builder| (child_builder)(element_state.menu.borrow().is_some()));
|
||||
|
||||
let child_layout_id = child_element
|
||||
.as_mut()
|
||||
.map(|child_element| child_element.before_layout(cx));
|
||||
|
||||
let layout_id = cx.request_layout(
|
||||
&gpui::Style::default(),
|
||||
menu_layout_id.into_iter().chain(child_layout_id),
|
||||
);
|
||||
|
||||
(
|
||||
layout_id,
|
||||
MenuHandleFrameState {
|
||||
child_element,
|
||||
child_layout_id,
|
||||
menu_element,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
element_state: Option<Self::State>,
|
||||
bounds: Bounds<Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) -> (gpui::LayoutId, Self::State) {
|
||||
let (menu, position) = if let Some(element_state) = element_state {
|
||||
(element_state.menu, element_state.position)
|
||||
} else {
|
||||
(Rc::default(), Rc::default())
|
||||
};
|
||||
) -> Hitbox {
|
||||
cx.with_element_id(Some(self.id.clone()), |cx| {
|
||||
// todo!("occlude child bounds instead?")
|
||||
let hitbox = cx.insert_hitbox(bounds, false);
|
||||
|
||||
let mut menu_layout_id = None;
|
||||
|
||||
let menu_element = menu.borrow_mut().as_mut().map(|menu| {
|
||||
let mut overlay = overlay().snap_to_window();
|
||||
if let Some(anchor) = self.anchor {
|
||||
overlay = overlay.anchor(anchor);
|
||||
if let Some(child) = before_layout.child_element.as_mut() {
|
||||
child.after_layout(cx);
|
||||
}
|
||||
overlay = overlay.position(*position.borrow());
|
||||
|
||||
let mut element = overlay.child(menu.clone()).into_any();
|
||||
menu_layout_id = Some(element.request_layout(cx));
|
||||
element
|
||||
});
|
||||
if let Some(menu) = before_layout.menu_element.as_mut() {
|
||||
menu.after_layout(cx);
|
||||
}
|
||||
|
||||
let mut child_element = self
|
||||
.child_builder
|
||||
.take()
|
||||
.map(|child_builder| (child_builder)(menu.borrow().is_some()));
|
||||
|
||||
let child_layout_id = child_element
|
||||
.as_mut()
|
||||
.map(|child_element| child_element.request_layout(cx));
|
||||
|
||||
let layout_id = cx.request_layout(
|
||||
&gpui::Style::default(),
|
||||
menu_layout_id.into_iter().chain(child_layout_id),
|
||||
);
|
||||
|
||||
(
|
||||
layout_id,
|
||||
MenuHandleState {
|
||||
menu,
|
||||
position,
|
||||
child_element,
|
||||
child_layout_id,
|
||||
menu_element,
|
||||
},
|
||||
)
|
||||
hitbox
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<gpui::Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
_bounds: Bounds<gpui::Pixels>,
|
||||
before_layout: &mut Self::BeforeLayout,
|
||||
hitbox: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if let Some(mut child) = element_state.child_element.take() {
|
||||
child.paint(cx);
|
||||
}
|
||||
|
||||
if let Some(mut menu) = element_state.menu_element.take() {
|
||||
menu.paint(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(builder) = self.menu_builder.take() else {
|
||||
return;
|
||||
};
|
||||
let menu = element_state.menu.clone();
|
||||
let position = element_state.position.clone();
|
||||
let attach = self.attach.clone();
|
||||
let child_layout_id = element_state.child_layout_id.clone();
|
||||
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
|
||||
|
||||
let interactive_bounds = InteractiveBounds {
|
||||
bounds: bounds.intersect(&cx.content_mask().bounds),
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == MouseButton::Right
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
cx.stop_propagation();
|
||||
cx.prevent_default();
|
||||
|
||||
let new_menu = (builder)(cx);
|
||||
let menu2 = menu.clone();
|
||||
let previous_focus_handle = cx.focused();
|
||||
|
||||
cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
|
||||
if modal.focus_handle(cx).contains_focused(cx) {
|
||||
if previous_focus_handle.is_some() {
|
||||
cx.focus(previous_focus_handle.as_ref().unwrap())
|
||||
}
|
||||
}
|
||||
*menu2.borrow_mut() = None;
|
||||
cx.refresh();
|
||||
})
|
||||
.detach();
|
||||
cx.focus_view(&new_menu);
|
||||
*menu.borrow_mut() = Some(new_menu);
|
||||
|
||||
*position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
|
||||
attach.unwrap().corner(child_bounds)
|
||||
} else {
|
||||
cx.mouse_position()
|
||||
};
|
||||
cx.refresh();
|
||||
self.with_element_state(cx, |this, element_state, cx| {
|
||||
if let Some(mut child) = before_layout.child_element.take() {
|
||||
child.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(mut menu) = before_layout.menu_element.take() {
|
||||
menu.paint(cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(builder) = this.menu_builder.take() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let attach = this.attach.clone();
|
||||
let menu = element_state.menu.clone();
|
||||
let position = element_state.position.clone();
|
||||
let child_layout_id = before_layout.child_layout_id.clone();
|
||||
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
|
||||
|
||||
let hitbox_id = hitbox.id;
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == MouseButton::Right
|
||||
&& hitbox_id.is_hovered(cx)
|
||||
{
|
||||
cx.stop_propagation();
|
||||
cx.prevent_default();
|
||||
|
||||
let new_menu = (builder)(cx);
|
||||
let menu2 = menu.clone();
|
||||
let previous_focus_handle = cx.focused();
|
||||
|
||||
cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
|
||||
if modal.focus_handle(cx).contains_focused(cx) {
|
||||
if previous_focus_handle.is_some() {
|
||||
cx.focus(previous_focus_handle.as_ref().unwrap())
|
||||
}
|
||||
}
|
||||
*menu2.borrow_mut() = None;
|
||||
cx.refresh();
|
||||
})
|
||||
.detach();
|
||||
cx.focus_view(&new_menu);
|
||||
*menu.borrow_mut() = Some(new_menu);
|
||||
*position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() {
|
||||
attach.unwrap().corner(child_bounds)
|
||||
} else {
|
||||
cx.mouse_position()
|
||||
};
|
||||
cx.refresh();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: ManagedView> IntoElement for RightClickMenu<M> {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<gpui::ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
|
|
@ -123,7 +123,6 @@ impl RenderOnce for TabBar {
|
|||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.z_index(1)
|
||||
.size_full()
|
||||
.border_b()
|
||||
.border_color(cx.theme().colors().border),
|
||||
|
@ -131,7 +130,6 @@ impl RenderOnce for TabBar {
|
|||
.child(
|
||||
h_flex()
|
||||
.id("tabs")
|
||||
.z_index(2)
|
||||
.flex_grow()
|
||||
.overflow_x_scroll()
|
||||
.when_some(self.scroll_handle, |cx, scroll_handle| {
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{ElevationIndex, UiTextSize};
|
|||
|
||||
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
||||
this.bg(cx.theme().colors().elevated_surface_background)
|
||||
.z_index(index.z_index())
|
||||
// .z_index(index.z_index()) // TODO! we need a new solution here.
|
||||
.rounded(px(8.))
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
|
|
|
@ -563,7 +563,6 @@ impl Render for Dock {
|
|||
cx.stop_propagation();
|
||||
}
|
||||
}))
|
||||
.z_index(1)
|
||||
.block_mouse();
|
||||
|
||||
match self.position() {
|
||||
|
|
|
@ -139,21 +139,15 @@ impl Render for ModalLayer {
|
|||
return div();
|
||||
};
|
||||
|
||||
div()
|
||||
.absolute()
|
||||
.size_full()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.z_index(169)
|
||||
.child(
|
||||
v_flex()
|
||||
.h(px(0.0))
|
||||
.top_20()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.track_focus(&active_modal.focus_handle)
|
||||
.child(h_flex().child(active_modal.modal.view())),
|
||||
)
|
||||
div().absolute().size_full().top_0().left_0().child(
|
||||
v_flex()
|
||||
.h(px(0.0))
|
||||
.top_20()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.track_focus(&active_modal.focus_handle)
|
||||
.child(h_flex().child(active_modal.modal.view())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1549,7 +1549,6 @@ impl Pane {
|
|||
fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(1)
|
||||
.bottom_0()
|
||||
.right_0()
|
||||
.size_0()
|
||||
|
@ -1867,7 +1866,6 @@ impl Render for Pane {
|
|||
.child(
|
||||
// drag target
|
||||
div()
|
||||
.z_index(1)
|
||||
.invisible()
|
||||
.absolute()
|
||||
.bg(theme::color_alpha(
|
||||
|
|
|
@ -258,7 +258,6 @@ impl Member {
|
|||
.right_3()
|
||||
.elevation_2(cx)
|
||||
.p_1()
|
||||
.z_index(1)
|
||||
.child(status_box)
|
||||
.when_some(
|
||||
leader_join_data,
|
||||
|
@ -588,9 +587,9 @@ mod element {
|
|||
use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
|
||||
|
||||
use gpui::{
|
||||
px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, IntoElement,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style,
|
||||
WeakView, WindowContext,
|
||||
px, relative, Along, AnyElement, Axis, Bounds, Element, IntoElement, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Size, Style, WeakView,
|
||||
WindowContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use settings::Settings;
|
||||
|
@ -753,58 +752,47 @@ mod element {
|
|||
size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
|
||||
};
|
||||
|
||||
cx.with_z_index(3, |cx| {
|
||||
if handle_bounds.contains(&cx.mouse_position()) {
|
||||
let stacking_order = cx.stacking_order().clone();
|
||||
let cursor_style = match axis {
|
||||
Axis::Vertical => CursorStyle::ResizeUpDown,
|
||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
||||
};
|
||||
cx.set_cursor_style(cursor_style, stacking_order);
|
||||
let handle_hitbox = cx.insert_hitbox(handle_bounds, false);
|
||||
cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
|
||||
|
||||
cx.on_mouse_event({
|
||||
let dragged_handle = dragged_handle.clone();
|
||||
let flexes = flexes.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |e: &MouseDownEvent, phase, cx| {
|
||||
if phase.bubble() && handle_hitbox.id.is_hovered(cx) {
|
||||
dragged_handle.replace(Some(ix));
|
||||
if e.click_count >= 2 {
|
||||
let mut borrow = flexes.lock();
|
||||
*borrow = vec![1.; borrow.len()];
|
||||
workspace
|
||||
.update(cx, |this, cx| this.schedule_serialize(cx))
|
||||
.log_err();
|
||||
|
||||
cx.refresh();
|
||||
}
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
cx.add_opaque_layer(handle_bounds);
|
||||
cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
|
||||
|
||||
cx.on_mouse_event({
|
||||
let dragged_handle = dragged_handle.clone();
|
||||
let flexes = flexes.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |e: &MouseDownEvent, phase, cx| {
|
||||
if phase.bubble() && handle_bounds.contains(&e.position) {
|
||||
dragged_handle.replace(Some(ix));
|
||||
if e.click_count >= 2 {
|
||||
let mut borrow = flexes.lock();
|
||||
*borrow = vec![1.; borrow.len()];
|
||||
workspace
|
||||
.update(cx, |this, cx| this.schedule_serialize(cx))
|
||||
.log_err();
|
||||
|
||||
cx.refresh();
|
||||
}
|
||||
cx.stop_propagation();
|
||||
}
|
||||
});
|
||||
cx.on_mouse_event({
|
||||
let workspace = workspace.clone();
|
||||
move |e: &MouseMoveEvent, phase, cx| {
|
||||
let dragged_handle = dragged_handle.borrow();
|
||||
if phase.bubble() && *dragged_handle == Some(ix) && handle_hitbox.is_hovered(cx)
|
||||
{
|
||||
Self::compute_resize(
|
||||
&flexes,
|
||||
e,
|
||||
ix,
|
||||
axis,
|
||||
pane_bounds.origin,
|
||||
axis_bounds.size,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
});
|
||||
cx.on_mouse_event({
|
||||
let workspace = workspace.clone();
|
||||
move |e: &MouseMoveEvent, phase, cx| {
|
||||
let dragged_handle = dragged_handle.borrow();
|
||||
|
||||
if phase.bubble() && *dragged_handle == Some(ix) {
|
||||
Self::compute_resize(
|
||||
&flexes,
|
||||
e,
|
||||
ix,
|
||||
axis,
|
||||
pane_bounds.origin,
|
||||
axis_bounds.size,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -812,40 +800,54 @@ mod element {
|
|||
impl IntoElement for PaneAxisElement {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ui::prelude::ElementId> {
|
||||
Some(self.basis.into())
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for PaneAxisElement {
|
||||
type State = Rc<RefCell<Option<usize>>>;
|
||||
type BeforeLayout = ();
|
||||
type AfterLayout = ();
|
||||
|
||||
fn request_layout(
|
||||
fn before_layout(
|
||||
&mut self,
|
||||
state: Option<Self::State>,
|
||||
cx: &mut ui::prelude::ElementContext,
|
||||
) -> (gpui::LayoutId, Self::State) {
|
||||
) -> (gpui::LayoutId, Self::BeforeLayout) {
|
||||
let mut style = Style::default();
|
||||
style.flex_grow = 1.;
|
||||
style.flex_shrink = 1.;
|
||||
style.flex_basis = relative(0.).into();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = relative(1.).into();
|
||||
let layout_id = cx.request_layout(&style, None);
|
||||
let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
|
||||
(layout_id, dragged_pane)
|
||||
(cx.request_layout(&style, None), ())
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
// we paint children below that need to be commited
|
||||
todo!("implement commit bounds on pane axis element")
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: gpui::Bounds<ui::prelude::Pixels>,
|
||||
state: &mut Self::State,
|
||||
_: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
cx: &mut ui::prelude::ElementContext,
|
||||
) {
|
||||
let state = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
|
||||
Some(self.basis.into()),
|
||||
|state, _cx| {
|
||||
let state = state
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| Rc::new(RefCell::new(None)));
|
||||
(state.clone(), Some(state))
|
||||
},
|
||||
);
|
||||
let flexes = self.flexes.lock().clone();
|
||||
let len = self.children.len();
|
||||
debug_assert!(flexes.len() == len);
|
||||
|
@ -891,40 +893,34 @@ mod element {
|
|||
size: child_size,
|
||||
};
|
||||
bounding_boxes.push(Some(child_bounds));
|
||||
cx.with_z_index(0, |cx| {
|
||||
child.draw(origin, child_size.into(), cx);
|
||||
});
|
||||
child.paint(cx);
|
||||
|
||||
if active_pane_magnification.is_none() {
|
||||
cx.with_z_index(1, |cx| {
|
||||
if ix < len - 1 {
|
||||
Self::push_handle(
|
||||
self.flexes.clone(),
|
||||
state.clone(),
|
||||
self.axis,
|
||||
ix,
|
||||
child_bounds,
|
||||
bounds,
|
||||
self.workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
if ix < len - 1 {
|
||||
Self::push_handle(
|
||||
self.flexes.clone(),
|
||||
state.clone(),
|
||||
self.axis,
|
||||
ix,
|
||||
child_bounds,
|
||||
bounds,
|
||||
self.workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
|
||||
}
|
||||
|
||||
cx.with_z_index(1, |cx| {
|
||||
cx.on_mouse_event({
|
||||
let state = state.clone();
|
||||
move |_: &MouseUpEvent, phase, _cx| {
|
||||
if phase.bubble() {
|
||||
state.replace(None);
|
||||
}
|
||||
cx.on_mouse_event({
|
||||
let state = state.clone();
|
||||
move |_: &MouseUpEvent, phase, _cx| {
|
||||
if phase.bubble() {
|
||||
state.replace(None);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2744,13 +2744,13 @@ impl Workspace {
|
|||
}
|
||||
|
||||
fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
|
||||
// TODO! Put these in the right spo
|
||||
if self.notifications.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
div()
|
||||
.absolute()
|
||||
.z_index(100)
|
||||
.right_3()
|
||||
.bottom_3()
|
||||
.w_112()
|
||||
|
@ -3825,18 +3825,15 @@ impl Render for Workspace {
|
|||
.border_t()
|
||||
.border_b()
|
||||
.border_color(colors.border)
|
||||
.child(
|
||||
canvas({
|
||||
let this = cx.view().clone();
|
||||
move |bounds, cx| {
|
||||
this.update(cx, |this, _cx| {
|
||||
this.bounds = *bounds;
|
||||
})
|
||||
}
|
||||
})
|
||||
.child({
|
||||
let this = cx.view().clone();
|
||||
canvas(
|
||||
move |bounds, cx| this.update(cx, |this, _cx| this.bounds = bounds),
|
||||
|_, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full(),
|
||||
)
|
||||
.size_full()
|
||||
})
|
||||
.on_drag_move(
|
||||
cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
|
||||
match e.drag(cx).0 {
|
||||
|
@ -3914,7 +3911,6 @@ impl Render for Workspace {
|
|||
.children(self.zoomed.as_ref().and_then(|view| {
|
||||
let zoomed_view = view.upgrade()?;
|
||||
let div = div()
|
||||
.z_index(1)
|
||||
.absolute()
|
||||
.overflow_hidden()
|
||||
.border_color(colors.border)
|
||||
|
@ -4606,14 +4602,12 @@ pub fn titlebar_height(cx: &mut WindowContext) -> Pixels {
|
|||
|
||||
struct DisconnectedOverlay;
|
||||
|
||||
// TODO! Actually render this on top
|
||||
impl Element for DisconnectedOverlay {
|
||||
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 background = cx.theme().colors().elevated_surface_background;
|
||||
background.fade_out(0.2);
|
||||
let mut overlay = div()
|
||||
|
@ -4631,29 +4625,33 @@ impl Element for DisconnectedOverlay {
|
|||
"Your connection to the remote project has been lost.",
|
||||
))
|
||||
.into_any();
|
||||
(overlay.request_layout(cx), overlay)
|
||||
(overlay.before_layout(cx), overlay)
|
||||
}
|
||||
|
||||
fn after_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
overlay: &mut Self::BeforeLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.insert_hitbox(bounds, true);
|
||||
overlay.after_layout(cx);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
overlay: &mut Self::State,
|
||||
_: Bounds<Pixels>,
|
||||
overlay: &mut Self::BeforeLayout,
|
||||
_: &mut Self::AfterLayout,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.with_z_index(u16::MAX, |cx| {
|
||||
cx.add_opaque_layer(bounds);
|
||||
overlay.paint(cx);
|
||||
})
|
||||
overlay.paint(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for DisconnectedOverlay {
|
||||
type Element = Self;
|
||||
|
||||
fn element_id(&self) -> Option<ui::prelude::ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
|
|
|
@ -268,7 +268,7 @@ fn main() {
|
|||
initialize_workspace(app_state.clone(), cx);
|
||||
|
||||
if stdout_is_a_pty() {
|
||||
//todo!(linux): unblock this
|
||||
// todo(linux): unblock this
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
upload_panics_and_crashes(http.clone(), cx);
|
||||
cx.activate(true);
|
||||
|
@ -997,7 +997,7 @@ fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
|
|||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
//todo!(linux): Port fsevents to linux
|
||||
// todo(linux): Port fsevents to linux
|
||||
/// Spawns a background task to watch the themes directory for changes.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue