Merge branch 'gpui2' into marshall/gpui2-playground

Marshall Bowers created

Change summary

Cargo.lock                                                   |  377 
Cargo.toml                                                   |    3 
crates/gpui/src/app.rs                                       |    2 
crates/gpui/src/geometry.rs                                  |   74 
crates/gpui/src/text_layout.rs                               |   34 
crates/gpui2/src/elements/text.rs                            |   14 
crates/gpui2/src/gpui2.rs                                    |    1 
crates/gpui2/src/style.rs                                    |    4 
crates/gpui2/src/view_context.rs                             |   37 
crates/gpui2/src/view_handle.rs                              |   60 
crates/gpui3/Cargo.toml                                      |   87 
crates/gpui3/build.rs                                        |  115 
crates/gpui3/src/app.rs                                      |  343 
crates/gpui3/src/app/async_context.rs                        |   52 
crates/gpui3/src/app/entity_map.rs                           |  175 
crates/gpui3/src/app/model_context.rs                        |   87 
crates/gpui3/src/arc_cow.rs                                  |    0 
crates/gpui3/src/color.rs                                    |  238 
crates/gpui3/src/element.rs                                  |  166 
crates/gpui3/src/elements.rs                                 |   11 
crates/gpui3/src/elements/div.rs                             |  297 
crates/gpui3/src/elements/hoverable.rs                       |  105 
crates/gpui3/src/elements/img.rs                             |  103 
crates/gpui3/src/elements/pressable.rs                       |  108 
crates/gpui3/src/elements/stateless.rs                       |   30 
crates/gpui3/src/elements/svg.rs                             |   82 
crates/gpui3/src/elements/text.rs                            |  121 
crates/gpui3/src/executor.rs                                 | 1095 ++
crates/gpui3/src/geometry.rs                                 |  723 +
crates/gpui3/src/gpui3.rs                                    |  207 
crates/gpui3/src/platform.rs                                 |  399 
crates/gpui3/src/platform/events.rs                          |  204 
crates/gpui3/src/platform/keystroke.rs                       |  121 
crates/gpui3/src/platform/mac.rs                             |  151 
crates/gpui3/src/platform/mac/dispatch.h                     |    1 
crates/gpui3/src/platform/mac/dispatcher.rs                  |   42 
crates/gpui3/src/platform/mac/events.rs                      |  356 
crates/gpui3/src/platform/mac/metal_renderer.rs              |  294 
crates/gpui3/src/platform/mac/open_type.rs                   |  394 
crates/gpui3/src/platform/mac/platform.rs                    | 1123 ++
crates/gpui3/src/platform/mac/screen.rs                      |  156 
crates/gpui3/src/platform/mac/shaders.metal                  |  180 
crates/gpui3/src/platform/mac/text_system.rs                 |  754 +
crates/gpui3/src/platform/mac/window.rs                      | 1595 +++
crates/gpui3/src/platform/mac/window_appearence.rs           |   35 
crates/gpui3/src/platform/test.rs                            |  172 
crates/gpui3/src/scene.rs                                    |  121 
crates/gpui3/src/style.rs                                    |  338 
crates/gpui3/src/style_helpers.rs                            |  308 
crates/gpui3/src/styled.rs                                   |   26 
crates/gpui3/src/taffy.rs                                    |  383 
crates/gpui3/src/text_system.rs                              |  477 
crates/gpui3/src/text_system/font_features.rs                |  162 
crates/gpui3/src/text_system/line_wrapper.rs                 |  329 
crates/gpui3/src/text_system/text_layout_cache.rs            |  478 
crates/gpui3/src/util.rs                                     |   49 
crates/gpui3/src/view.rs                                     |  143 
crates/gpui3/src/window.rs                                   |  403 
crates/gpui3_macros/Cargo.toml                               |   14 
crates/gpui3_macros/src/derive_element.rs                    |   50 
crates/gpui3_macros/src/gpui3_macros.rs                      |   14 
crates/gpui3_macros/src/style_helpers.rs                     |  326 
crates/refineable/derive_refineable/src/derive_refineable.rs |   29 
crates/refineable/src/refineable.rs                          |    6 
crates/storybook/Cargo.toml                                  |    5 
crates/storybook/build.rs                                    |    5 
crates/storybook2/Cargo.lock                                 | 2919 ++++++
crates/storybook2/Cargo.toml                                 |   25 
crates/storybook2/build.rs                                   |    5 
crates/storybook2/docs/thoughts.md                           |   72 
crates/storybook2/src/collab_panel.rs                        |  176 
crates/storybook2/src/components.rs                          |   97 
crates/storybook2/src/storybook2.rs                          |   75 
crates/storybook2/src/theme.rs                               |  193 
crates/storybook2/src/themes.rs                              |    3 
crates/storybook2/src/themes/rose_pine_dawn.rs               |  845 +
crates/storybook2/src/workspace.rs                           |  473 
crates/util/src/arc_cow.rs                                   |   14 
crates/zed/Cargo.toml                                        |    1 
79 files changed, 19,100 insertions(+), 192 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -108,7 +108,7 @@ dependencies = [
  "rusqlite",
  "serde",
  "serde_json",
- "tiktoken-rs 0.5.4",
+ "tiktoken-rs 0.5.1",
  "util",
 ]
 
@@ -119,7 +119,7 @@ source = "git+https://github.com/zed-industries/alacritty?rev=33306142195b354ef3
 dependencies = [
  "log",
  "serde",
- "toml 0.7.8",
+ "toml 0.7.6",
 ]
 
 [[package]]
@@ -129,7 +129,7 @@ source = "git+https://github.com/zed-industries/alacritty?rev=33306142195b354ef3
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -148,14 +148,14 @@ dependencies = [
  "mio-anonymous-pipes",
  "mio-extras",
  "miow 0.3.7",
- "nix 0.26.4",
+ "nix 0.26.2",
  "parking_lot 0.12.1",
  "regex-automata 0.1.10",
  "serde",
  "serde_yaml",
  "signal-hook",
  "signal-hook-mio",
- "toml 0.7.8",
+ "toml 0.7.6",
  "unicode-width",
  "vte",
  "windows-sys",
@@ -218,9 +218,9 @@ dependencies = [
 
 [[package]]
 name = "anstream"
-version = "0.5.0"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
+checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -232,9 +232,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.3"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
+checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
 
 [[package]]
 name = "anstyle-parse"
@@ -256,9 +256,9 @@ dependencies = [
 
 [[package]]
 name = "anstyle-wincon"
-version = "2.1.0"
+version = "3.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
+checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
 dependencies = [
  "anstyle",
  "windows-sys",
@@ -363,7 +363,7 @@ dependencies = [
  "futures-core",
  "futures-io",
  "once_cell",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "tokio",
 ]
 
@@ -377,7 +377,7 @@ dependencies = [
  "futures-core",
  "futures-io",
  "memchr",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
 ]
 
 [[package]]
@@ -502,13 +502,13 @@ dependencies = [
 
 [[package]]
 name = "async-recursion"
-version = "1.0.5"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
+checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -531,7 +531,7 @@ dependencies = [
  "log",
  "memchr",
  "once_cell",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "pin-utils",
  "slab",
  "wasm-bindgen-futures",
@@ -545,7 +545,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
 dependencies = [
  "async-stream-impl",
  "futures-core",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
 ]
 
 [[package]]
@@ -556,7 +556,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -599,7 +599,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -612,7 +612,7 @@ dependencies = [
  "futures-io",
  "futures-util",
  "log",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "tungstenite 0.16.0",
 ]
 
@@ -701,7 +701,7 @@ dependencies = [
  "axum-core",
  "base64 0.13.1",
  "bitflags 1.3.2",
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "futures-util",
  "headers",
  "http",
@@ -712,7 +712,7 @@ dependencies = [
  "memchr",
  "mime",
  "percent-encoding",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "serde",
  "serde_json",
  "serde_urlencoded",
@@ -733,7 +733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc"
 dependencies = [
  "async-trait",
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "futures-util",
  "http",
  "http-body",
@@ -749,11 +749,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "69034b3b0fd97923eee2ce8a47540edb21e07f48f87f67d44bb4271cec622bdb"
 dependencies = [
  "axum",
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "futures-util",
  "http",
  "mime",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "serde",
  "serde_json",
  "tokio",
@@ -774,10 +774,21 @@ dependencies = [
  "cfg-if 1.0.0",
  "libc",
  "miniz_oxide 0.7.1",
- "object 0.32.1",
+ "object 0.32.0",
  "rustc-demangle",
 ]
 
+[[package]]
+name = "backtrace-on-stack-overflow"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd2d70527f3737a1ad17355e260706c1badebabd1fa06a7a053407380df841b"
+dependencies = [
+ "backtrace",
+ "libc",
+ "nix 0.23.2",
+]
+
 [[package]]
 name = "bae"
 version = "0.1.7"
@@ -799,9 +810,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64"
-version = "0.21.4"
+version = "0.21.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
+checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
 
 [[package]]
 name = "base64ct"
@@ -857,7 +868,7 @@ dependencies = [
  "regex",
  "rustc-hash",
  "shlex",
- "syn 2.0.37",
+ "syn 2.0.29",
  "which",
 ]
 
@@ -1017,20 +1028,20 @@ dependencies = [
 
 [[package]]
 name = "bstr"
-version = "1.6.2"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
+checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
 dependencies = [
  "memchr",
- "regex-automata 0.3.8",
+ "regex-automata 0.3.6",
  "serde",
 ]
 
 [[package]]
 name = "bumpalo"
-version = "3.14.0"
+version = "3.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
+checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
 
 [[package]]
 name = "bytecheck"
@@ -1059,6 +1070,20 @@ name = "bytemuck"
 version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.29",
+]
 
 [[package]]
 name = "byteorder"
@@ -1078,9 +1103,9 @@ dependencies = [
 
 [[package]]
 name = "bytes"
-version = "1.5.0"
+version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
 
 [[package]]
 name = "call"
@@ -1181,6 +1206,25 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
 
+[[package]]
+name = "cbindgen"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49"
+dependencies = [
+ "clap 3.2.25",
+ "heck 0.4.1",
+ "indexmap 1.9.3",
+ "log",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "syn 1.0.109",
+ "tempfile",
+ "toml 0.5.11",
+]
+
 [[package]]
 name = "cc"
 version = "1.0.83"
@@ -1247,7 +1291,7 @@ dependencies = [
  "tempfile",
  "text",
  "thiserror",
- "time",
+ "time 0.3.27",
  "tiny_http",
  "url",
  "util",
@@ -1256,17 +1300,18 @@ dependencies = [
 
 [[package]]
 name = "chrono"
-version = "0.4.31"
+version = "0.4.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
+checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
  "js-sys",
  "num-traits",
  "serde",
+ "time 0.1.45",
  "wasm-bindgen",
- "windows-targets 0.48.5",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -1314,9 +1359,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.4"
+version = "4.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
+checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
 dependencies = [
  "clap_builder",
  "clap_derive 4.4.2",
@@ -1324,13 +1369,13 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.4.4"
+version = "4.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
+checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
 dependencies = [
  "anstream",
  "anstyle",
- "clap_lex 0.5.1",
+ "clap_lex 0.5.0",
  "strsim",
 ]
 
@@ -1356,7 +1401,7 @@ dependencies = [
  "heck 0.4.1",
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -1370,9 +1415,9 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.5.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
+checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
 
 [[package]]
 name = "cli"
@@ -1418,7 +1463,7 @@ dependencies = [
  "tempfile",
  "text",
  "thiserror",
- "time",
+ "time 0.3.27",
  "tiny_http",
  "url",
  "util",
@@ -1527,7 +1572,7 @@ dependencies = [
  "sqlx",
  "text",
  "theme",
- "time",
+ "time 0.3.27",
  "tokio",
  "tokio-tungstenite",
  "toml 0.5.11",
@@ -1574,7 +1619,7 @@ dependencies = [
  "settings",
  "theme",
  "theme_selector",
- "time",
+ "time 0.3.27",
  "util",
  "vcs_menu",
  "workspace",
@@ -1606,7 +1651,7 @@ version = "4.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
 dependencies = [
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "memchr",
 ]
 
@@ -1775,9 +1820,9 @@ dependencies = [
 
 [[package]]
 name = "core-services"
-version = "0.2.1"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92567e81db522550ebaf742c5d875624ec7820c2c7ee5f8c60e4ce7c2ae3c0fd"
+checksum = "51b344b958cae90858bf6086f49599ecc5ec8698eacad0ea155509ba11fab347"
 dependencies = [
  "core-foundation",
 ]
@@ -2077,9 +2122,9 @@ dependencies = [
 
 [[package]]
 name = "curl-sys"
-version = "0.4.66+curl-8.3.0"
+version = "0.4.65+curl-8.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70c44a72e830f0e40ad90dda8a6ab6ed6314d39776599a58a2e5e37fbc6db5b9"
+checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986"
 dependencies = [
  "cc",
  "libc",
@@ -2087,14 +2132,14 @@ dependencies = [
  "openssl-sys",
  "pkg-config",
  "vcpkg",
- "windows-sys",
+ "winapi 0.3.9",
 ]
 
 [[package]]
 name = "dashmap"
-version = "5.5.3"
+version = "5.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28"
 dependencies = [
  "cfg-if 1.0.0",
  "hashbrown 0.14.0",
@@ -2168,7 +2213,7 @@ dependencies = [
  "convert_case 0.4.0",
  "proc-macro2",
  "quote",
- "rustc_version",
+ "rustc_version 0.4.0",
  "syn 1.0.109",
 ]
 
@@ -2345,9 +2390,9 @@ dependencies = [
 
 [[package]]
 name = "dyn-clone"
-version = "1.0.14"
+version = "1.0.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd"
+checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555"
 
 [[package]]
 name = "editor"
@@ -2460,9 +2505,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "erased-serde"
-version = "0.3.31"
+version = "0.3.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c"
+checksum = "fc978899517288e3ebbd1a3bfc1d9537dbb87eeab149e53ea490e63bcdff561a"
 dependencies = [
  "serde",
 ]
@@ -2480,9 +2525,9 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.3.3"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
+checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
 dependencies = [
  "errno-dragonfly",
  "libc",
@@ -2644,12 +2689,6 @@ dependencies = [
  "windows-sys",
 ]
 
-[[package]]
-name = "finl_unicode"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
-
 [[package]]
 name = "fixedbitset"
 version = "0.4.2"
@@ -2801,7 +2840,7 @@ dependencies = [
  "sum_tree",
  "tempfile",
  "text",
- "time",
+ "time 0.3.27",
  "util",
 ]
 
@@ -2939,7 +2978,7 @@ dependencies = [
  "futures-io",
  "memchr",
  "parking",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "waker-fn",
 ]
 
@@ -2951,7 +2990,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -2980,7 +3019,7 @@ dependencies = [
  "futures-sink",
  "futures-task",
  "memchr",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "pin-utils",
  "slab",
  "tokio-io",
@@ -3195,7 +3234,7 @@ dependencies = [
  "sum_tree",
  "taffy",
  "thiserror",
- "time",
+ "time 0.3.27",
  "tiny-skia",
  "usvg",
  "util",
@@ -3233,6 +3272,81 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "gpui3"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-task",
+ "backtrace",
+ "bindgen 0.65.1",
+ "bitflags 2.4.0",
+ "block",
+ "bytemuck",
+ "cbindgen",
+ "cocoa",
+ "collections",
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "ctor",
+ "derive_more",
+ "dhat",
+ "env_logger 0.9.3",
+ "etagere",
+ "font-kit",
+ "foreign-types",
+ "futures 0.3.28",
+ "gpui3_macros",
+ "gpui_macros",
+ "image",
+ "itertools 0.10.5",
+ "lazy_static",
+ "log",
+ "media",
+ "metal",
+ "num_cpus",
+ "objc",
+ "ordered-float",
+ "parking",
+ "parking_lot 0.11.2",
+ "pathfinder_geometry",
+ "plane-split",
+ "png",
+ "postage",
+ "rand 0.8.5",
+ "refineable",
+ "resvg",
+ "schemars",
+ "seahash",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "simplelog",
+ "slotmap",
+ "smallvec",
+ "smol",
+ "sqlez",
+ "sum_tree",
+ "taffy",
+ "thiserror",
+ "time 0.3.27",
+ "tiny-skia",
+ "usvg",
+ "util",
+ "uuid 1.4.1",
+ "waker-fn",
+]
+
+[[package]]
+name = "gpui3_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "gpui_macros"
 version = "0.1.0"
@@ -3255,7 +3369,7 @@ version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
 dependencies = [
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "fnv",
  "futures-core",
  "futures-sink",
@@ -3264,7 +3378,7 @@ dependencies = [
  "indexmap 1.9.3",
  "slab",
  "tokio",
- "tokio-util 0.7.9",
+ "tokio-util 0.7.8",
  "tracing",
 ]
 
@@ -3316,21 +3430,22 @@ dependencies = [
 
 [[package]]
 name = "hashlink"
-version = "0.8.4"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
+checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f"
 dependencies = [
  "hashbrown 0.14.0",
 ]
 
 [[package]]
 name = "headers"
-version = "0.3.9"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
+checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
 dependencies = [
- "base64 0.21.4",
- "bytes 1.5.0",
+ "base64 0.13.1",
+ "bitflags 1.3.2",
+ "bytes 1.4.0",
  "headers-core",
  "http",
  "httpdate",
@@ -3385,9 +3500,9 @@ dependencies = [
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.3"
+version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
 
 [[package]]
 name = "hex"
@@ -3444,7 +3559,7 @@ version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
 dependencies = [
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "fnv",
  "itoa",
 ]
@@ -3455,9 +3570,9 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
 dependencies = [
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "http",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
 ]
 
 [[package]]
@@ -3480,9 +3595,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "human_bytes"
-version = "0.4.3"
+version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e"
+checksum = "27e2b089f28ad15597b48d8c0a8fe94eeb1c1cb26ca99b6f66ac9582ae10c5e6"
 
 [[package]]
 name = "humantime"
@@ -3496,7 +3611,7 @@ version = "0.14.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
 dependencies = [
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "futures-channel",
  "futures-core",
  "futures-util",
@@ -3506,7 +3621,7 @@ dependencies = [
  "httparse",
  "httpdate",
  "itoa",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "socket2 0.4.9",
  "tokio",
  "tower-service",
@@ -3521,7 +3636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
 dependencies = [
  "hyper",
- "pin-project-lite 0.2.13",
+ "pin-project-lite 0.2.12",
  "tokio",
  "tokio-io-timeout",
 ]
@@ -3532,7 +3647,7 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
 dependencies = [
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "hyper",
  "native-tls",
  "tokio",
@@ -3681,7 +3796,7 @@ version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
 dependencies = [
- "hermit-abi 0.3.3",
+ "hermit-abi 0.3.2",
  "libc",
  "windows-sys",
 ]
@@ -3738,8 +3853,8 @@ version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
 dependencies = [
- "hermit-abi 0.3.3",
- "rustix 0.38.14",
+ "hermit-abi 0.3.2",
+ "rustix 0.38.8",
  "windows-sys",
 ]
 
@@ -4042,9 +4157,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
 
 [[package]]
 name = "libc"
-version = "0.2.148"
+version = "0.2.147"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
 
 [[package]]
 name = "libgit2-sys"
@@ -4136,9 +4251,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.7"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
 
 [[package]]
 name = "lipsum"
@@ -4159,7 +4274,7 @@ dependencies = [
  "async-trait",
  "block",
  "byteorder",
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "cocoa",
  "collections",
  "core-foundation",
@@ -4307,9 +4422,9 @@ checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
 
 [[package]]
 name = "matrixmultiply"
-version = "0.3.8"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2"
+checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77"
 dependencies = [
  "autocfg",
  "rawpointer",
@@ -4337,7 +4452,7 @@ dependencies = [
  "anyhow",
  "bindgen 0.65.1",
  "block",
- "bytes 1.5.0",
+ "bytes 1.4.0",
  "core-foundation",
  "foreign-types",
  "metal",
@@ -4346,9 +4461,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.6.3"
+version = "2.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
+checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c"
 
 [[package]]
 name = "memfd"
@@ -4636,6 +4751,19 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "nix"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
+dependencies = [
+ "bitflags 1.3.2",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "memoffset 0.6.5",
+]
+
 [[package]]
 name = "nix"
 version = "0.24.3"
@@ -4649,13 +4777,14 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.26.4"
+version = "0.26.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
 dependencies = [
  "bitflags 1.3.2",
  "cfg-if 1.0.0",
  "libc",
+ "static_assertions",
 ]
 
 [[package]]
@@ -4858,7 +4987,7 @@ version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
 dependencies = [
- "hermit-abi 0.3.3",
+ "hermit-abi 0.3.2",
  "libc",
 ]
 
@@ -4885,8 +5014,8 @@ dependencies = [
 
 [[package]]
 name = "nvim-rs"
-version = "0.6.0-pre"
-source = "git+https://github.com/KillTheMule/nvim-rs?branch=master#0d2b1c884f3c39a76b5b7aac0b429f4624843954"
+version = "0.5.0"
+source = "git+https://github.com/KillTheMule/nvim-rs?branch=master#d701c2790dcb2579f8f4d7003ba30e2100a7d25b"
 dependencies = [
  "async-trait",
  "futures 0.3.28",
@@ -4895,7 +5024,7 @@ dependencies = [
  "rmp",
  "rmpv",
  "tokio",
- "tokio-util 0.7.9",
+ "tokio-util 0.7.8",
 ]
 
 [[package]]
@@ -4931,9 +5060,9 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.32.1"
+version = "0.32.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
 dependencies = [
  "memchr",
 ]
@@ -4975,11 +5104,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
 [[package]]
 name = "openssl"
-version = "0.10.57"
+version = "0.10.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
+checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e"
 dependencies = [
- "bitflags 2.4.0",
+ "bitflags 1.3.2",
  "cfg-if 1.0.0",
  "foreign-types",
  "libc",
@@ -4996,7 +5125,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.37",
+ "syn 2.0.29",
 ]
 
 [[package]]
@@ -5007,9 +5136,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
 
 [[package]]
 name = "openssl-sys"
-version = "0.9.93"
+version = "0.9.91"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
+checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac"
 dependencies = [
  "cc",
  "libc",

Cargo.toml 🔗

@@ -36,6 +36,8 @@ members = [
     "crates/gpui_macros",
     "crates/gpui2",
     "crates/gpui2_macros",
+    "crates/gpui3",
+    "crates/gpui3_macros",
     "crates/install_cli",
     "crates/journal",
     "crates/language",
@@ -65,6 +67,7 @@ members = [
     "crates/sqlez_macros",
     "crates/feature_flags",
     "crates/storybook",
+    "crates/storybook2",
     "crates/sum_tree",
     "crates/terminal",
     "crates/text",

crates/gpui/src/app.rs 🔗

@@ -3607,7 +3607,7 @@ impl<V> BorrowWindowContext for EventContext<'_, '_, '_, V> {
     }
 }
 
-pub(crate) enum Reference<'a, T> {
+pub enum Reference<'a, T> {
     Immutable(&'a T),
     Mutable(&'a mut T),
 }

crates/gpui/src/geometry.rs 🔗

@@ -1,12 +1,12 @@
+use std::fmt::Debug;
+
 use super::scene::{Path, PathVertex};
 use crate::{color::Color, json::ToJson};
-use derive_more::Neg;
 pub use pathfinder_geometry::*;
 use rect::RectF;
 use refineable::Refineable;
 use serde::{Deserialize, Deserializer};
 use serde_json::json;
-use std::fmt::Debug;
 use vector::{vec2f, Vector2F};
 
 pub struct PathBuilder {
@@ -167,6 +167,15 @@ pub struct Size<T: Clone + Default + Debug> {
     pub height: T,
 }
 
+impl Size<Length> {
+    pub fn full() -> Self {
+        Self {
+            width: relative(1.),
+            height: relative(1.),
+        }
+    }
+}
+
 impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
 where
     S: Into<T>,
@@ -194,8 +203,8 @@ where
 impl Size<DefiniteLength> {
     pub fn zero() -> Self {
         Self {
-            width: pixels(0.).into(),
-            height: pixels(0.).into(),
+            width: px(0.),
+            height: px(0.),
         }
     }
 
@@ -235,17 +244,6 @@ pub struct Edges<T: Clone + Default + Debug> {
     pub left: T,
 }
 
-impl<T: Clone + Default + Debug> Edges<T> {
-    pub fn uniform(value: T) -> Self {
-        Self {
-            top: value.clone(),
-            right: value.clone(),
-            bottom: value.clone(),
-            left: value.clone(),
-        }
-    }
-}
-
 impl Edges<Length> {
     pub fn auto() -> Self {
         Self {
@@ -258,10 +256,10 @@ impl Edges<Length> {
 
     pub fn zero() -> Self {
         Self {
-            top: pixels(0.).into(),
-            right: pixels(0.).into(),
-            bottom: pixels(0.).into(),
-            left: pixels(0.).into(),
+            top: px(0.),
+            right: px(0.),
+            bottom: px(0.),
+            left: px(0.),
         }
     }
 
@@ -281,10 +279,10 @@ impl Edges<Length> {
 impl Edges<DefiniteLength> {
     pub fn zero() -> Self {
         Self {
-            top: pixels(0.).into(),
-            right: pixels(0.).into(),
-            bottom: pixels(0.).into(),
-            left: pixels(0.).into(),
+            top: px(0.),
+            right: px(0.),
+            bottom: px(0.),
+            left: px(0.),
         }
     }
 
@@ -301,10 +299,10 @@ impl Edges<DefiniteLength> {
 impl Edges<AbsoluteLength> {
     pub fn zero() -> Self {
         Self {
-            top: pixels(0.),
-            right: pixels(0.),
-            bottom: pixels(0.),
-            left: pixels(0.),
+            top: px(0.),
+            right: px(0.),
+            bottom: px(0.),
+            left: px(0.),
         }
     }
 
@@ -333,7 +331,7 @@ impl Edges<f32> {
     }
 }
 
-#[derive(Clone, Copy, Neg)]
+#[derive(Clone, Copy)]
 pub enum AbsoluteLength {
     Pixels(f32),
     Rems(f32),
@@ -371,7 +369,7 @@ impl Default for AbsoluteLength {
 }
 
 /// A non-auto length that can be defined in pixels, rems, or percent of parent.
-#[derive(Clone, Copy, Neg)]
+#[derive(Clone, Copy)]
 pub enum DefiniteLength {
     Absolute(AbsoluteLength),
     Relative(f32), // 0. to 1.
@@ -415,7 +413,7 @@ impl Default for DefiniteLength {
 }
 
 /// A length that can be defined in pixels, rems, percent of parent, or auto.
-#[derive(Clone, Copy, Neg)]
+#[derive(Clone, Copy)]
 pub enum Length {
     Definite(DefiniteLength),
     Auto,
@@ -430,16 +428,20 @@ impl std::fmt::Debug for Length {
     }
 }
 
-pub fn relative(fraction: f32) -> DefiniteLength {
-    DefiniteLength::Relative(fraction)
+pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
+    DefiniteLength::Relative(fraction).into()
+}
+
+pub fn rems<T: From<AbsoluteLength>>(rems: f32) -> T {
+    AbsoluteLength::Rems(rems).into()
 }
 
-pub fn rems(rems: f32) -> AbsoluteLength {
-    AbsoluteLength::Rems(rems)
+pub fn px<T: From<AbsoluteLength>>(pixels: f32) -> T {
+    AbsoluteLength::Pixels(pixels).into()
 }
 
-pub fn pixels(pixels: f32) -> AbsoluteLength {
-    AbsoluteLength::Pixels(pixels)
+pub fn pixels<T: From<AbsoluteLength>>(pixels: f32) -> T {
+    AbsoluteLength::Pixels(pixels).into()
 }
 
 pub fn auto() -> Length {

crates/gpui/src/text_layout.rs 🔗

@@ -22,8 +22,8 @@ use std::{
 };
 
 pub struct TextLayoutCache {
-    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    prev_frame: Mutex<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
+    curr_frame: RwLock<HashMap<OwnedCacheKey, Arc<LineLayout>>>,
     fonts: Arc<dyn platform::FontSystem>,
 }
 
@@ -56,7 +56,7 @@ impl TextLayoutCache {
         font_size: f32,
         runs: &'a [(usize, RunStyle)],
     ) -> Line {
-        let key = &CacheKeyRef {
+        let key = &BorrowedCacheKey {
             text,
             font_size: OrderedFloat(font_size),
             runs,
@@ -72,7 +72,7 @@ impl TextLayoutCache {
             Line::new(layout, runs)
         } else {
             let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
-            let key = CacheKeyValue {
+            let key = OwnedCacheKey {
                 text: text.into(),
                 font_size: OrderedFloat(font_size),
                 runs: SmallVec::from(runs),
@@ -84,7 +84,7 @@ impl TextLayoutCache {
 }
 
 trait CacheKey {
-    fn key(&self) -> CacheKeyRef;
+    fn key(&self) -> BorrowedCacheKey;
 }
 
 impl<'a> PartialEq for (dyn CacheKey + 'a) {
@@ -102,15 +102,15 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
 }
 
 #[derive(Eq)]
-struct CacheKeyValue {
+struct OwnedCacheKey {
     text: String,
     font_size: OrderedFloat<f32>,
     runs: SmallVec<[(usize, RunStyle); 1]>,
 }
 
-impl CacheKey for CacheKeyValue {
-    fn key(&self) -> CacheKeyRef {
-        CacheKeyRef {
+impl CacheKey for OwnedCacheKey {
+    fn key(&self) -> BorrowedCacheKey {
+        BorrowedCacheKey {
             text: self.text.as_str(),
             font_size: self.font_size,
             runs: self.runs.as_slice(),
@@ -118,38 +118,38 @@ impl CacheKey for CacheKeyValue {
     }
 }
 
-impl PartialEq for CacheKeyValue {
+impl PartialEq for OwnedCacheKey {
     fn eq(&self, other: &Self) -> bool {
         self.key().eq(&other.key())
     }
 }
 
-impl Hash for CacheKeyValue {
+impl Hash for OwnedCacheKey {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.key().hash(state);
     }
 }
 
-impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
+impl<'a> Borrow<dyn CacheKey + 'a> for OwnedCacheKey {
     fn borrow(&self) -> &(dyn CacheKey + 'a) {
         self as &dyn CacheKey
     }
 }
 
 #[derive(Copy, Clone)]
-struct CacheKeyRef<'a> {
+struct BorrowedCacheKey<'a> {
     text: &'a str,
     font_size: OrderedFloat<f32>,
     runs: &'a [(usize, RunStyle)],
 }
 
-impl<'a> CacheKey for CacheKeyRef<'a> {
-    fn key(&self) -> CacheKeyRef {
+impl<'a> CacheKey for BorrowedCacheKey<'a> {
+    fn key(&self) -> BorrowedCacheKey {
         *self
     }
 }
 
-impl<'a> PartialEq for CacheKeyRef<'a> {
+impl<'a> PartialEq for BorrowedCacheKey<'a> {
     fn eq(&self, other: &Self) -> bool {
         self.text == other.text
             && self.font_size == other.font_size
@@ -162,7 +162,7 @@ impl<'a> PartialEq for CacheKeyRef<'a> {
     }
 }
 
-impl<'a> Hash for CacheKeyRef<'a> {
+impl<'a> Hash for BorrowedCacheKey<'a> {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.text.hash(state);
         self.font_size.hash(state);

crates/gpui2/src/elements/text.rs 🔗

@@ -12,11 +12,21 @@ use parking_lot::Mutex;
 use std::sync::Arc;
 use util::arc_cow::ArcCow;
 
-impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
+impl<V: 'static> IntoElement<V> for ArcCow<'static, str> {
     type Element = Text;
 
     fn into_element(self) -> Self::Element {
-        Text { text: self.into() }
+        Text { text: self }
+    }
+}
+
+impl<V: 'static> IntoElement<V> for &'static str {
+    type Element = Text;
+
+    fn into_element(self) -> Self::Element {
+        Text {
+            text: ArcCow::from(self),
+        }
     }
 }
 

crates/gpui2/src/gpui2.rs 🔗

@@ -6,6 +6,7 @@ pub mod interactive;
 pub mod style;
 pub mod view;
 pub mod view_context;
+pub mod view_handle;
 
 pub use color::*;
 pub use element::{AnyElement, Element, IntoElement, Layout, ParentElement};

crates/gpui2/src/style.rs 🔗

@@ -22,6 +22,8 @@ use gpui2_macros::styleable_helpers;
 use refineable::{Refineable, RefinementCascade};
 use std::sync::Arc;
 
+pub type StyleCascade = RefinementCascade<Style>;
+
 #[derive(Clone, Refineable, Debug)]
 #[refineable(debug)]
 pub struct Style {
@@ -129,7 +131,7 @@ impl Style {
             color: self.text_color.map(Into::into),
             font_family: self.font_family.clone(),
             font_size: self.font_size.map(|size| size * cx.rem_size()),
-            font_weight: self.font_weight,
+            font_weight: self.font_weight.map(Into::into),
             font_style: self.font_style,
             underline: None,
         })

crates/gpui2/src/view_context.rs 🔗

@@ -3,7 +3,10 @@ use std::{any::TypeId, rc::Rc};
 use crate::{element::LayoutId, style::Style};
 use anyhow::{anyhow, Result};
 use derive_more::{Deref, DerefMut};
-use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams};
+use gpui::{
+    geometry::Size, scene::EventHandler, AnyWindowHandle, BorrowWindowContext, EventContext,
+    Layout, MeasureParams, WindowContext,
+};
 pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext};
 
 #[derive(Deref, DerefMut)]
@@ -77,3 +80,35 @@ impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> {
             .computed_layout(layout_id)
     }
 }
+
+impl<'a, 'b, 'c, V: 'static> BorrowWindowContext for ViewContext<'a, 'b, 'c, V> {
+    type Result<T> = T;
+
+    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
+    where
+        F: FnOnce(&WindowContext) -> T,
+    {
+        self.legacy_cx.read_window(window, f)
+    }
+
+    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        self.legacy_cx.read_window_optional(window, f)
+    }
+
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
+    where
+        F: FnOnce(&mut WindowContext) -> T,
+    {
+        self.legacy_cx.update_window(window, f)
+    }
+
+    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        self.legacy_cx.update_window_optional(window, f)
+    }
+}

crates/gpui2/src/view_handle.rs 🔗

@@ -0,0 +1,60 @@
+use crate::{style::Style, Element, IntoElement, ViewContext};
+use gpui::{
+    geometry::{Point, Size},
+    taffy::style::Overflow,
+    AnyElement, View, ViewHandle,
+};
+
+impl<ParentView: 'static, ChildView: View> Element<ParentView> for ViewHandle<ChildView> {
+    type PaintState = AnyElement<ChildView>;
+
+    fn layout(
+        &mut self,
+        _: &mut ParentView,
+        cx: &mut ViewContext<ParentView>,
+    ) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        let layout_id = cx.add_layout_node(
+            Style {
+                overflow: Point {
+                    x: Overflow::Hidden,
+                    y: Overflow::Hidden,
+                },
+                size: Size::full(),
+                ..Default::default()
+            },
+            None,
+        )?;
+        let element = self.update(cx, |view, cx| view.render(cx));
+        Ok((layout_id, element))
+    }
+
+    fn paint(
+        &mut self,
+        _: &mut ParentView,
+        parent_origin: gpui::geometry::vector::Vector2F,
+        layout: &gpui::Layout,
+        element: &mut AnyElement<ChildView>,
+        cx: &mut ViewContext<ParentView>,
+    ) where
+        Self: Sized,
+    {
+        self.update(cx, |view, cx| {
+            let bounds = layout.bounds + parent_origin;
+            element.layout(gpui::SizeConstraint::strict(bounds.size()), view, cx);
+            cx.paint_layer(Some(layout.bounds), |cx| {
+                element.paint(bounds.origin(), bounds, view, cx);
+            });
+        })
+    }
+}
+
+impl<ParentView: 'static, ChildView: View> IntoElement<ParentView> for ViewHandle<ChildView> {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

crates/gpui3/Cargo.toml 🔗

@@ -0,0 +1,87 @@
+[package]
+name = "gpui3"
+version = "0.1.0"
+edition = "2021"
+authors = ["Nathan Sobo <nathan@zed.dev>"]
+description = "The next version of Zed's GPU-accelerated UI framework"
+publish = false
+
+[features]
+test = ["backtrace", "dhat", "env_logger", "collections/test-support"]
+
+[lib]
+path = "src/gpui3.rs"
+doctest = false
+
+[dependencies]
+collections = { path = "../collections" }
+gpui_macros = { path = "../gpui_macros" }
+gpui3_macros = { path = "../gpui3_macros" }
+util = { path = "../util" }
+sum_tree = { path = "../sum_tree" }
+sqlez = { path = "../sqlez" }
+async-task = "4.0.3"
+backtrace = { version = "0.3", optional = true }
+ctor.workspace = true
+derive_more.workspace = true
+dhat = { version = "0.3", optional = true }
+env_logger = { version = "0.9", optional = true }
+etagere = "0.2"
+futures.workspace = true
+image = "0.23"
+itertools = "0.10"
+lazy_static.workspace = true
+log.workspace = true
+num_cpus = "1.13"
+ordered-float.workspace = true
+parking = "2.0.0"
+parking_lot.workspace = true
+pathfinder_geometry = "0.5"
+postage.workspace = true
+rand.workspace = true
+refineable.workspace = true
+resvg = "0.14"
+seahash = "4.1"
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smallvec.workspace = true
+smol.workspace = true
+taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
+thiserror.workspace = true
+time.workspace = true
+tiny-skia = "0.5"
+usvg = { version = "0.14", features = [] }
+uuid = { version = "1.1.2", features = ["v4"] }
+waker-fn = "1.1.0"
+slotmap = "1.0.6"
+bytemuck = { version = "1.14.0", features = ["derive"] }
+schemars.workspace = true
+plane-split = "0.18.0"
+bitflags = "2.4.0"
+
+[dev-dependencies]
+backtrace = "0.3"
+collections = { path = "../collections", features = ["test-support"] }
+dhat = "0.3"
+env_logger.workspace = true
+png = "0.16"
+simplelog = "0.9"
+
+[build-dependencies]
+bindgen = "0.65.1"
+cbindgen = "0.26.0"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+media = { path = "../media" }
+anyhow.workspace = true
+block = "0.1"
+cocoa = "0.24"
+core-foundation = { version = "0.9.3", features = ["with-uuid"] }
+core-graphics = "0.22.3"
+core-text = "19.2"
+font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18" }
+foreign-types = "0.3"
+log.workspace = true
+metal = "0.21.0"
+objc = "0.2"

crates/gpui3/build.rs 🔗

@@ -0,0 +1,115 @@
+use std::{
+    env,
+    path::{Path, PathBuf},
+    process::{self, Command},
+};
+
+use cbindgen::Config;
+
+fn main() {
+    generate_dispatch_bindings();
+    let header_path = generate_shader_bindings();
+    compile_metal_shaders(&header_path);
+}
+
+fn generate_dispatch_bindings() {
+    println!("cargo:rustc-link-lib=framework=System");
+    println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
+
+    let bindings = bindgen::Builder::default()
+        .header("src/platform/mac/dispatch.h")
+        .allowlist_var("_dispatch_main_q")
+        .allowlist_function("dispatch_async_f")
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+        .layout_tests(false)
+        .generate()
+        .expect("unable to generate bindings");
+
+    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+    bindings
+        .write_to_file(out_path.join("dispatch_sys.rs"))
+        .expect("couldn't write dispatch bindings");
+}
+
+fn generate_shader_bindings() -> PathBuf {
+    let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
+    let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
+    let mut config = Config::default();
+    config.include_guard = Some("SCENE_H".into());
+    config.language = cbindgen::Language::C;
+    config.export.include.extend([
+        "Bounds".into(),
+        "Corners".into(),
+        "Edges".into(),
+        "Size".into(),
+        "Pixels".into(),
+        "PointF".into(),
+        "Hsla".into(),
+        "Quad".into(),
+        "QuadInputIndex".into(),
+        "QuadUniforms".into(),
+    ]);
+    config.no_includes = true;
+    config.enumeration.prefix_with_name = true;
+    cbindgen::Builder::new()
+        .with_src(crate_dir.join("src/scene.rs"))
+        .with_src(crate_dir.join("src/geometry.rs"))
+        .with_src(crate_dir.join("src/color.rs"))
+        .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
+        .with_config(config)
+        .generate()
+        .expect("Unable to generate bindings")
+        .write_to_file(&output_path);
+    output_path
+}
+
+fn compile_metal_shaders(header_path: &Path) {
+    let shader_path = "./src/platform/mac/shaders.metal";
+    let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
+    let metallib_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
+
+    println!("cargo:rerun-if-changed={}", header_path.display());
+    println!("cargo:rerun-if-changed={}", shader_path);
+
+    let output = Command::new("xcrun")
+        .args([
+            "-sdk",
+            "macosx",
+            "metal",
+            "-gline-tables-only",
+            "-mmacosx-version-min=10.15.7",
+            "-MO",
+            "-c",
+            shader_path,
+            "-include",
+            &header_path.to_str().unwrap(),
+            "-o",
+        ])
+        .arg(&air_output_path)
+        .output()
+        .unwrap();
+
+    if !output.status.success() {
+        eprintln!(
+            "metal shader compilation failed:\n{}",
+            String::from_utf8_lossy(&output.stderr)
+        );
+        process::exit(1);
+    }
+
+    let output = Command::new("xcrun")
+        .args(["-sdk", "macosx", "metallib"])
+        .arg(air_output_path)
+        .arg("-o")
+        .arg(metallib_output_path)
+        .output()
+        .unwrap();
+
+    if !output.status.success() {
+        eprintln!(
+            "metallib compilation failed:\n{}",
+            String::from_utf8_lossy(&output.stderr)
+        );
+        process::exit(1);
+    }
+}

crates/gpui3/src/app.rs 🔗

@@ -0,0 +1,343 @@
+mod async_context;
+mod entity_map;
+mod model_context;
+
+pub use async_context::*;
+pub use entity_map::*;
+pub use model_context::*;
+use refineable::Refineable;
+
+use crate::{
+    current_platform, run_on_main, spawn_on_main, Context, LayoutId, MainThread, MainThreadOnly,
+    Platform, PlatformDispatcher, RootView, TextStyle, TextStyleRefinement, TextSystem, Window,
+    WindowContext, WindowHandle, WindowId,
+};
+use anyhow::{anyhow, Result};
+use collections::{HashMap, VecDeque};
+use futures::Future;
+use parking_lot::Mutex;
+use slotmap::SlotMap;
+use smallvec::SmallVec;
+use std::{
+    any::{type_name, Any, TypeId},
+    mem,
+    sync::{Arc, Weak},
+};
+use util::ResultExt;
+
+#[derive(Clone)]
+pub struct App(Arc<Mutex<MainThread<AppContext>>>);
+
+impl App {
+    pub fn production() -> Self {
+        Self::new(current_platform())
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn test() -> Self {
+        Self::new(Arc::new(super::TestPlatform::new()))
+    }
+
+    fn new(platform: Arc<dyn Platform>) -> Self {
+        let dispatcher = platform.dispatcher();
+        let text_system = Arc::new(TextSystem::new(platform.text_system()));
+        let entities = EntityMap::new();
+        let unit_entity = entities.redeem(entities.reserve(), ());
+        Self(Arc::new_cyclic(|this| {
+            Mutex::new(MainThread::new(AppContext {
+                this: this.clone(),
+                platform: MainThreadOnly::new(platform, dispatcher.clone()),
+                dispatcher,
+                text_system,
+                pending_updates: 0,
+                text_style_stack: Vec::new(),
+                state_stacks_by_type: HashMap::default(),
+                unit_entity,
+                entities,
+                windows: SlotMap::with_key(),
+                pending_effects: Default::default(),
+                observers: Default::default(),
+                layout_id_buffer: Default::default(),
+            }))
+        }))
+    }
+
+    pub fn run<F>(self, on_finish_launching: F)
+    where
+        F: 'static + FnOnce(&mut MainThread<AppContext>),
+    {
+        let this = self.clone();
+        let platform = self.0.lock().platform.clone();
+        platform.borrow_on_main_thread().run(Box::new(move || {
+            let cx = &mut *this.0.lock();
+            on_finish_launching(cx);
+        }));
+    }
+}
+
+type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
+
+pub struct AppContext {
+    this: Weak<Mutex<MainThread<AppContext>>>,
+    platform: MainThreadOnly<dyn Platform>,
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    text_system: Arc<TextSystem>,
+    pending_updates: usize,
+    pub(crate) text_style_stack: Vec<TextStyleRefinement>,
+    pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
+    pub(crate) unit_entity: Handle<()>,
+    pub(crate) entities: EntityMap,
+    pub(crate) windows: SlotMap<WindowId, Option<Window>>,
+    pub(crate) pending_effects: VecDeque<Effect>,
+    pub(crate) observers: HashMap<EntityId, Handlers>,
+    pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
+}
+
+impl AppContext {
+    fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
+        self.pending_updates += 1;
+        let result = update(self);
+        if self.pending_updates == 1 {
+            self.flush_effects();
+        }
+        self.pending_updates -= 1;
+        result
+    }
+
+    pub(crate) fn update_window<R>(
+        &mut self,
+        id: WindowId,
+        update: impl FnOnce(&mut WindowContext) -> R,
+    ) -> Result<R> {
+        self.update(|cx| {
+            let mut window = cx
+                .windows
+                .get_mut(id)
+                .ok_or_else(|| anyhow!("window not found"))?
+                .take()
+                .unwrap();
+
+            let result = update(&mut WindowContext::mutable(cx, &mut window));
+
+            cx.windows
+                .get_mut(id)
+                .ok_or_else(|| anyhow!("window not found"))?
+                .replace(window);
+
+            Ok(result)
+        })
+    }
+
+    fn flush_effects(&mut self) {
+        while let Some(effect) = self.pending_effects.pop_front() {
+            match effect {
+                Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),
+            }
+        }
+
+        let dirty_window_ids = self
+            .windows
+            .iter()
+            .filter_map(|(window_id, window)| {
+                let window = window.as_ref().unwrap();
+                if window.dirty {
+                    Some(window_id)
+                } else {
+                    None
+                }
+            })
+            .collect::<Vec<_>>();
+
+        for dirty_window_id in dirty_window_ids {
+            self.update_window(dirty_window_id, |cx| cx.draw())
+                .unwrap()
+                .log_err();
+        }
+    }
+
+    fn apply_notify_effect(&mut self, updated_entity: EntityId) {
+        if let Some(mut handlers) = self.observers.remove(&updated_entity) {
+            handlers.retain(|handler| handler(self));
+            if let Some(new_handlers) = self.observers.remove(&updated_entity) {
+                handlers.extend(new_handlers);
+            }
+            self.observers.insert(updated_entity, handlers);
+        }
+    }
+
+    pub fn to_async(&self) -> AsyncContext {
+        AsyncContext(unsafe { mem::transmute(self.this.clone()) })
+    }
+
+    pub fn run_on_main<R>(
+        &self,
+        f: impl FnOnce(&mut MainThread<AppContext>) -> R + Send + 'static,
+    ) -> impl Future<Output = R>
+    where
+        R: Send + 'static,
+    {
+        let this = self.this.upgrade().unwrap();
+        run_on_main(self.dispatcher.clone(), move || {
+            let cx = &mut *this.lock();
+            cx.update(|cx| {
+                f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) })
+            })
+        })
+    }
+
+    pub fn spawn_on_main<F, R>(
+        &self,
+        f: impl FnOnce(&mut MainThread<AppContext>) -> F + Send + 'static,
+    ) -> impl Future<Output = R>
+    where
+        F: Future<Output = R> + 'static,
+        R: Send + 'static,
+    {
+        let this = self.this.upgrade().unwrap();
+        spawn_on_main(self.dispatcher.clone(), move || {
+            let cx = &mut *this.lock();
+            cx.update(|cx| {
+                f(unsafe { mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx) })
+            })
+        })
+    }
+
+    pub fn text_system(&self) -> &Arc<TextSystem> {
+        &self.text_system
+    }
+
+    pub fn text_style(&self) -> TextStyle {
+        let mut style = TextStyle::default();
+        for refinement in &self.text_style_stack {
+            style.refine(refinement);
+        }
+        style
+    }
+
+    pub fn state<S: 'static>(&self) -> &S {
+        self.state_stacks_by_type
+            .get(&TypeId::of::<S>())
+            .and_then(|stack| stack.last())
+            .and_then(|any_state| any_state.downcast_ref::<S>())
+            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
+            .unwrap()
+    }
+
+    pub fn state_mut<S: 'static>(&mut self) -> &mut S {
+        self.state_stacks_by_type
+            .get_mut(&TypeId::of::<S>())
+            .and_then(|stack| stack.last_mut())
+            .and_then(|any_state| any_state.downcast_mut::<S>())
+            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
+            .unwrap()
+    }
+
+    pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
+        self.text_style_stack.push(text_style);
+    }
+
+    pub(crate) fn pop_text_style(&mut self) {
+        self.text_style_stack.pop();
+    }
+
+    pub(crate) fn push_state<T: Send + Sync + 'static>(&mut self, state: T) {
+        self.state_stacks_by_type
+            .entry(TypeId::of::<T>())
+            .or_default()
+            .push(Box::new(state));
+    }
+
+    pub(crate) fn pop_state<T: 'static>(&mut self) {
+        self.state_stacks_by_type
+            .get_mut(&TypeId::of::<T>())
+            .and_then(|stack| stack.pop())
+            .expect("state stack underflow");
+    }
+}
+
+impl Context for AppContext {
+    type EntityContext<'a, 'w, T: Send + Sync + 'static> = ModelContext<'a, T>;
+    type Result<T> = T;
+
+    fn entity<T: Send + Sync + 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Handle<T> {
+        let slot = self.entities.reserve();
+        let entity = build_entity(&mut ModelContext::mutable(self, slot.id));
+        self.entities.redeem(slot, entity)
+    }
+
+    fn update_entity<T: Send + Sync + 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> R {
+        let mut entity = self.entities.lease(handle);
+        let result = update(&mut *entity, &mut ModelContext::mutable(self, handle.id));
+        self.entities.end_lease(entity);
+        result
+    }
+}
+
+impl MainThread<AppContext> {
+    fn update<R>(&mut self, update: impl FnOnce(&mut Self) -> R) -> R {
+        self.0.update(|cx| {
+            update(unsafe {
+                std::mem::transmute::<&mut AppContext, &mut MainThread<AppContext>>(cx)
+            })
+        })
+    }
+
+    pub(crate) fn update_window<R>(
+        &mut self,
+        id: WindowId,
+        update: impl FnOnce(&mut WindowContext) -> R,
+    ) -> Result<R> {
+        self.0.update_window(id, |cx| {
+            update(unsafe {
+                std::mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
+            })
+        })
+    }
+
+    pub(crate) fn platform(&self) -> &dyn Platform {
+        self.platform.borrow_on_main_thread()
+    }
+
+    pub fn activate(&mut self, ignoring_other_apps: bool) {
+        self.platform().activate(ignoring_other_apps);
+    }
+
+    pub fn open_window<S: 'static + Send + Sync>(
+        &mut self,
+        options: crate::WindowOptions,
+        build_root_view: impl FnOnce(&mut WindowContext) -> RootView<S> + Send + 'static,
+    ) -> WindowHandle<S> {
+        self.update(|cx| {
+            let id = cx.windows.insert(None);
+            let handle = WindowHandle::new(id);
+            let mut window = Window::new(handle.into(), options, cx);
+            let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
+            window.root_view.replace(root_view.into_any());
+            cx.windows.get_mut(id).unwrap().replace(window);
+            handle
+        })
+    }
+}
+
+pub(crate) enum Effect {
+    Notify(EntityId),
+}
+
+#[cfg(test)]
+mod tests {
+    use super::AppContext;
+
+    #[test]
+    fn test_app_context_send_sync() {
+        // This will not compile if `AppContext` does not implement `Send`
+        fn assert_send<T: Send>() {}
+        assert_send::<AppContext>();
+    }
+}

crates/gpui3/src/app/async_context.rs 🔗

@@ -0,0 +1,52 @@
+use crate::{AnyWindowHandle, AppContext, Context, Handle, ModelContext, Result, WindowContext};
+use anyhow::anyhow;
+use parking_lot::Mutex;
+use std::sync::Weak;
+
+#[derive(Clone)]
+pub struct AsyncContext(pub(crate) Weak<Mutex<AppContext>>);
+
+impl Context for AsyncContext {
+    type EntityContext<'a, 'b, T: 'static + Send + Sync> = ModelContext<'a, T>;
+    type Result<T> = Result<T>;
+
+    fn entity<T: Send + Sync + 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Result<Handle<T>> {
+        let app = self
+            .0
+            .upgrade()
+            .ok_or_else(|| anyhow!("app was released"))?;
+        let mut lock = app.lock();
+        Ok(lock.entity(build_entity))
+    }
+
+    fn update_entity<T: Send + Sync + 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> Result<R> {
+        let app = self
+            .0
+            .upgrade()
+            .ok_or_else(|| anyhow!("app was released"))?;
+        let mut lock = app.lock();
+        Ok(lock.update_entity(handle, update))
+    }
+}
+
+impl AsyncContext {
+    pub fn update_window<T>(
+        &self,
+        handle: AnyWindowHandle,
+        update: impl FnOnce(&mut WindowContext) -> T + Send + Sync,
+    ) -> Result<T> {
+        let app = self
+            .0
+            .upgrade()
+            .ok_or_else(|| anyhow!("app was released"))?;
+        let mut app_context = app.lock();
+        app_context.update_window(handle.id, update)
+    }
+}

crates/gpui3/src/app/entity_map.rs 🔗

@@ -0,0 +1,175 @@
+use crate::Context;
+use anyhow::{anyhow, Result};
+use derive_more::{Deref, DerefMut};
+use parking_lot::{Mutex, RwLock};
+use slotmap::{SecondaryMap, SlotMap};
+use std::{
+    any::Any,
+    marker::PhantomData,
+    sync::{
+        atomic::{AtomicUsize, Ordering::SeqCst},
+        Arc, Weak,
+    },
+};
+
+slotmap::new_key_type! { pub struct EntityId; }
+
+#[derive(Deref, DerefMut)]
+pub struct Lease<T> {
+    #[deref]
+    #[deref_mut]
+    entity: Box<T>,
+    pub id: EntityId,
+}
+
+pub(crate) struct EntityMap {
+    ref_counts: Arc<RwLock<RefCounts>>,
+    entities: Arc<Mutex<SecondaryMap<EntityId, Box<dyn Any + Send + Sync>>>>,
+}
+
+impl EntityMap {
+    pub fn new() -> Self {
+        Self {
+            ref_counts: Arc::new(RwLock::new(SlotMap::with_key())),
+            entities: Arc::new(Mutex::new(SecondaryMap::new())),
+        }
+    }
+
+    pub fn reserve<T: 'static + Send + Sync>(&self) -> Slot<T> {
+        let id = self.ref_counts.write().insert(1.into());
+        Slot(Handle::new(id, Arc::downgrade(&self.ref_counts)))
+    }
+
+    pub fn redeem<T: 'static + Any + Send + Sync>(&self, slot: Slot<T>, entity: T) -> Handle<T> {
+        let handle = slot.0;
+        self.entities.lock().insert(handle.id, Box::new(entity));
+        handle
+    }
+
+    pub fn lease<T: 'static + Send + Sync>(&self, handle: &Handle<T>) -> Lease<T> {
+        let id = handle.id;
+        let entity = self
+            .entities
+            .lock()
+            .remove(id)
+            .expect("Circular entity lease. Is the entity already being updated?")
+            .downcast::<T>()
+            .unwrap();
+        Lease { id, entity }
+    }
+
+    pub fn end_lease<T: 'static + Send + Sync>(&mut self, lease: Lease<T>) {
+        self.entities.lock().insert(lease.id, lease.entity);
+    }
+
+    pub fn weak_handle<T: 'static + Send + Sync>(&self, id: EntityId) -> WeakHandle<T> {
+        WeakHandle {
+            id,
+            entity_type: PhantomData,
+            ref_counts: Arc::downgrade(&self.ref_counts),
+        }
+    }
+}
+
+#[derive(Deref, DerefMut)]
+pub struct Slot<T: Send + Sync + 'static>(Handle<T>);
+
+pub struct Handle<T: Send + Sync> {
+    pub(crate) id: EntityId,
+    entity_type: PhantomData<T>,
+    ref_counts: Weak<RwLock<RefCounts>>,
+}
+
+type RefCounts = SlotMap<EntityId, AtomicUsize>;
+
+impl<T: 'static + Send + Sync> Handle<T> {
+    pub fn new(id: EntityId, ref_counts: Weak<RwLock<RefCounts>>) -> Self {
+        Self {
+            id,
+            entity_type: PhantomData,
+            ref_counts,
+        }
+    }
+
+    pub fn downgrade(&self) -> WeakHandle<T> {
+        WeakHandle {
+            id: self.id,
+            entity_type: self.entity_type,
+            ref_counts: self.ref_counts.clone(),
+        }
+    }
+
+    /// Update the entity referenced by this handle with the given function.
+    ///
+    /// The update function receives a context appropriate for its environment.
+    /// When updating in an `AppContext`, it receives a `ModelContext`.
+    /// When updating an a `WindowContext`, it receives a `ViewContext`.
+    pub fn update<C: Context, R>(
+        &self,
+        cx: &mut C,
+        update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
+    ) -> C::Result<R> {
+        cx.update_entity(self, update)
+    }
+}
+
+impl<T: Send + Sync> Clone for Handle<T> {
+    fn clone(&self) -> Self {
+        Self {
+            id: self.id,
+            entity_type: PhantomData,
+            ref_counts: self.ref_counts.clone(),
+        }
+    }
+}
+
+impl<T: Send + Sync> Drop for Handle<T> {
+    fn drop(&mut self) {
+        if let Some(ref_counts) = self.ref_counts.upgrade() {
+            if let Some(count) = ref_counts.read().get(self.id) {
+                let prev_count = count.fetch_sub(1, SeqCst);
+                assert_ne!(prev_count, 0, "Detected over-release of a handle.");
+            }
+        }
+    }
+}
+
+pub struct WeakHandle<T> {
+    pub(crate) id: EntityId,
+    pub(crate) entity_type: PhantomData<T>,
+    pub(crate) ref_counts: Weak<RwLock<RefCounts>>,
+}
+
+impl<T: Send + Sync + 'static> WeakHandle<T> {
+    pub fn upgrade(&self, _: &impl Context) -> Option<Handle<T>> {
+        let ref_counts = self.ref_counts.upgrade()?;
+        ref_counts.read().get(self.id).unwrap().fetch_add(1, SeqCst);
+        Some(Handle {
+            id: self.id,
+            entity_type: self.entity_type,
+            ref_counts: self.ref_counts.clone(),
+        })
+    }
+
+    /// Update the entity referenced by this handle with the given function if
+    /// the referenced entity still exists. Returns an error if the entity has
+    /// been released.
+    ///
+    /// The update function receives a context appropriate for its environment.
+    /// When updating in an `AppContext`, it receives a `ModelContext`.
+    /// When updating an a `WindowContext`, it receives a `ViewContext`.
+    pub fn update<C: Context, R>(
+        &self,
+        cx: &mut C,
+        update: impl FnOnce(&mut T, &mut C::EntityContext<'_, '_, T>) -> R,
+    ) -> Result<R>
+    where
+        Result<C::Result<R>>: crate::Flatten<R>,
+    {
+        crate::Flatten::flatten(
+            self.upgrade(cx)
+                .ok_or_else(|| anyhow!("entity release"))
+                .map(|this| cx.update_entity(&this, update)),
+        )
+    }
+}

crates/gpui3/src/app/model_context.rs 🔗

@@ -0,0 +1,87 @@
+use crate::{AppContext, Context, Effect, EntityId, Handle, Reference, WeakHandle};
+use std::{marker::PhantomData, sync::Arc};
+
+pub struct ModelContext<'a, T> {
+    app: Reference<'a, AppContext>,
+    entity_type: PhantomData<T>,
+    entity_id: EntityId,
+}
+
+impl<'a, T: Send + Sync + 'static> ModelContext<'a, T> {
+    pub(crate) fn mutable(app: &'a mut AppContext, entity_id: EntityId) -> Self {
+        Self {
+            app: Reference::Mutable(app),
+            entity_type: PhantomData,
+            entity_id,
+        }
+    }
+
+    // todo!
+    // fn update<R>(&mut self, update: impl FnOnce(&mut T, &mut Self) -> R) -> R {
+    //     let mut entity = self
+    //         .app
+    //         .entities
+    //         .get_mut(self.entity_id)
+    //         .unwrap()
+    //         .take()
+    //         .unwrap();
+    //     let result = update(entity.downcast_mut::<T>().unwrap(), self);
+    //     self.app
+    //         .entities
+    //         .get_mut(self.entity_id)
+    //         .unwrap()
+    //         .replace(entity);
+    //     result
+    // }
+
+    pub fn handle(&self) -> WeakHandle<T> {
+        self.app.entities.weak_handle(self.entity_id)
+    }
+
+    pub fn observe<E: Send + Sync + 'static>(
+        &mut self,
+        handle: &Handle<E>,
+        on_notify: impl Fn(&mut T, Handle<E>, &mut ModelContext<'_, T>) + Send + Sync + 'static,
+    ) {
+        let this = self.handle();
+        let handle = handle.downgrade();
+        self.app
+            .observers
+            .entry(handle.id)
+            .or_default()
+            .push(Arc::new(move |cx| {
+                if let Some((this, handle)) = this.upgrade(cx).zip(handle.upgrade(cx)) {
+                    this.update(cx, |this, cx| on_notify(this, handle, cx));
+                    true
+                } else {
+                    false
+                }
+            }));
+    }
+
+    pub fn notify(&mut self) {
+        self.app
+            .pending_effects
+            .push_back(Effect::Notify(self.entity_id));
+    }
+}
+
+impl<'a, T: 'static> Context for ModelContext<'a, T> {
+    type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>;
+    type Result<U> = U;
+
+    fn entity<U: Send + Sync + 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, U>) -> U,
+    ) -> Handle<U> {
+        self.app.entity(build_entity)
+    }
+
+    fn update_entity<U: Send + Sync + 'static, R>(
+        &mut self,
+        handle: &Handle<U>,
+        update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
+    ) -> R {
+        self.app.update_entity(handle, update)
+    }
+}

crates/gpui3/src/color.rs 🔗

@@ -0,0 +1,238 @@
+#![allow(dead_code)]
+
+use bytemuck::{Pod, Zeroable};
+use serde::de::{self, Deserialize, Deserializer, Visitor};
+use std::fmt;
+use std::num::ParseIntError;
+
+pub fn rgb(hex: u32) -> Rgba {
+    let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
+    let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
+    let b = (hex & 0xFF) as f32 / 255.0;
+    Rgba { r, g, b, a: 1.0 }.into()
+}
+
+#[derive(Clone, Copy, Default, Debug)]
+pub struct Rgba {
+    pub r: f32,
+    pub g: f32,
+    pub b: f32,
+    pub a: f32,
+}
+
+impl Rgba {
+    pub fn blend(&self, other: Rgba) -> Self {
+        if other.a >= 1.0 {
+            return other;
+        } else if other.a <= 0.0 {
+            return *self;
+        } else {
+            return Rgba {
+                r: (self.r * (1.0 - other.a)) + (other.r * other.a),
+                g: (self.g * (1.0 - other.a)) + (other.g * other.a),
+                b: (self.b * (1.0 - other.a)) + (other.b * other.a),
+                a: self.a,
+            };
+        }
+    }
+}
+
+struct RgbaVisitor;
+
+impl<'de> Visitor<'de> for RgbaVisitor {
+    type Value = Rgba;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
+    }
+
+    fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
+        if value.len() == 7 || value.len() == 9 {
+            let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
+            let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
+            let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
+            let a = if value.len() == 9 {
+                u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
+            } else {
+                1.0
+            };
+            Ok(Rgba { r, g, b, a })
+        } else {
+            Err(E::custom(
+                "Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
+            ))
+        }
+    }
+}
+
+impl<'de> Deserialize<'de> for Rgba {
+    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        deserializer.deserialize_str(RgbaVisitor)
+    }
+}
+
+impl From<Hsla> for Rgba {
+    fn from(color: Hsla) -> Self {
+        let h = color.h;
+        let s = color.s;
+        let l = color.l;
+
+        let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
+        let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
+        let m = l - c / 2.0;
+        let cm = c + m;
+        let xm = x + m;
+
+        let (r, g, b) = match (h * 6.0).floor() as i32 {
+            0 | 6 => (cm, xm, m),
+            1 => (xm, cm, m),
+            2 => (m, cm, xm),
+            3 => (m, xm, cm),
+            4 => (xm, m, cm),
+            _ => (cm, m, xm),
+        };
+
+        Rgba {
+            r,
+            g,
+            b,
+            a: color.a,
+        }
+    }
+}
+
+impl TryFrom<&'_ str> for Rgba {
+    type Error = ParseIntError;
+
+    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+        let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0;
+        let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0;
+        let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0;
+        let a = if value.len() > 7 {
+            u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0
+        } else {
+            1.0
+        };
+
+        Ok(Rgba { r, g, b, a })
+    }
+}
+
+#[derive(Default, Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
+#[repr(C)]
+pub struct Hsla {
+    pub h: f32,
+    pub s: f32,
+    pub l: f32,
+    pub a: f32,
+}
+
+impl Eq for Hsla {}
+
+pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
+    Hsla {
+        h: h.clamp(0., 1.),
+        s: s.clamp(0., 1.),
+        l: l.clamp(0., 1.),
+        a: a.clamp(0., 1.),
+    }
+}
+
+pub fn black() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 0.,
+        l: 0.,
+        a: 1.,
+    }
+}
+
+impl Hsla {
+    /// Returns true if the HSLA color is fully transparent, false otherwise.
+    pub fn is_transparent(&self) -> bool {
+        self.a == 0.0
+    }
+
+    /// Blends `other` on top of `self` based on `other`'s alpha value. The resulting color is a combination of `self`'s and `other`'s colors.
+    ///
+    /// If `other`'s alpha value is 1.0 or greater, `other` color is fully opaque, thus `other` is returned as the output color.
+    /// If `other`'s alpha value is 0.0 or less, `other` color is fully transparent, thus `self` is returned as the output color.
+    /// Else, the output color is calculated as a blend of `self` and `other` based on their weighted alpha values.
+    ///
+    /// Assumptions:
+    /// - Alpha values are contained in the range [0, 1], with 1 as fully opaque and 0 as fully transparent.
+    /// - The relative contributions of `self` and `other` is based on `self`'s alpha value (`self.a`) and `other`'s  alpha value (`other.a`), `self` contributing `self.a * (1.0 - other.a)` and `other` contributing it's own alpha value.
+    /// - RGB color components are contained in the range [0, 1].
+    /// - If `self` and `other` colors are out of the valid range, the blend operation's output and behavior is undefined.
+    pub fn blend(self, other: Hsla) -> Hsla {
+        let alpha = other.a;
+
+        if alpha >= 1.0 {
+            return other;
+        } else if alpha <= 0.0 {
+            return self;
+        } else {
+            let converted_self = Rgba::from(self);
+            let converted_other = Rgba::from(other);
+            let blended_rgb = converted_self.blend(converted_other);
+            return Hsla::from(blended_rgb);
+        }
+    }
+
+    /// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
+    /// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
+    pub fn fade_out(&mut self, factor: f32) {
+        self.a *= 1.0 - factor.clamp(0., 1.);
+    }
+}
+
+impl From<Rgba> for Hsla {
+    fn from(color: Rgba) -> Self {
+        let r = color.r;
+        let g = color.g;
+        let b = color.b;
+
+        let max = r.max(g.max(b));
+        let min = r.min(g.min(b));
+        let delta = max - min;
+
+        let l = (max + min) / 2.0;
+        let s = if l == 0.0 || l == 1.0 {
+            0.0
+        } else if l < 0.5 {
+            delta / (2.0 * l)
+        } else {
+            delta / (2.0 - 2.0 * l)
+        };
+
+        let h = if delta == 0.0 {
+            0.0
+        } else if max == r {
+            ((g - b) / delta).rem_euclid(6.0) / 6.0
+        } else if max == g {
+            ((b - r) / delta + 2.0) / 6.0
+        } else {
+            ((r - g) / delta + 4.0) / 6.0
+        };
+
+        Hsla {
+            h,
+            s,
+            l,
+            a: color.a,
+        }
+    }
+}
+
+impl<'de> Deserialize<'de> for Hsla {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        // First, deserialize it into Rgba
+        let rgba = Rgba::deserialize(deserializer)?;
+
+        // Then, use the From<Rgba> for Hsla implementation to convert it
+        Ok(Hsla::from(rgba))
+    }
+}

crates/gpui3/src/element.rs 🔗

@@ -0,0 +1,166 @@
+use super::{Layout, LayoutId, Pixels, Point, Result, ViewContext};
+pub(crate) use smallvec::SmallVec;
+
+pub trait Element: 'static {
+    type State;
+    type FrameState;
+
+    fn layout(
+        &mut self,
+        state: &mut Self::State,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<(LayoutId, Self::FrameState)>;
+
+    fn paint(
+        &mut self,
+        layout: Layout,
+        state: &mut Self::State,
+        frame_state: &mut Self::FrameState,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()>;
+}
+
+pub trait ParentElement<S> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]>;
+
+    fn child(mut self, child: impl IntoAnyElement<S>) -> Self
+    where
+        Self: Sized,
+    {
+        self.children_mut().push(child.into_any());
+        self
+    }
+
+    fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<S>>) -> Self
+    where
+        Self: Sized,
+    {
+        self.children_mut()
+            .extend(iter.into_iter().map(|item| item.into_any()));
+        self
+    }
+}
+
+trait ElementObject<S> {
+    fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId>;
+    fn paint(
+        &mut self,
+        state: &mut S,
+        offset: Option<Point<Pixels>>,
+        cx: &mut ViewContext<S>,
+    ) -> Result<()>;
+}
+
+struct RenderedElement<E: Element> {
+    element: E,
+    phase: ElementRenderPhase<E::FrameState>,
+}
+
+#[derive(Default)]
+enum ElementRenderPhase<S> {
+    #[default]
+    Rendered,
+    LayoutRequested {
+        layout_id: LayoutId,
+        frame_state: S,
+    },
+    Painted {
+        layout: Layout,
+        frame_state: S,
+    },
+}
+
+/// Internal struct that wraps an element to store Layout and FrameState after the element is rendered.
+/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for
+/// improved usability.
+impl<E: Element> RenderedElement<E> {
+    fn new(element: E) -> Self {
+        RenderedElement {
+            element,
+            phase: ElementRenderPhase::Rendered,
+        }
+    }
+}
+
+impl<E: Element> ElementObject<E::State> for RenderedElement<E> {
+    fn layout(&mut self, state: &mut E::State, cx: &mut ViewContext<E::State>) -> Result<LayoutId> {
+        let (layout_id, frame_state) = self.element.layout(state, cx)?;
+        self.phase = ElementRenderPhase::LayoutRequested {
+            layout_id,
+            frame_state,
+        };
+        Ok(layout_id)
+    }
+
+    fn paint(
+        &mut self,
+        state: &mut E::State,
+        offset: Option<Point<Pixels>>,
+        cx: &mut ViewContext<E::State>,
+    ) -> Result<()> {
+        self.phase = match std::mem::take(&mut self.phase) {
+            ElementRenderPhase::Rendered => panic!("must call layout before paint"),
+
+            ElementRenderPhase::LayoutRequested {
+                layout_id,
+                mut frame_state,
+            } => {
+                let mut layout = cx.layout(layout_id)?.clone();
+                offset.map(|offset| layout.bounds.origin += offset);
+                self.element
+                    .paint(layout.clone(), state, &mut frame_state, cx)?;
+                ElementRenderPhase::Painted {
+                    layout,
+                    frame_state,
+                }
+            }
+
+            ElementRenderPhase::Painted {
+                layout,
+                mut frame_state,
+            } => {
+                self.element
+                    .paint(layout.clone(), state, &mut frame_state, cx)?;
+                ElementRenderPhase::Painted {
+                    layout,
+                    frame_state,
+                }
+            }
+        };
+
+        Ok(())
+    }
+}
+
+pub struct AnyElement<S>(Box<dyn ElementObject<S>>);
+
+impl<S> AnyElement<S> {
+    pub fn layout(&mut self, state: &mut S, cx: &mut ViewContext<S>) -> Result<LayoutId> {
+        self.0.layout(state, cx)
+    }
+
+    pub fn paint(
+        &mut self,
+        state: &mut S,
+        offset: Option<Point<Pixels>>,
+        cx: &mut ViewContext<S>,
+    ) -> Result<()> {
+        self.0.paint(state, offset, cx)
+    }
+}
+
+pub trait IntoAnyElement<S> {
+    fn into_any(self) -> AnyElement<S>;
+}
+
+impl<E: Element> IntoAnyElement<E::State> for E {
+    fn into_any(self) -> AnyElement<E::State> {
+        AnyElement(Box::new(RenderedElement::new(self)))
+    }
+}
+
+impl<S> IntoAnyElement<S> for AnyElement<S> {
+    fn into_any(self) -> AnyElement<S> {
+        self
+    }
+}

crates/gpui3/src/elements.rs 🔗

@@ -0,0 +1,11 @@
+mod div;
+mod img;
+mod stateless;
+mod svg;
+mod text;
+
+pub use div::*;
+pub use img::*;
+pub use stateless::*;
+pub use svg::*;
+pub use text::*;

crates/gpui3/src/elements/div.rs 🔗

@@ -0,0 +1,297 @@
+use crate::{
+    AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
+    Refineable, RefinementCascade, Result, StackContext, Style, StyleHelpers, Styled, ViewContext,
+};
+use parking_lot::Mutex;
+use smallvec::SmallVec;
+use std::sync::Arc;
+use util::ResultExt;
+
+pub struct Div<S: 'static> {
+    styles: RefinementCascade<Style>,
+    // handlers: InteractionHandlers<V>,
+    children: SmallVec<[AnyElement<S>; 2]>,
+    scroll_state: Option<ScrollState>,
+}
+
+pub fn div<S>() -> Div<S> {
+    Div {
+        styles: Default::default(),
+        // handlers: Default::default(),
+        children: Default::default(),
+        scroll_state: None,
+    }
+}
+
+impl<S: 'static + Send + Sync> Element for Div<S> {
+    type State = S;
+    type FrameState = Vec<LayoutId>;
+
+    fn layout(
+        &mut self,
+        view: &mut S,
+        cx: &mut ViewContext<S>,
+    ) -> Result<(LayoutId, Self::FrameState)> {
+        let style = self.computed_style();
+        let child_layout_ids = if let Some(text_style) = style.text_style(cx) {
+            cx.with_text_style(text_style.clone(), |cx| self.layout_children(view, cx))?
+        } else {
+            self.layout_children(view, cx)?
+        };
+
+        Ok((
+            cx.request_layout(style.into(), child_layout_ids.clone())?,
+            child_layout_ids,
+        ))
+    }
+
+    fn paint(
+        &mut self,
+        layout: Layout,
+        state: &mut S,
+        child_layouts: &mut Self::FrameState,
+        cx: &mut ViewContext<S>,
+    ) -> Result<()> {
+        let Layout { order, bounds } = layout;
+
+        let style = self.computed_style();
+        style.paint(order, bounds, cx);
+        let overflow = &style.overflow;
+        // // todo!("support only one dimension being hidden")
+        // if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
+        //     cx.scene().push_layer(Some(bounds));
+        //     pop_layer = true;
+        // }
+        if let Some(text_style) = style.text_style(cx) {
+            cx.with_text_style(text_style.clone(), |cx| {
+                self.paint_children(overflow, state, cx)
+            })?;
+        } else {
+            self.paint_children(overflow, state, cx)?;
+        }
+
+        self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
+
+        // todo!("enable inspector")
+        // if cx.is_inspector_enabled() {
+        //     self.paint_inspector(parent_origin, layout, cx);
+        // }
+        //
+        Ok(())
+    }
+}
+
+impl<S: 'static> Div<S> {
+    pub fn overflow_hidden(mut self) -> Self {
+        self.declared_style().overflow.x = Some(Overflow::Hidden);
+        self.declared_style().overflow.y = Some(Overflow::Hidden);
+        self
+    }
+
+    pub fn overflow_hidden_x(mut self) -> Self {
+        self.declared_style().overflow.x = Some(Overflow::Hidden);
+        self
+    }
+
+    pub fn overflow_hidden_y(mut self) -> Self {
+        self.declared_style().overflow.y = Some(Overflow::Hidden);
+        self
+    }
+
+    pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
+        self.scroll_state = Some(scroll_state);
+        self.declared_style().overflow.x = Some(Overflow::Scroll);
+        self.declared_style().overflow.y = Some(Overflow::Scroll);
+        self
+    }
+
+    pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
+        self.scroll_state = Some(scroll_state);
+        self.declared_style().overflow.x = Some(Overflow::Scroll);
+        self
+    }
+
+    pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
+        self.scroll_state = Some(scroll_state);
+        self.declared_style().overflow.y = Some(Overflow::Scroll);
+        self
+    }
+
+    fn scroll_offset(&self, overflow: &Point<Overflow>) -> Point<Pixels> {
+        let mut offset = Point::default();
+        if overflow.y == Overflow::Scroll {
+            offset.y = self.scroll_state.as_ref().unwrap().y();
+        }
+        if overflow.x == Overflow::Scroll {
+            offset.x = self.scroll_state.as_ref().unwrap().x();
+        }
+
+        offset
+    }
+
+    fn layout_children(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> Result<Vec<LayoutId>> {
+        self.children
+            .iter_mut()
+            .map(|child| child.layout(view, cx))
+            .collect::<Result<Vec<LayoutId>>>()
+    }
+
+    fn paint_children(
+        &mut self,
+        overflow: &Point<Overflow>,
+        state: &mut S,
+        cx: &mut ViewContext<S>,
+    ) -> Result<()> {
+        let scroll_offset = self.scroll_offset(overflow);
+        for child in &mut self.children {
+            child.paint(state, Some(scroll_offset), cx)?;
+        }
+        Ok(())
+    }
+
+    fn handle_scroll(
+        &mut self,
+        _order: u32,
+        bounds: Bounds<Pixels>,
+        overflow: Point<Overflow>,
+        child_layout_ids: &[LayoutId],
+        cx: &mut ViewContext<S>,
+    ) {
+        if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
+            let mut scroll_max = Point::default();
+            for child_layout_id in child_layout_ids {
+                if let Some(child_layout) = cx.layout(*child_layout_id).log_err() {
+                    scroll_max = scroll_max.max(&child_layout.bounds.lower_right());
+                }
+            }
+            scroll_max -= bounds.size;
+
+            // todo!("handle scroll")
+            // let scroll_state = self.scroll_state.as_ref().unwrap().clone();
+            // cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
+            //     if bounds.contains_point(event.position) {
+            //         let scroll_delta = match event.delta {
+            //             ScrollDelta::Pixels(delta) => delta,
+            //             ScrollDelta::Lines(delta) => cx.text_style().font_size * delta,
+            //         };
+            //         if overflow.x == Overflow::Scroll {
+            //             scroll_state.set_x(
+            //                 (scroll_state.x() - scroll_delta.x())
+            //                     .max(px(0.))
+            //                     .min(scroll_max.x),
+            //             );
+            //         }
+            //         if overflow.y == Overflow::Scroll {
+            //             scroll_state.set_y(
+            //                 (scroll_state.y() - scroll_delta.y())
+            //                     .max(px(0.))
+            //                     .min(scroll_max.y),
+            //             );
+            //         }
+            //         cx.repaint();
+            //     } else {
+            //         cx.bubble_event();
+            //     }
+            // })
+        }
+    }
+
+    // fn paint_inspector(
+    //     &self,
+    //     parent_origin: Point<Pixels>,
+    //     layout: &Layout,
+    //     cx: &mut ViewContext<V>,
+    // ) {
+    //     let style = self.styles.merged();
+    //     let bounds = layout.bounds;
+
+    //     let hovered = bounds.contains_point(cx.mouse_position());
+    //     if hovered {
+    //         let rem_size = cx.rem_size();
+    //         // cx.scene().push_quad(scene::Quad {
+    //         //     bounds,
+    //         //     background: Some(hsla(0., 0., 1., 0.05).into()),
+    //         //     border: gpui::Border {
+    //         //         color: hsla(0., 0., 1., 0.2).into(),
+    //         //         top: 1.,
+    //         //         right: 1.,
+    //         //         bottom: 1.,
+    //         //         left: 1.,
+    //         //     },
+    //         //     corner_radii: CornerRadii::default()
+    //         //         .refined(&style.corner_radii)
+    //         //         .to_gpui(bounds.size(), rem_size),
+    //         // })
+    //     }
+
+    //     // let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left));
+    //     // cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| {
+    //     //     if bounds.contains_point(event.position) {
+    //     //         if event.is_down {
+    //     //             pressed.set(true);
+    //     //         } else if pressed.get() {
+    //     //             pressed.set(false);
+    //     //             eprintln!("clicked div {:?} {:#?}", bounds, style);
+    //     //         }
+    //     //     }
+    //     // });
+
+    //     // let hovered = Cell::new(hovered);
+    //     // cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| {
+    //     //     cx.bubble_event();
+    //     //     let hovered_now = bounds.contains_point(event.position);
+    //     //     if hovered.get() != hovered_now {
+    //     //         hovered.set(hovered_now);
+    //     //         cx.repaint();
+    //     //     }
+    //     // });
+    // }
+    //
+}
+
+impl<V> Styled for Div<V> {
+    type Style = Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+        &mut self.styles
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+        self.styles.base()
+    }
+}
+
+impl<V> StyleHelpers for Div<V> {}
+
+// impl<V> Interactive<V> for Div<V> {
+//     fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+//         &mut self.handlers
+//     }
+// }
+
+impl<V: 'static> ParentElement<V> for Div<V> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        &mut self.children
+    }
+}
+
+#[derive(Default, Clone)]
+pub struct ScrollState(Arc<Mutex<Point<Pixels>>>);
+
+impl ScrollState {
+    pub fn x(&self) -> Pixels {
+        self.0.lock().x
+    }
+
+    pub fn set_x(&self, value: Pixels) {
+        self.0.lock().x = value;
+    }
+
+    pub fn y(&self) -> Pixels {
+        self.0.lock().y
+    }
+
+    pub fn set_y(&self, value: Pixels) {
+        self.0.lock().y = value;
+    }
+}

crates/gpui3/src/elements/hoverable.rs 🔗

@@ -0,0 +1,105 @@
+use crate::{
+    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
+    style::{Style, StyleHelpers, Styleable},
+    ViewContext,
+};
+use anyhow::Result;
+use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
+use refineable::{CascadeSlot, Refineable, RefinementCascade};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+pub struct Hoverable<E: Styleable> {
+    hovered: Rc<Cell<bool>>,
+    cascade_slot: CascadeSlot,
+    hovered_style: <E::Style as Refineable>::Refinement,
+    child: E,
+}
+
+pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
+    Hoverable {
+        hovered: Rc::new(Cell::new(false)),
+        cascade_slot: child.style_cascade().reserve(),
+        hovered_style: Default::default(),
+        child,
+    }
+}
+
+impl<E: Styleable> Styleable for Hoverable<E> {
+    type Style = E::Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+        self.child.style_cascade()
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+        &mut self.hovered_style
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
+    type PaintState = E::PaintState;
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        Ok(self.child.layout(view, cx)?)
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        parent_origin: Vector2F,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        let bounds = layout.bounds + parent_origin;
+        self.hovered.set(bounds.contains_point(cx.mouse_position()));
+
+        let slot = self.cascade_slot;
+        let style = self.hovered.get().then_some(self.hovered_style.clone());
+        self.style_cascade().set(slot, style);
+
+        let hovered = self.hovered.clone();
+        cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
+            cx.bubble_event();
+            if bounds.contains_point(cx.mouse_position()) != hovered.get() {
+                cx.repaint();
+            }
+        });
+
+        self.child
+            .paint(view, parent_origin, layout, paint_state, cx);
+    }
+}
+
+impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
+
+impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
+    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+        self.child.interaction_handlers()
+    }
+}
+
+impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        self.child.children_mut()
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

crates/gpui3/src/elements/img.rs 🔗

@@ -0,0 +1,103 @@
+use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
+use refineable::RefinementCascade;
+use std::marker::PhantomData;
+use util::arc_cow::ArcCow;
+
+pub struct Img<S> {
+    style: RefinementCascade<Style>,
+    uri: Option<ArcCow<'static, str>>,
+    state_type: PhantomData<S>,
+}
+
+pub fn img<S>() -> Img<S> {
+    Img {
+        style: RefinementCascade::default(),
+        uri: None,
+        state_type: PhantomData,
+    }
+}
+
+impl<S> Img<S> {
+    pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
+        self.uri = Some(uri.into());
+        self
+    }
+}
+
+impl<S: 'static> Element for Img<S> {
+    type State = S;
+    type FrameState = ();
+
+    fn layout(
+        &mut self,
+        _: &mut Self::State,
+        cx: &mut crate::ViewContext<Self::State>,
+    ) -> anyhow::Result<(LayoutId, Self::FrameState)>
+    where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        let layout_id = cx.request_layout(style, [])?;
+        Ok((layout_id, ()))
+    }
+
+    fn paint(
+        &mut self,
+        layout: Layout,
+        _: &mut Self::State,
+        _: &mut Self::FrameState,
+        cx: &mut crate::ViewContext<Self::State>,
+    ) -> Result<()> {
+        let style = self.computed_style();
+        let order = layout.order;
+        let bounds = layout.bounds;
+
+        style.paint(order, bounds, cx);
+
+        // if let Some(uri) = &self.uri {
+        //     let image_future = cx.image_cache.get(uri.clone());
+        //     if let Some(data) = image_future
+        //         .clone()
+        //         .now_or_never()
+        //         .and_then(ResultExt::log_err)
+        //     {
+        //         let rem_size = cx.rem_size();
+        //         cx.scene().push_image(scene::Image {
+        //             bounds,
+        //             border: gpui::Border {
+        //                 color: style.border_color.unwrap_or_default().into(),
+        //                 top: style.border_widths.top.to_pixels(rem_size),
+        //                 right: style.border_widths.right.to_pixels(rem_size),
+        //                 bottom: style.border_widths.bottom.to_pixels(rem_size),
+        //                 left: style.border_widths.left.to_pixels(rem_size),
+        //             },
+        //             corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
+        //             grayscale: false,
+        //             data,
+        //         })
+        //     } else {
+        //         cx.spawn(|this, mut cx| async move {
+        //             if image_future.await.log_err().is_some() {
+        //                 this.update(&mut cx, |_, cx| cx.notify()).ok();
+        //             }
+        //         })
+        //         .detach();
+        //     }
+        // }
+        Ok(())
+    }
+}
+
+impl<S> Styled for Img<S> {
+    type Style = Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+        &mut self.style
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
+        self.style.base()
+    }
+}
+
+impl<S> StyleHelpers for Img<S> {}

crates/gpui3/src/elements/pressable.rs 🔗

@@ -0,0 +1,108 @@
+use crate::{
+    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
+    style::{Style, StyleHelpers, Styleable},
+    ViewContext,
+};
+use anyhow::Result;
+use gpui::{geometry::vector::Vector2F, platform::MouseButtonEvent, LayoutId};
+use refineable::{CascadeSlot, Refineable, RefinementCascade};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+pub struct Pressable<E: Styleable> {
+    pressed: Rc<Cell<bool>>,
+    pressed_style: <E::Style as Refineable>::Refinement,
+    cascade_slot: CascadeSlot,
+    child: E,
+}
+
+pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
+    Pressable {
+        pressed: Rc::new(Cell::new(false)),
+        pressed_style: Default::default(),
+        cascade_slot: child.style_cascade().reserve(),
+        child,
+    }
+}
+
+impl<E: Styleable> Styleable for Pressable<E> {
+    type Style = E::Style;
+
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+        &mut self.pressed_style
+    }
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
+        self.child.style_cascade()
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
+    type PaintState = E::PaintState;
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        self.child.layout(view, cx)
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        parent_origin: Vector2F,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        let slot = self.cascade_slot;
+        let style = self.pressed.get().then_some(self.pressed_style.clone());
+        self.style_cascade().set(slot, style);
+
+        let pressed = self.pressed.clone();
+        let bounds = layout.bounds + parent_origin;
+        cx.on_event(layout.order, move |_view, event: &MouseButtonEvent, cx| {
+            if event.is_down {
+                if bounds.contains_point(event.position) {
+                    pressed.set(true);
+                    cx.repaint();
+                }
+            } else if pressed.get() {
+                pressed.set(false);
+                cx.repaint();
+            }
+        });
+
+        self.child
+            .paint(view, parent_origin, layout, paint_state, cx);
+    }
+}
+
+impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
+
+impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
+    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+        self.child.interaction_handlers()
+    }
+}
+
+impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        self.child.children_mut()
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

crates/gpui3/src/elements/stateless.rs 🔗

@@ -0,0 +1,30 @@
+use crate::Element;
+use std::marker::PhantomData;
+
+pub struct Stateless<E: Element<State = ()>, S> {
+    element: E,
+    parent_state_type: PhantomData<S>,
+}
+
+impl<E: Element<State = ()>, S: Send + Sync + 'static> Element for Stateless<E, S> {
+    type State = S;
+    type FrameState = E::FrameState;
+
+    fn layout(
+        &mut self,
+        _: &mut Self::State,
+        cx: &mut crate::ViewContext<Self::State>,
+    ) -> anyhow::Result<(crate::LayoutId, Self::FrameState)> {
+        cx.erase_state(|cx| self.element.layout(&mut (), cx))
+    }
+
+    fn paint(
+        &mut self,
+        layout: crate::Layout,
+        _: &mut Self::State,
+        frame_state: &mut Self::FrameState,
+        cx: &mut crate::ViewContext<Self::State>,
+    ) -> anyhow::Result<()> {
+        cx.erase_state(|cx| self.element.paint(layout, &mut (), frame_state, cx))
+    }
+}

crates/gpui3/src/elements/svg.rs 🔗

@@ -0,0 +1,82 @@
+use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
+use refineable::RefinementCascade;
+use std::{borrow::Cow, marker::PhantomData};
+
+pub struct Svg<S> {
+    path: Option<Cow<'static, str>>,
+    style: RefinementCascade<Style>,
+    state_type: PhantomData<S>,
+}
+
+pub fn svg<S>() -> Svg<S> {
+    Svg {
+        path: None,
+        style: RefinementCascade::<Style>::default(),
+        state_type: PhantomData,
+    }
+}
+
+impl<S> Svg<S> {
+    pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
+        self.path = Some(path.into());
+        self
+    }
+}
+
+impl<S: 'static> Element for Svg<S> {
+    type State = S;
+    type FrameState = ();
+
+    fn layout(
+        &mut self,
+        _: &mut S,
+        cx: &mut crate::ViewContext<S>,
+    ) -> anyhow::Result<(LayoutId, Self::FrameState)>
+    where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        Ok((cx.request_layout(style, [])?, ()))
+    }
+
+    fn paint(
+        &mut self,
+        _layout: Layout,
+        _: &mut Self::State,
+        _: &mut Self::FrameState,
+        _cx: &mut crate::ViewContext<S>,
+    ) -> Result<()>
+    where
+        Self: Sized,
+    {
+        // todo!
+        // let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
+        // if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
+        //     if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
+        //         let icon = scene::Icon {
+        //             bounds: layout.bounds + parent_origin,
+        //             svg: svg_tree,
+        //             path: path.clone(),
+        //             color: Rgba::from(fill_color).into(),
+        //         };
+
+        //         cx.scene().push_icon(icon);
+        //     }
+        // }
+        Ok(())
+    }
+}
+
+impl<S> Styled for Svg<S> {
+    type Style = Style;
+
+    fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
+        &mut self.style
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
+        self.style.base()
+    }
+}
+
+impl<S> StyleHelpers for Svg<S> {}

crates/gpui3/src/elements/text.rs 🔗

@@ -0,0 +1,121 @@
+use crate::{
+    AnyElement, Element, IntoAnyElement, Layout, LayoutId, Line, Pixels, Result, Size, ViewContext,
+};
+use parking_lot::Mutex;
+use std::{marker::PhantomData, sync::Arc};
+use util::{arc_cow::ArcCow, ResultExt};
+
+impl<S: 'static> IntoAnyElement<S> for ArcCow<'static, str> {
+    fn into_any(self) -> AnyElement<S> {
+        Text {
+            text: self,
+            state_type: PhantomData,
+        }
+        .into_any()
+    }
+}
+
+impl<V: 'static> IntoAnyElement<V> for &'static str {
+    fn into_any(self) -> AnyElement<V> {
+        Text {
+            text: ArcCow::from(self),
+            state_type: PhantomData,
+        }
+        .into_any()
+    }
+}
+
+pub struct Text<S> {
+    text: ArcCow<'static, str>,
+    state_type: PhantomData<S>,
+}
+
+impl<S: 'static> Element for Text<S> {
+    type State = S;
+    type FrameState = Arc<Mutex<Option<TextLayout>>>;
+
+    fn layout(
+        &mut self,
+        _view: &mut S,
+        cx: &mut ViewContext<S>,
+    ) -> Result<(LayoutId, Self::FrameState)> {
+        dbg!("layout text");
+
+        let text_system = cx.text_system().clone();
+        let text_style = cx.text_style();
+        let font_size = text_style.font_size * cx.rem_size();
+        let line_height = text_style
+            .line_height
+            .to_pixels(font_size.into(), cx.rem_size());
+        let text = self.text.clone();
+        let paint_state = Arc::new(Mutex::new(None));
+
+        let rem_size = cx.rem_size();
+        let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
+            let frame_state = paint_state.clone();
+            move |_, _| {
+                dbg!("starting measurement");
+                let Some(line_layout) = text_system
+                    .layout_line(
+                        text.as_ref(),
+                        font_size,
+                        &[(text.len(), text_style.to_run())],
+                    )
+                    .log_err()
+                else {
+                    return Size::default();
+                };
+                dbg!("bbbb");
+
+                let size = Size {
+                    width: line_layout.width(),
+                    height: line_height,
+                };
+
+                frame_state.lock().replace(TextLayout {
+                    line: Arc::new(line_layout),
+                    line_height,
+                });
+
+                dbg!(size)
+            }
+        });
+
+        dbg!("got to end of text layout");
+        Ok((layout_id?, paint_state))
+    }
+
+    fn paint<'a>(
+        &mut self,
+        layout: Layout,
+        _: &mut Self::State,
+        paint_state: &mut Self::FrameState,
+        cx: &mut ViewContext<S>,
+    ) -> Result<()> {
+        let bounds = layout.bounds;
+
+        let line;
+        let line_height;
+        {
+            let paint_state = paint_state.lock();
+            let paint_state = paint_state
+                .as_ref()
+                .expect("measurement has not been performed");
+            line = paint_state.line.clone();
+            line_height = paint_state.line_height;
+        }
+
+        let _text_style = cx.text_style();
+
+        // todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
+        let visible_bounds = bounds;
+        line.paint(bounds.origin, visible_bounds, line_height, cx)?;
+
+        Ok(())
+    }
+}
+
+pub struct TextLayout {
+    line: Arc<Line>,
+    line_height: Pixels,
+}

crates/gpui3/src/executor.rs 🔗

@@ -0,0 +1,1095 @@
+use crate::util;
+use crate::PlatformDispatcher;
+use anyhow::{anyhow, Result};
+use async_task::Runnable;
+use futures::channel::{mpsc, oneshot};
+use smol::{channel, prelude::*, Executor};
+use std::{
+    any::Any,
+    fmt::{self},
+    marker::PhantomData,
+    mem,
+    pin::Pin,
+    rc::Rc,
+    sync::Arc,
+    task::{Context, Poll},
+    thread,
+    time::Duration,
+};
+
+/// Enqueues the given closure to run on the application's event loop.
+/// Returns the result asynchronously.
+pub(crate) fn run_on_main<F, R>(
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    func: F,
+) -> impl Future<Output = R>
+where
+    F: FnOnce() -> R + Send + 'static,
+    R: Send + 'static,
+{
+    spawn_on_main(dispatcher, move || async move { func() })
+}
+
+/// Enqueues the given closure to be run on the application's event loop. The
+/// closure returns a future which will be run to completion on the main thread.
+pub(crate) fn spawn_on_main<F, R>(
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    func: impl FnOnce() -> F + Send + 'static,
+) -> impl Future<Output = R>
+where
+    F: Future<Output = R> + 'static,
+    R: Send + 'static,
+{
+    let (tx, rx) = oneshot::channel();
+    let (runnable, task) = async_task::spawn(
+        {
+            let dispatcher = dispatcher.clone();
+            async move {
+                let future = func();
+                let _ = spawn_on_main_local(dispatcher, async move {
+                    let result = future.await;
+                    let _ = tx.send(result);
+                });
+            }
+        },
+        move |runnable| dispatcher.run_on_main_thread(runnable),
+    );
+    runnable.schedule();
+    task.detach();
+    async move { rx.await.unwrap() }
+}
+
+/// Enqueues the given closure to be run on the application's event loop. Must
+/// be called on the main thread.
+pub(crate) fn spawn_on_main_local<R>(
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    future: impl Future<Output = R> + 'static,
+) -> impl Future<Output = R>
+where
+    R: 'static,
+{
+    assert!(dispatcher.is_main_thread(), "must be called on main thread");
+
+    let (tx, rx) = oneshot::channel();
+    let (runnable, task) = async_task::spawn_local(
+        async move {
+            let result = future.await;
+            let _ = tx.send(result);
+        },
+        move |runnable| dispatcher.run_on_main_thread(runnable),
+    );
+    runnable.schedule();
+    task.detach();
+    async move { rx.await.unwrap() }
+}
+
+pub enum ForegroundExecutor {
+    Platform {
+        dispatcher: Arc<dyn PlatformDispatcher>,
+        _not_send_or_sync: PhantomData<Rc<()>>,
+    },
+    #[cfg(any(test, feature = "test"))]
+    Deterministic {
+        cx_id: usize,
+        executor: Arc<Deterministic>,
+    },
+}
+
+pub enum BackgroundExecutor {
+    #[cfg(any(test, feature = "test"))]
+    Deterministic { executor: Arc<Deterministic> },
+    Production {
+        executor: Arc<smol::Executor<'static>>,
+        _stop: channel::Sender<()>,
+    },
+}
+
+type AnyLocalFuture = Pin<Box<dyn 'static + Future<Output = Box<dyn Any + 'static>>>>;
+type AnyFuture = Pin<Box<dyn 'static + Send + Future<Output = Box<dyn Any + Send + 'static>>>>;
+type AnyTask = async_task::Task<Box<dyn Any + Send + 'static>>;
+type AnyLocalTask = async_task::Task<Box<dyn Any + 'static>>;
+
+#[must_use]
+pub enum Task<T> {
+    Ready(Option<T>),
+    Local {
+        any_task: AnyLocalTask,
+        result_type: PhantomData<T>,
+    },
+    Send {
+        any_task: AnyTask,
+        result_type: PhantomData<T>,
+    },
+}
+
+unsafe impl<T: Send> Send for Task<T> {}
+
+#[cfg(any(test, feature = "test"))]
+struct DeterministicState {
+    rng: rand::prelude::StdRng,
+    seed: u64,
+    scheduled_from_foreground: collections::HashMap<usize, Vec<ForegroundRunnable>>,
+    scheduled_from_background: Vec<BackgroundRunnable>,
+    forbid_parking: bool,
+    block_on_ticks: std::ops::RangeInclusive<usize>,
+    now: std::time::Instant,
+    next_timer_id: usize,
+    pending_timers: Vec<(usize, std::time::Instant, postage::barrier::Sender)>,
+    waiting_backtrace: Option<backtrace::Backtrace>,
+    next_runnable_id: usize,
+    poll_history: Vec<ExecutorEvent>,
+    previous_poll_history: Option<Vec<ExecutorEvent>>,
+    enable_runnable_backtraces: bool,
+    runnable_backtraces: collections::HashMap<usize, backtrace::Backtrace>,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ExecutorEvent {
+    PollRunnable { id: usize },
+    EnqueuRunnable { id: usize },
+}
+
+#[cfg(any(test, feature = "test"))]
+struct ForegroundRunnable {
+    id: usize,
+    runnable: Runnable,
+    main: bool,
+}
+
+#[cfg(any(test, feature = "test"))]
+struct BackgroundRunnable {
+    id: usize,
+    runnable: Runnable,
+}
+
+#[cfg(any(test, feature = "test"))]
+pub struct Deterministic {
+    state: Arc<parking_lot::Mutex<DeterministicState>>,
+    parker: parking_lot::Mutex<parking::Parker>,
+}
+
+#[must_use]
+pub enum Timer {
+    Production(smol::Timer),
+    #[cfg(any(test, feature = "test"))]
+    Deterministic(DeterministicTimer),
+}
+
+#[cfg(any(test, feature = "test"))]
+pub struct DeterministicTimer {
+    rx: postage::barrier::Receiver,
+    id: usize,
+    state: Arc<parking_lot::Mutex<DeterministicState>>,
+}
+
+#[cfg(any(test, feature = "test"))]
+impl Deterministic {
+    pub fn new(seed: u64) -> Arc<Self> {
+        use rand::prelude::*;
+
+        Arc::new(Self {
+            state: Arc::new(parking_lot::Mutex::new(DeterministicState {
+                rng: StdRng::seed_from_u64(seed),
+                seed,
+                scheduled_from_foreground: Default::default(),
+                scheduled_from_background: Default::default(),
+                forbid_parking: false,
+                block_on_ticks: 0..=1000,
+                now: std::time::Instant::now(),
+                next_timer_id: Default::default(),
+                pending_timers: Default::default(),
+                waiting_backtrace: None,
+                next_runnable_id: 0,
+                poll_history: Default::default(),
+                previous_poll_history: Default::default(),
+                enable_runnable_backtraces: false,
+                runnable_backtraces: Default::default(),
+            })),
+            parker: Default::default(),
+        })
+    }
+
+    pub fn execution_history(&self) -> Vec<ExecutorEvent> {
+        self.state.lock().poll_history.clone()
+    }
+
+    pub fn set_previous_execution_history(&self, history: Option<Vec<ExecutorEvent>>) {
+        self.state.lock().previous_poll_history = history;
+    }
+
+    pub fn enable_runnable_backtrace(&self) {
+        self.state.lock().enable_runnable_backtraces = true;
+    }
+
+    pub fn runnable_backtrace(&self, runnable_id: usize) -> backtrace::Backtrace {
+        let mut backtrace = self.state.lock().runnable_backtraces[&runnable_id].clone();
+        backtrace.resolve();
+        backtrace
+    }
+
+    pub fn build_background(self: &Arc<Self>) -> Arc<BackgroundExecutor> {
+        Arc::new(BackgroundExecutor::Deterministic {
+            executor: self.clone(),
+        })
+    }
+
+    pub fn build_foreground(self: &Arc<Self>, id: usize) -> Rc<ForegroundExecutor> {
+        Rc::new(ForegroundExecutor::Deterministic {
+            cx_id: id,
+            executor: self.clone(),
+        })
+    }
+
+    fn spawn_from_foreground(
+        &self,
+        cx_id: usize,
+        future: AnyLocalFuture,
+        main: bool,
+    ) -> AnyLocalTask {
+        let state = self.state.clone();
+        let id;
+        {
+            let mut state = state.lock();
+            id = util::post_inc(&mut state.next_runnable_id);
+            if state.enable_runnable_backtraces {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+
+        let unparker = self.parker.lock().unparker();
+        let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+            let mut state = state.lock();
+            state.push_to_history(ExecutorEvent::EnqueuRunnable { id });
+            state
+                .scheduled_from_foreground
+                .entry(cx_id)
+                .or_default()
+                .push(ForegroundRunnable { id, runnable, main });
+            unparker.unpark();
+        });
+        runnable.schedule();
+        task
+    }
+
+    fn spawn(&self, future: AnyFuture) -> AnyTask {
+        let state = self.state.clone();
+        let id;
+        {
+            let mut state = state.lock();
+            id = util::post_inc(&mut state.next_runnable_id);
+            if state.enable_runnable_backtraces {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+
+        let unparker = self.parker.lock().unparker();
+        let (runnable, task) = async_task::spawn(future, move |runnable| {
+            let mut state = state.lock();
+            state
+                .poll_history
+                .push(ExecutorEvent::EnqueuRunnable { id });
+            state
+                .scheduled_from_background
+                .push(BackgroundRunnable { id, runnable });
+            unparker.unpark();
+        });
+        runnable.schedule();
+        task
+    }
+
+    fn run<'a>(
+        &self,
+        cx_id: usize,
+        main_future: Pin<Box<dyn 'a + Future<Output = Box<dyn Any>>>>,
+    ) -> Box<dyn Any> {
+        use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
+
+        let woken = Arc::new(AtomicBool::new(false));
+
+        let state = self.state.clone();
+        let id;
+        {
+            let mut state = state.lock();
+            id = util::post_inc(&mut state.next_runnable_id);
+            if state.enable_runnable_backtraces {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+
+        let unparker = self.parker.lock().unparker();
+        let (runnable, mut main_task) = unsafe {
+            async_task::spawn_unchecked(main_future, move |runnable| {
+                let state = &mut *state.lock();
+                state
+                    .scheduled_from_foreground
+                    .entry(cx_id)
+                    .or_default()
+                    .push(ForegroundRunnable {
+                        id: util::post_inc(&mut state.next_runnable_id),
+                        runnable,
+                        main: true,
+                    });
+                unparker.unpark();
+            })
+        };
+        runnable.schedule();
+
+        loop {
+            if let Some(result) = self.run_internal(woken.clone(), Some(&mut main_task)) {
+                return result;
+            }
+
+            if !woken.load(SeqCst) {
+                self.state.lock().will_park();
+            }
+
+            woken.store(false, SeqCst);
+            self.parker.lock().park();
+        }
+    }
+
+    pub fn run_until_parked(&self) {
+        use std::sync::atomic::AtomicBool;
+        let woken = Arc::new(AtomicBool::new(false));
+        self.run_internal(woken, None);
+    }
+
+    fn run_internal(
+        &self,
+        woken: Arc<std::sync::atomic::AtomicBool>,
+        mut main_task: Option<&mut AnyLocalTask>,
+    ) -> Option<Box<dyn Any>> {
+        use rand::prelude::*;
+        use std::sync::atomic::Ordering::SeqCst;
+
+        let unparker = self.parker.lock().unparker();
+        let waker = waker_fn::waker_fn(move || {
+            woken.store(true, SeqCst);
+            unparker.unpark();
+        });
+
+        let mut cx = Context::from_waker(&waker);
+        loop {
+            let mut state = self.state.lock();
+
+            if state.scheduled_from_foreground.is_empty()
+                && state.scheduled_from_background.is_empty()
+            {
+                if let Some(main_task) = main_task {
+                    if let Poll::Ready(result) = main_task.poll(&mut cx) {
+                        return Some(result);
+                    }
+                }
+
+                return None;
+            }
+
+            if !state.scheduled_from_background.is_empty() && state.rng.gen() {
+                let background_len = state.scheduled_from_background.len();
+                let ix = state.rng.gen_range(0..background_len);
+                let background_runnable = state.scheduled_from_background.remove(ix);
+                state.push_to_history(ExecutorEvent::PollRunnable {
+                    id: background_runnable.id,
+                });
+                drop(state);
+                background_runnable.runnable.run();
+            } else if !state.scheduled_from_foreground.is_empty() {
+                let available_cx_ids = state
+                    .scheduled_from_foreground
+                    .keys()
+                    .copied()
+                    .collect::<Vec<_>>();
+                let cx_id_to_run = *available_cx_ids.iter().choose(&mut state.rng).unwrap();
+                let scheduled_from_cx = state
+                    .scheduled_from_foreground
+                    .get_mut(&cx_id_to_run)
+                    .unwrap();
+                let foreground_runnable = scheduled_from_cx.remove(0);
+                if scheduled_from_cx.is_empty() {
+                    state.scheduled_from_foreground.remove(&cx_id_to_run);
+                }
+                state.push_to_history(ExecutorEvent::PollRunnable {
+                    id: foreground_runnable.id,
+                });
+
+                drop(state);
+
+                foreground_runnable.runnable.run();
+                if let Some(main_task) = main_task.as_mut() {
+                    if foreground_runnable.main {
+                        if let Poll::Ready(result) = main_task.poll(&mut cx) {
+                            return Some(result);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fn block<F, T>(&self, future: &mut F, max_ticks: usize) -> Option<T>
+    where
+        F: Unpin + Future<Output = T>,
+    {
+        use rand::prelude::*;
+
+        let unparker = self.parker.lock().unparker();
+        let waker = waker_fn::waker_fn(move || {
+            unparker.unpark();
+        });
+
+        let mut cx = Context::from_waker(&waker);
+        for _ in 0..max_ticks {
+            let mut state = self.state.lock();
+            let runnable_count = state.scheduled_from_background.len();
+            let ix = state.rng.gen_range(0..=runnable_count);
+            if ix < state.scheduled_from_background.len() {
+                let background_runnable = state.scheduled_from_background.remove(ix);
+                state.push_to_history(ExecutorEvent::PollRunnable {
+                    id: background_runnable.id,
+                });
+                drop(state);
+                background_runnable.runnable.run();
+            } else {
+                drop(state);
+                if let Poll::Ready(result) = future.poll(&mut cx) {
+                    return Some(result);
+                }
+                let mut state = self.state.lock();
+                if state.scheduled_from_background.is_empty() {
+                    state.will_park();
+                    drop(state);
+                    self.parker.lock().park();
+                }
+
+                continue;
+            }
+        }
+
+        None
+    }
+
+    pub fn timer(&self, duration: Duration) -> Timer {
+        let (tx, rx) = postage::barrier::channel();
+        let mut state = self.state.lock();
+        let wakeup_at = state.now + duration;
+        let id = util::post_inc(&mut state.next_timer_id);
+        match state
+            .pending_timers
+            .binary_search_by_key(&wakeup_at, |e| e.1)
+        {
+            Ok(ix) | Err(ix) => state.pending_timers.insert(ix, (id, wakeup_at, tx)),
+        }
+        let state = self.state.clone();
+        Timer::Deterministic(DeterministicTimer { rx, id, state })
+    }
+
+    pub fn now(&self) -> std::time::Instant {
+        let state = self.state.lock();
+        state.now
+    }
+
+    pub fn advance_clock(&self, duration: Duration) {
+        let new_now = self.state.lock().now + duration;
+        loop {
+            self.run_until_parked();
+            let mut state = self.state.lock();
+
+            if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
+                let wakeup_time = *wakeup_time;
+                if wakeup_time <= new_now {
+                    let timer_count = state
+                        .pending_timers
+                        .iter()
+                        .take_while(|(_, t, _)| *t == wakeup_time)
+                        .count();
+                    state.now = wakeup_time;
+                    let timers_to_wake = state
+                        .pending_timers
+                        .drain(0..timer_count)
+                        .collect::<Vec<_>>();
+                    drop(state);
+                    drop(timers_to_wake);
+                    continue;
+                }
+            }
+
+            break;
+        }
+
+        self.state.lock().now = new_now;
+    }
+
+    pub fn start_waiting(&self) {
+        self.state.lock().waiting_backtrace = Some(backtrace::Backtrace::new_unresolved());
+    }
+
+    pub fn finish_waiting(&self) {
+        self.state.lock().waiting_backtrace.take();
+    }
+
+    pub fn forbid_parking(&self) {
+        use rand::prelude::*;
+
+        let mut state = self.state.lock();
+        state.forbid_parking = true;
+        state.rng = StdRng::seed_from_u64(state.seed);
+    }
+
+    pub fn allow_parking(&self) {
+        use rand::prelude::*;
+
+        let mut state = self.state.lock();
+        state.forbid_parking = false;
+        state.rng = StdRng::seed_from_u64(state.seed);
+    }
+
+    pub async fn simulate_random_delay(&self) {
+        use rand::prelude::*;
+        use smol::future::yield_now;
+        if self.state.lock().rng.gen_bool(0.2) {
+            let yields = self.state.lock().rng.gen_range(1..=10);
+            for _ in 0..yields {
+                yield_now().await;
+            }
+        }
+    }
+
+    pub fn record_backtrace(&self) {
+        let mut state = self.state.lock();
+        if state.enable_runnable_backtraces {
+            let current_id = state
+                .poll_history
+                .iter()
+                .rev()
+                .find_map(|event| match event {
+                    ExecutorEvent::PollRunnable { id } => Some(*id),
+                    _ => None,
+                });
+            if let Some(id) = current_id {
+                state
+                    .runnable_backtraces
+                    .insert(id, backtrace::Backtrace::new_unresolved());
+            }
+        }
+    }
+}
+
+impl Drop for Timer {
+    fn drop(&mut self) {
+        #[cfg(any(test, feature = "test"))]
+        if let Timer::Deterministic(DeterministicTimer { state, id, .. }) = self {
+            state
+                .lock()
+                .pending_timers
+                .retain(|(timer_id, _, _)| timer_id != id)
+        }
+    }
+}
+
+impl Future for Timer {
+    type Output = ();
+
+    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        match &mut *self {
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic(DeterministicTimer { rx, .. }) => {
+                use postage::stream::{PollRecv, Stream as _};
+                smol::pin!(rx);
+                match rx.poll_recv(&mut postage::Context::from_waker(cx.waker())) {
+                    PollRecv::Ready(()) | PollRecv::Closed => Poll::Ready(()),
+                    PollRecv::Pending => Poll::Pending,
+                }
+            }
+            Self::Production(timer) => {
+                smol::pin!(timer);
+                match timer.poll(cx) {
+                    Poll::Ready(_) => Poll::Ready(()),
+                    Poll::Pending => Poll::Pending,
+                }
+            }
+        }
+    }
+}
+
+#[cfg(any(test, feature = "test"))]
+impl DeterministicState {
+    fn push_to_history(&mut self, event: ExecutorEvent) {
+        use std::fmt::Write as _;
+
+        self.poll_history.push(event);
+        if let Some(prev_history) = &self.previous_poll_history {
+            let ix = self.poll_history.len() - 1;
+            let prev_event = prev_history[ix];
+            if event != prev_event {
+                let mut message = String::new();
+                writeln!(
+                    &mut message,
+                    "current runnable backtrace:\n{:?}",
+                    self.runnable_backtraces.get_mut(&event.id()).map(|trace| {
+                        trace.resolve();
+                        crate::util::CwdBacktrace(trace)
+                    })
+                )
+                .unwrap();
+                writeln!(
+                    &mut message,
+                    "previous runnable backtrace:\n{:?}",
+                    self.runnable_backtraces
+                        .get_mut(&prev_event.id())
+                        .map(|trace| {
+                            trace.resolve();
+                            util::CwdBacktrace(trace)
+                        })
+                )
+                .unwrap();
+                panic!("detected non-determinism after {ix}. {message}");
+            }
+        }
+    }
+
+    fn will_park(&mut self) {
+        if self.forbid_parking {
+            let mut backtrace_message = String::new();
+            #[cfg(any(test, feature = "test"))]
+            if let Some(backtrace) = self.waiting_backtrace.as_mut() {
+                backtrace.resolve();
+                backtrace_message = format!(
+                    "\nbacktrace of waiting future:\n{:?}",
+                    util::CwdBacktrace(backtrace)
+                );
+            }
+
+            panic!(
+                "deterministic executor parked after a call to forbid_parking{}",
+                backtrace_message
+            );
+        }
+    }
+}
+
+#[cfg(any(test, feature = "test"))]
+impl ExecutorEvent {
+    pub fn id(&self) -> usize {
+        match self {
+            ExecutorEvent::PollRunnable { id } => *id,
+            ExecutorEvent::EnqueuRunnable { id } => *id,
+        }
+    }
+}
+
+impl ForegroundExecutor {
+    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {
+        if dispatcher.is_main_thread() {
+            Ok(Self::Platform {
+                dispatcher,
+                _not_send_or_sync: PhantomData,
+            })
+        } else {
+            Err(anyhow!("must be constructed on main thread"))
+        }
+    }
+
+    pub fn spawn<T: 'static>(&self, future: impl Future<Output = T> + 'static) -> Task<T> {
+        let future = any_local_future(future);
+        let any_task = match self {
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic { cx_id, executor } => {
+                executor.spawn_from_foreground(*cx_id, future, false)
+            }
+            Self::Platform { dispatcher, .. } => {
+                fn spawn_inner(
+                    future: AnyLocalFuture,
+                    dispatcher: &Arc<dyn PlatformDispatcher>,
+                ) -> AnyLocalTask {
+                    let dispatcher = dispatcher.clone();
+                    let schedule =
+                        move |runnable: Runnable| dispatcher.run_on_main_thread(runnable);
+                    let (runnable, task) = async_task::spawn_local(future, schedule);
+                    runnable.schedule();
+                    task
+                }
+                spawn_inner(future, dispatcher)
+            }
+        };
+        Task::local(any_task)
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn run<T: 'static>(&self, future: impl Future<Output = T>) -> T {
+        let future = async move { Box::new(future.await) as Box<dyn Any> }.boxed_local();
+        let result = match self {
+            Self::Deterministic { cx_id, executor } => executor.run(*cx_id, future),
+            Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"),
+        };
+        *result.downcast().unwrap()
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn run_until_parked(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.run_until_parked(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn parking_forbidden(&self) -> bool {
+        match self {
+            Self::Deterministic { executor, .. } => executor.state.lock().forbid_parking,
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn start_waiting(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.start_waiting(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn finish_waiting(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.finish_waiting(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn forbid_parking(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.forbid_parking(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn allow_parking(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.allow_parking(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn advance_clock(&self, duration: Duration) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.advance_clock(duration),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.state.lock().block_on_ticks = range,
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+}
+
+impl BackgroundExecutor {
+    pub fn new() -> Self {
+        let executor = Arc::new(Executor::new());
+        let stop = channel::unbounded::<()>();
+
+        for i in 0..2 * num_cpus::get() {
+            let executor = executor.clone();
+            let stop = stop.1.clone();
+            thread::Builder::new()
+                .name(format!("background-executor-{}", i))
+                .spawn(move || smol::block_on(executor.run(stop.recv())))
+                .unwrap();
+        }
+
+        Self::Production {
+            executor,
+            _stop: stop.0,
+        }
+    }
+
+    pub fn num_cpus(&self) -> usize {
+        num_cpus::get()
+    }
+
+    pub fn spawn<T, F>(&self, future: F) -> Task<T>
+    where
+        T: 'static + Send,
+        F: Send + Future<Output = T> + 'static,
+    {
+        let future = any_future(future);
+        let any_task = match self {
+            Self::Production { executor, .. } => executor.spawn(future),
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic { executor } => executor.spawn(future),
+        };
+        Task::send(any_task)
+    }
+
+    pub fn block<F, T>(&self, future: F) -> T
+    where
+        F: Future<Output = T>,
+    {
+        smol::pin!(future);
+        match self {
+            Self::Production { .. } => smol::block_on(&mut future),
+            #[cfg(any(test, feature = "test"))]
+            Self::Deterministic { executor, .. } => {
+                executor.block(&mut future, usize::MAX).unwrap()
+            }
+        }
+    }
+
+    pub fn block_with_timeout<F, T>(
+        &self,
+        timeout: Duration,
+        future: F,
+    ) -> Result<T, impl Future<Output = T>>
+    where
+        T: 'static,
+        F: 'static + Unpin + Future<Output = T>,
+    {
+        let mut future = any_local_future(future);
+        if !timeout.is_zero() {
+            let output = match self {
+                Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(),
+                #[cfg(any(test, feature = "test"))]
+                Self::Deterministic { executor, .. } => {
+                    use rand::prelude::*;
+                    let max_ticks = {
+                        let mut state = executor.state.lock();
+                        let range = state.block_on_ticks.clone();
+                        state.rng.gen_range(range)
+                    };
+                    executor.block(&mut future, max_ticks)
+                }
+            };
+            if let Some(output) = output {
+                return Ok(*output.downcast().unwrap());
+            }
+        }
+        Err(async { *future.await.downcast().unwrap() })
+    }
+
+    pub async fn scoped<'scope, F>(self: &Arc<Self>, scheduler: F)
+    where
+        F: FnOnce(&mut Scope<'scope>),
+    {
+        let mut scope = Scope::new(self.clone());
+        (scheduler)(&mut scope);
+        let spawned = mem::take(&mut scope.futures)
+            .into_iter()
+            .map(|f| self.spawn(f))
+            .collect::<Vec<_>>();
+        for task in spawned {
+            task.await;
+        }
+    }
+
+    pub fn timer(&self, duration: Duration) -> Timer {
+        match self {
+            BackgroundExecutor::Production { .. } => {
+                Timer::Production(smol::Timer::after(duration))
+            }
+            #[cfg(any(test, feature = "test"))]
+            BackgroundExecutor::Deterministic { executor } => executor.timer(duration),
+        }
+    }
+
+    pub fn now(&self) -> std::time::Instant {
+        match self {
+            BackgroundExecutor::Production { .. } => std::time::Instant::now(),
+            #[cfg(any(test, feature = "test"))]
+            BackgroundExecutor::Deterministic { executor } => executor.now(),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn rng<'a>(&'a self) -> impl 'a + std::ops::DerefMut<Target = rand::prelude::StdRng> {
+        match self {
+            Self::Deterministic { executor, .. } => {
+                parking_lot::lock_api::MutexGuard::map(executor.state.lock(), |s| &mut s.rng)
+            }
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub async fn simulate_random_delay(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => {
+                executor.simulate_random_delay().await;
+            }
+            _ => {
+                panic!("this method can only be called on a deterministic executor")
+            }
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn record_backtrace(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.record_backtrace(),
+            _ => {
+                panic!("this method can only be called on a deterministic executor")
+            }
+        }
+    }
+
+    #[cfg(any(test, feature = "test"))]
+    pub fn start_waiting(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.start_waiting(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+}
+
+impl Default for BackgroundExecutor {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+pub struct Scope<'a> {
+    executor: Arc<BackgroundExecutor>,
+    futures: Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>,
+    tx: Option<mpsc::Sender<()>>,
+    rx: mpsc::Receiver<()>,
+    _phantom: PhantomData<&'a ()>,
+}
+
+impl<'a> Scope<'a> {
+    fn new(executor: Arc<BackgroundExecutor>) -> Self {
+        let (tx, rx) = mpsc::channel(1);
+        Self {
+            executor,
+            tx: Some(tx),
+            rx,
+            futures: Default::default(),
+            _phantom: PhantomData,
+        }
+    }
+
+    pub fn spawn<F>(&mut self, f: F)
+    where
+        F: Future<Output = ()> + Send + 'a,
+    {
+        let tx = self.tx.clone().unwrap();
+
+        // Safety: The 'a lifetime is guaranteed to outlive any of these futures because
+        // dropping this `Scope` blocks until all of the futures have resolved.
+        let f = unsafe {
+            mem::transmute::<
+                Pin<Box<dyn Future<Output = ()> + Send + 'a>>,
+                Pin<Box<dyn Future<Output = ()> + Send + 'static>>,
+            >(Box::pin(async move {
+                f.await;
+                drop(tx);
+            }))
+        };
+        self.futures.push(f);
+    }
+}
+
+impl<'a> Drop for Scope<'a> {
+    fn drop(&mut self) {
+        self.tx.take().unwrap();
+
+        // Wait until the channel is closed, which means that all of the spawned
+        // futures have resolved.
+        self.executor.block(self.rx.next());
+    }
+}
+
+impl<T> Task<T> {
+    pub fn ready(value: T) -> Self {
+        Self::Ready(Some(value))
+    }
+
+    fn local(any_task: AnyLocalTask) -> Self {
+        Self::Local {
+            any_task,
+            result_type: PhantomData,
+        }
+    }
+
+    pub fn detach(self) {
+        match self {
+            Task::Ready(_) => {}
+            Task::Local { any_task, .. } => any_task.detach(),
+            Task::Send { any_task, .. } => any_task.detach(),
+        }
+    }
+}
+
+// impl<T: 'static, E: 'static + Display> Task<Result<T, E>> {
+//     #[track_caller]
+//     pub fn detach_and_log_err(self, cx: &mut AppContext) {
+//         let caller = Location::caller();
+//         cx.spawn(|_| async move {
+//             if let Err(err) = self.await {
+//                 log::error!("{}:{}: {:#}", caller.file(), caller.line(), err);
+//             }
+//         })
+//         .detach();
+//     }
+// }
+
+impl<T: Send> Task<T> {
+    fn send(any_task: AnyTask) -> Self {
+        Self::Send {
+            any_task,
+            result_type: PhantomData,
+        }
+    }
+}
+
+impl<T: fmt::Debug> fmt::Debug for Task<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Task::Ready(value) => value.fmt(f),
+            Task::Local { any_task, .. } => any_task.fmt(f),
+            Task::Send { any_task, .. } => any_task.fmt(f),
+        }
+    }
+}
+
+impl<T: 'static> Future for Task<T> {
+    type Output = T;
+
+    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+        match unsafe { self.get_unchecked_mut() } {
+            Task::Ready(value) => Poll::Ready(value.take().unwrap()),
+            Task::Local { any_task, .. } => {
+                any_task.poll(cx).map(|value| *value.downcast().unwrap())
+            }
+            Task::Send { any_task, .. } => {
+                any_task.poll(cx).map(|value| *value.downcast().unwrap())
+            }
+        }
+    }
+}
+
+fn any_future<T, F>(future: F) -> AnyFuture
+where
+    T: 'static + Send,
+    F: Future<Output = T> + Send + 'static,
+{
+    async { Box::new(future.await) as Box<dyn Any + Send> }.boxed()
+}
+
+fn any_local_future<T, F>(future: F) -> AnyLocalFuture
+where
+    T: 'static,
+    F: Future<Output = T> + 'static,
+{
+    async { Box::new(future.await) as Box<dyn Any> }.boxed_local()
+}

crates/gpui3/src/geometry.rs 🔗

@@ -0,0 +1,723 @@
+use bytemuck::{Pod, Zeroable};
+use core::fmt::Debug;
+use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
+use refineable::Refineable;
+use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign};
+
+#[derive(Refineable, Default, Add, AddAssign, Sub, SubAssign, Copy, Debug, PartialEq, Eq, Hash)]
+#[refineable(debug)]
+#[repr(C)]
+pub struct Point<T: Clone + Debug> {
+    pub x: T,
+    pub y: T,
+}
+
+pub fn point<T: Clone + Debug>(x: T, y: T) -> Point<T> {
+    Point { x, y }
+}
+
+impl<T: Clone + Debug> Point<T> {
+    pub fn new(x: T, y: T) -> Self {
+        Self { x, y }
+    }
+
+    pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Point<U> {
+        Point {
+            x: f(self.x.clone()),
+            y: f(self.y.clone()),
+        }
+    }
+}
+
+impl<T, Rhs> Mul<Rhs> for Point<T>
+where
+    T: Mul<Rhs, Output = Rhs> + Clone + Debug,
+    Rhs: Clone + Debug,
+{
+    type Output = Point<Rhs>;
+
+    fn mul(self, rhs: Rhs) -> Self::Output {
+        Point {
+            x: self.x * rhs.clone(),
+            y: self.y * rhs,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Point<T> {
+    fn mul_assign(&mut self, rhs: S) {
+        self.x = self.x.clone() * rhs.clone();
+        self.y = self.y.clone() * rhs;
+    }
+}
+
+impl<T: Clone + Debug + Sub<Output = T>> SubAssign<Size<T>> for Point<T> {
+    fn sub_assign(&mut self, rhs: Size<T>) {
+        self.x = self.x.clone() - rhs.width;
+        self.y = self.y.clone() - rhs.height;
+    }
+}
+
+impl<T: Clone + Debug + Add<Output = T> + Copy> AddAssign<T> for Point<T> {
+    fn add_assign(&mut self, rhs: T) {
+        self.x = self.x.clone() + rhs;
+        self.y = self.y.clone() + rhs;
+    }
+}
+
+impl<T: Clone + Debug + Div<S, Output = T>, S: Clone> Div<S> for Point<T> {
+    type Output = Self;
+
+    fn div(self, rhs: S) -> Self::Output {
+        Self {
+            x: self.x / rhs.clone(),
+            y: self.y / rhs,
+        }
+    }
+}
+
+impl<T: Clone + Debug + std::cmp::PartialOrd> Point<T> {
+    pub fn max(&self, other: &Self) -> Self {
+        Point {
+            x: if self.x >= other.x {
+                self.x.clone()
+            } else {
+                other.x.clone()
+            },
+            y: if self.y >= other.y {
+                self.y.clone()
+            } else {
+                other.y.clone()
+            },
+        }
+    }
+}
+
+impl<T: Clone + Debug> Clone for Point<T> {
+    fn clone(&self) -> Self {
+        Self {
+            x: self.x.clone(),
+            y: self.y.clone(),
+        }
+    }
+}
+
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
+
+#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div)]
+#[refineable(debug)]
+#[repr(C)]
+pub struct Size<T: Clone + Debug> {
+    pub width: T,
+    pub height: T,
+}
+
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Size<T> {}
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Size<T> {}
+
+pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
+    Size { width, height }
+}
+
+impl<T: Clone + Debug> Size<T> {
+    pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Size<U> {
+        Size {
+            width: f(self.width.clone()),
+            height: f(self.height.clone()),
+        }
+    }
+}
+
+impl<T, Rhs> Mul<Rhs> for Size<T>
+where
+    T: Mul<Rhs, Output = Rhs> + Debug + Clone,
+    Rhs: Debug + Clone,
+{
+    type Output = Size<Rhs>;
+
+    fn mul(self, rhs: Rhs) -> Self::Output {
+        Size {
+            width: self.width * rhs.clone(),
+            height: self.height * rhs,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
+    fn mul_assign(&mut self, rhs: S) {
+        self.width = self.width.clone() * rhs.clone();
+        self.height = self.height.clone() * rhs;
+    }
+}
+
+impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
+    fn from(val: Size<Option<Pixels>>) -> Self {
+        Size {
+            width: val.width.map(|p| p.0 as f32),
+            height: val.height.map(|p| p.0 as f32),
+        }
+    }
+}
+
+impl Size<Length> {
+    pub fn full() -> Self {
+        Self {
+            width: relative(1.).into(),
+            height: relative(1.).into(),
+        }
+    }
+}
+
+impl Size<DefiniteLength> {
+    pub fn zero() -> Self {
+        Self {
+            width: px(0.).into(),
+            height: px(0.).into(),
+        }
+    }
+}
+
+impl Size<Length> {
+    pub fn auto() -> Self {
+        Self {
+            width: Length::Auto,
+            height: Length::Auto,
+        }
+    }
+}
+
+#[derive(Refineable, Clone, Default, Debug, PartialEq)]
+#[refineable(debug)]
+#[repr(C)]
+pub struct Bounds<T: Clone + Debug> {
+    pub origin: Point<T>,
+    pub size: Size<T>,
+}
+
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Bounds<T> {}
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Bounds<T> {}
+
+// Bounds<f32> * Pixels = Bounds<Pixels>
+impl<T, Rhs> Mul<Rhs> for Bounds<T>
+where
+    T: Mul<Rhs, Output = Rhs> + Clone + Debug,
+    Rhs: Clone + Debug,
+{
+    type Output = Bounds<Rhs>;
+
+    fn mul(self, rhs: Rhs) -> Self::Output {
+        Bounds {
+            origin: self.origin * rhs.clone(),
+            size: self.size * rhs,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Bounds<T> {
+    fn mul_assign(&mut self, rhs: S) {
+        self.origin *= rhs.clone();
+        self.size *= rhs;
+    }
+}
+
+impl<T: Clone + Debug + Div<S, Output = T>, S: Clone> Div<S> for Bounds<T>
+where
+    Size<T>: Div<S, Output = Size<T>>,
+{
+    type Output = Self;
+
+    fn div(self, rhs: S) -> Self {
+        Self {
+            origin: self.origin / rhs.clone(),
+            size: self.size / rhs,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Add<T, Output = T>> Bounds<T> {
+    pub fn upper_right(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.clone(),
+            y: self.origin.y.clone(),
+        }
+    }
+
+    pub fn lower_right(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone() + self.size.width.clone(),
+            y: self.origin.y.clone() + self.size.height.clone(),
+        }
+    }
+}
+
+impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
+    pub fn contains_point(&self, point: Point<T>) -> bool {
+        point.x >= self.origin.x
+            && point.x <= self.origin.x.clone() + self.size.width.clone()
+            && point.y >= self.origin.y
+            && point.y <= self.origin.y.clone() + self.size.height.clone()
+    }
+
+    pub fn map<U: Clone + Debug, F: Fn(T) -> U>(&self, f: F) -> Bounds<U> {
+        Bounds {
+            origin: self.origin.map(&f),
+            size: self.size.map(f),
+        }
+    }
+}
+
+impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
+
+#[derive(Refineable, Clone, Default, Debug)]
+#[refineable(debug)]
+#[repr(C)]
+pub struct Edges<T: Clone + Debug> {
+    pub top: T,
+    pub right: T,
+    pub bottom: T,
+    pub left: T,
+}
+
+impl<T: Clone + Debug + Mul<Output = T>> Mul for Edges<T> {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self {
+            top: self.top.clone() * rhs.top,
+            right: self.right.clone() * rhs.right,
+            bottom: self.bottom.clone() * rhs.bottom,
+            left: self.left.clone() * rhs.left,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Edges<T> {
+    fn mul_assign(&mut self, rhs: S) {
+        self.top = self.top.clone() * rhs.clone();
+        self.right = self.right.clone() * rhs.clone();
+        self.bottom = self.bottom.clone() * rhs.clone();
+        self.left = self.left.clone() * rhs.clone();
+    }
+}
+
+impl<T: Clone + Debug + Copy> Copy for Edges<T> {}
+
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Edges<T> {}
+
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Edges<T> {}
+
+impl<T: Clone + Debug> Edges<T> {
+    pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Edges<U> {
+        Edges {
+            top: f(&self.top),
+            right: f(&self.right),
+            bottom: f(&self.bottom),
+            left: f(&self.left),
+        }
+    }
+
+    pub fn any<F: Fn(&T) -> bool>(&self, predicate: F) -> bool {
+        predicate(&self.top)
+            || predicate(&self.right)
+            || predicate(&self.bottom)
+            || predicate(&self.left)
+    }
+}
+
+impl Edges<Length> {
+    pub fn auto() -> Self {
+        Self {
+            top: Length::Auto,
+            right: Length::Auto,
+            bottom: Length::Auto,
+            left: Length::Auto,
+        }
+    }
+
+    pub fn zero() -> Self {
+        Self {
+            top: px(0.).into(),
+            right: px(0.).into(),
+            bottom: px(0.).into(),
+            left: px(0.).into(),
+        }
+    }
+}
+
+impl Edges<DefiniteLength> {
+    pub fn zero() -> Self {
+        Self {
+            top: px(0.).into(),
+            right: px(0.).into(),
+            bottom: px(0.).into(),
+            left: px(0.).into(),
+        }
+    }
+}
+
+impl Edges<AbsoluteLength> {
+    pub fn zero() -> Self {
+        Self {
+            top: px(0.).into(),
+            right: px(0.).into(),
+            bottom: px(0.).into(),
+            left: px(0.).into(),
+        }
+    }
+
+    pub fn to_pixels(&self, rem_size: Pixels) -> Edges<Pixels> {
+        Edges {
+            top: self.top.to_pixels(rem_size),
+            right: self.right.to_pixels(rem_size),
+            bottom: self.bottom.to_pixels(rem_size),
+            left: self.left.to_pixels(rem_size),
+        }
+    }
+}
+
+#[derive(Refineable, Clone, Default, Debug)]
+#[refineable(debug)]
+#[repr(C)]
+pub struct Corners<T: Clone + Debug> {
+    pub top_left: T,
+    pub top_right: T,
+    pub bottom_right: T,
+    pub bottom_left: T,
+}
+
+impl<T: Clone + Debug> Corners<T> {
+    pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Corners<U> {
+        Corners {
+            top_left: f(&self.top_left),
+            top_right: f(&self.top_right),
+            bottom_right: f(&self.bottom_right),
+            bottom_left: f(&self.bottom_left),
+        }
+    }
+}
+
+impl<T: Clone + Debug + Mul<Output = T>> Mul for Corners<T> {
+    type Output = Self;
+
+    fn mul(self, rhs: Self) -> Self::Output {
+        Self {
+            top_left: self.top_left.clone() * rhs.top_left,
+            top_right: self.top_right.clone() * rhs.top_right,
+            bottom_right: self.bottom_right.clone() * rhs.bottom_right,
+            bottom_left: self.bottom_left.clone() * rhs.bottom_left,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Corners<T> {
+    fn mul_assign(&mut self, rhs: S) {
+        self.top_left = self.top_left.clone() * rhs.clone();
+        self.top_right = self.top_right.clone() * rhs.clone();
+        self.bottom_right = self.bottom_right.clone() * rhs.clone();
+        self.bottom_left = self.bottom_left.clone() * rhs;
+    }
+}
+
+impl<T: Clone + Debug + Copy> Copy for Corners<T> {}
+
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Corners<T> {}
+
+unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Corners<T> {}
+
+#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct Pixels(pub(crate) f32);
+
+impl Mul<f32> for Pixels {
+    type Output = Pixels;
+
+    fn mul(self, other: f32) -> Pixels {
+        Pixels(self.0 * other)
+    }
+}
+
+impl Mul<Pixels> for f32 {
+    type Output = Pixels;
+
+    fn mul(self, rhs: Pixels) -> Self::Output {
+        Pixels(self * rhs.0)
+    }
+}
+
+impl Pixels {
+    pub fn round(&self) -> Self {
+        Self(self.0.round())
+    }
+
+    pub fn to_device_pixels(&self, scale: f32) -> DevicePixels {
+        DevicePixels((self.0 * scale).ceil() as u32)
+    }
+}
+
+impl Mul<Pixels> for Pixels {
+    type Output = Pixels;
+
+    fn mul(self, rhs: Pixels) -> Self::Output {
+        Pixels(self.0 * rhs.0)
+    }
+}
+
+impl Eq for Pixels {}
+
+impl Ord for Pixels {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.0.partial_cmp(&other.0).unwrap()
+    }
+}
+
+impl std::hash::Hash for Pixels {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.0.to_bits().hash(state);
+    }
+}
+
+impl From<f64> for Pixels {
+    fn from(val: f64) -> Self {
+        Pixels(val as f32)
+    }
+}
+
+impl From<f32> for Pixels {
+    fn from(val: f32) -> Self {
+        Pixels(val)
+    }
+}
+
+unsafe impl bytemuck::Pod for Pixels {}
+unsafe impl bytemuck::Zeroable for Pixels {}
+
+impl Debug for Pixels {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{} px", self.0)
+    }
+}
+
+impl From<Pixels> for f32 {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0
+    }
+}
+
+impl From<&Pixels> for f32 {
+    fn from(pixels: &Pixels) -> Self {
+        pixels.0
+    }
+}
+
+impl From<Pixels> for f64 {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0 as f64
+    }
+}
+
+#[derive(
+    Clone, Copy, Debug, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd,
+)]
+#[repr(transparent)]
+pub struct DevicePixels(pub(crate) u32);
+
+unsafe impl bytemuck::Pod for DevicePixels {}
+unsafe impl bytemuck::Zeroable for DevicePixels {}
+
+impl From<DevicePixels> for u32 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0
+    }
+}
+
+impl From<u32> for DevicePixels {
+    fn from(val: u32) -> Self {
+        DevicePixels(val)
+    }
+}
+
+#[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
+pub struct Rems(f32);
+
+impl Mul<Pixels> for Rems {
+    type Output = Pixels;
+
+    fn mul(self, other: Pixels) -> Pixels {
+        Pixels(self.0 * other.0)
+    }
+}
+
+impl Debug for Rems {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{} rem", self.0)
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum AbsoluteLength {
+    Pixels(Pixels),
+    Rems(Rems),
+}
+
+impl AbsoluteLength {
+    pub fn is_zero(&self) -> bool {
+        match self {
+            AbsoluteLength::Pixels(px) => px.0 == 0.,
+            AbsoluteLength::Rems(rems) => rems.0 == 0.,
+        }
+    }
+}
+
+impl From<Pixels> for AbsoluteLength {
+    fn from(pixels: Pixels) -> Self {
+        AbsoluteLength::Pixels(pixels)
+    }
+}
+
+impl From<Rems> for AbsoluteLength {
+    fn from(rems: Rems) -> Self {
+        AbsoluteLength::Rems(rems)
+    }
+}
+
+impl AbsoluteLength {
+    pub fn to_pixels(&self, rem_size: Pixels) -> Pixels {
+        match self {
+            AbsoluteLength::Pixels(pixels) => *pixels,
+            AbsoluteLength::Rems(rems) => *rems * rem_size,
+        }
+    }
+}
+
+impl Default for AbsoluteLength {
+    fn default() -> Self {
+        px(0.).into()
+    }
+}
+
+/// A non-auto length that can be defined in pixels, rems, or percent of parent.
+#[derive(Clone, Copy)]
+pub enum DefiniteLength {
+    Absolute(AbsoluteLength),
+    /// A fraction of the parent's size between 0 and 1.
+    Fraction(f32),
+}
+
+impl DefiniteLength {
+    pub fn to_pixels(&self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
+        match self {
+            DefiniteLength::Absolute(size) => size.to_pixels(rem_size),
+            DefiniteLength::Fraction(fraction) => match base_size {
+                AbsoluteLength::Pixels(px) => px * *fraction,
+                AbsoluteLength::Rems(rems) => rems * rem_size * *fraction,
+            },
+        }
+    }
+}
+
+impl Debug for DefiniteLength {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            DefiniteLength::Absolute(length) => Debug::fmt(length, f),
+            DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
+        }
+    }
+}
+
+impl From<Pixels> for DefiniteLength {
+    fn from(pixels: Pixels) -> Self {
+        Self::Absolute(pixels.into())
+    }
+}
+
+impl From<Rems> for DefiniteLength {
+    fn from(rems: Rems) -> Self {
+        Self::Absolute(rems.into())
+    }
+}
+
+impl From<AbsoluteLength> for DefiniteLength {
+    fn from(length: AbsoluteLength) -> Self {
+        Self::Absolute(length)
+    }
+}
+
+impl Default for DefiniteLength {
+    fn default() -> Self {
+        Self::Absolute(AbsoluteLength::default())
+    }
+}
+
+/// A length that can be defined in pixels, rems, percent of parent, or auto.
+#[derive(Clone, Copy)]
+pub enum Length {
+    Definite(DefiniteLength),
+    Auto,
+}
+
+impl Debug for Length {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
+            Length::Auto => write!(f, "auto"),
+        }
+    }
+}
+
+pub fn relative(fraction: f32) -> DefiniteLength {
+    DefiniteLength::Fraction(fraction).into()
+}
+
+/// Returns the Golden Ratio, i.e. `~(1.0 + sqrt(5.0)) / 2.0`.
+pub fn phi() -> DefiniteLength {
+    relative(1.61803398875)
+}
+
+pub fn rems(rems: f32) -> Rems {
+    Rems(rems)
+}
+
+pub fn px(pixels: f32) -> Pixels {
+    Pixels(pixels)
+}
+
+pub fn auto() -> Length {
+    Length::Auto
+}
+
+impl From<Pixels> for Length {
+    fn from(pixels: Pixels) -> Self {
+        Self::Definite(pixels.into())
+    }
+}
+
+impl From<Rems> for Length {
+    fn from(rems: Rems) -> Self {
+        Self::Definite(rems.into())
+    }
+}
+
+impl From<DefiniteLength> for Length {
+    fn from(length: DefiniteLength) -> Self {
+        Self::Definite(length)
+    }
+}
+
+impl From<AbsoluteLength> for Length {
+    fn from(length: AbsoluteLength) -> Self {
+        Self::Definite(length.into())
+    }
+}
+
+impl Default for Length {
+    fn default() -> Self {
+        Self::Definite(DefiniteLength::default())
+    }
+}
+
+impl From<()> for Length {
+    fn from(_: ()) -> Self {
+        Self::Definite(DefiniteLength::default())
+    }
+}

crates/gpui3/src/gpui3.rs 🔗

@@ -0,0 +1,207 @@
+mod app;
+mod color;
+mod element;
+mod elements;
+mod executor;
+mod geometry;
+mod platform;
+mod scene;
+mod style;
+mod style_helpers;
+mod styled;
+mod taffy;
+mod text_system;
+mod util;
+mod view;
+mod window;
+
+pub use anyhow::Result;
+pub use app::*;
+pub use color::*;
+pub use element::*;
+pub use elements::*;
+pub use executor::*;
+pub use geometry::*;
+pub use gpui3_macros::*;
+pub use platform::*;
+pub use refineable::*;
+pub use scene::*;
+pub use serde;
+pub use serde_json;
+pub use smallvec;
+pub use smol::Timer;
+use std::{
+    ops::{Deref, DerefMut},
+    sync::Arc,
+};
+pub use style::*;
+pub use style_helpers::*;
+pub use styled::*;
+use taffy::TaffyLayoutEngine;
+pub use taffy::{AvailableSpace, LayoutId};
+pub use text_system::*;
+pub use util::arc_cow::ArcCow;
+pub use view::*;
+pub use window::*;
+
+pub trait Context {
+    type EntityContext<'a, 'w, T: 'static + Send + Sync>;
+    type Result<T>;
+
+    fn entity<T: Send + Sync + 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Self::Result<Handle<T>>;
+
+    fn update_entity<T: Send + Sync + 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> Self::Result<R>;
+}
+
+#[repr(transparent)]
+pub struct MainThread<T>(T);
+
+impl<T> MainThread<T> {
+    fn new(value: T) -> Self {
+        Self(value)
+    }
+}
+
+impl<T> Deref for MainThread<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<T> DerefMut for MainThread<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+pub trait StackContext {
+    fn app(&mut self) -> &mut AppContext;
+
+    fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.app().push_text_style(style);
+        let result = f(self);
+        self.app().pop_text_style();
+        result
+    }
+
+    fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.app().push_state(state);
+        let result = f(self);
+        self.app().pop_state::<T>();
+        result
+    }
+}
+
+pub trait Flatten<T> {
+    fn flatten(self) -> Result<T>;
+}
+
+impl<T> Flatten<T> for Result<Result<T>> {
+    fn flatten(self) -> Result<T> {
+        self?
+    }
+}
+
+impl<T> Flatten<T> for Result<T> {
+    fn flatten(self) -> Result<T> {
+        self
+    }
+}
+
+#[derive(Clone, Eq, PartialEq, Hash)]
+pub struct SharedString(ArcCow<'static, str>);
+
+impl Default for SharedString {
+    fn default() -> Self {
+        Self(ArcCow::Owned("".into()))
+    }
+}
+
+impl AsRef<str> for SharedString {
+    fn as_ref(&self) -> &str {
+        &self.0
+    }
+}
+
+impl std::fmt::Debug for SharedString {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
+    fn from(value: T) -> Self {
+        Self(value.into())
+    }
+}
+
+pub enum Reference<'a, T> {
+    Immutable(&'a T),
+    Mutable(&'a mut T),
+}
+
+impl<'a, T> Deref for Reference<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            Reference::Immutable(target) => target,
+            Reference::Mutable(target) => target,
+        }
+    }
+}
+
+impl<'a, T> DerefMut for Reference<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        match self {
+            Reference::Immutable(_) => {
+                panic!("cannot mutably deref an immutable reference. this is a bug in GPUI.");
+            }
+            Reference::Mutable(target) => target,
+        }
+    }
+}
+
+pub(crate) struct MainThreadOnly<T: ?Sized> {
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    value: Arc<T>,
+}
+
+impl<T: ?Sized> Clone for MainThreadOnly<T> {
+    fn clone(&self) -> Self {
+        Self {
+            dispatcher: self.dispatcher.clone(),
+            value: self.value.clone(),
+        }
+    }
+}
+
+/// Allows a value to be accessed only on the main thread, allowing a non-`Send` type
+/// to become `Send`.
+impl<T: 'static + ?Sized> MainThreadOnly<T> {
+    pub(crate) fn new(value: Arc<T>, dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+        Self { dispatcher, value }
+    }
+
+    pub(crate) fn borrow_on_main_thread(&self) -> &T {
+        assert!(self.dispatcher.is_main_thread());
+        &self.value
+    }
+}
+
+unsafe impl<T: ?Sized> Send for MainThreadOnly<T> {}

crates/gpui3/src/platform.rs 🔗

@@ -0,0 +1,399 @@
+mod events;
+mod keystroke;
+#[cfg(target_os = "macos")]
+mod mac;
+#[cfg(any(test, feature = "test"))]
+mod test;
+
+use crate::{
+    AnyWindowHandle, Bounds, Font, FontId, FontMetrics, GlyphId, LineLayout, Pixels, Point, Result,
+    Scene, SharedString, Size,
+};
+use anyhow::anyhow;
+use async_task::Runnable;
+use futures::channel::oneshot;
+use seahash::SeaHasher;
+use serde::{Deserialize, Serialize};
+use std::ffi::c_void;
+use std::hash::{Hash, Hasher};
+use std::{
+    any::Any,
+    fmt::{self, Debug, Display},
+    ops::Range,
+    path::{Path, PathBuf},
+    rc::Rc,
+    str::FromStr,
+    sync::Arc,
+};
+use uuid::Uuid;
+
+pub use events::*;
+pub use keystroke::*;
+#[cfg(target_os = "macos")]
+pub use mac::*;
+#[cfg(any(test, feature = "test"))]
+pub use test::*;
+pub use time::UtcOffset;
+
+#[cfg(target_os = "macos")]
+pub(crate) fn current_platform() -> Arc<dyn Platform> {
+    Arc::new(MacPlatform::new())
+}
+
+pub trait Platform: 'static {
+    fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;
+    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
+
+    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
+    fn quit(&self);
+    fn restart(&self);
+    fn activate(&self, ignoring_other_apps: bool);
+    fn hide(&self);
+    fn hide_other_apps(&self);
+    fn unhide_other_apps(&self);
+
+    fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
+    fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>;
+    fn main_window(&self) -> Option<AnyWindowHandle>;
+    fn open_window(
+        &self,
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+    ) -> Box<dyn PlatformWindow>;
+    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
+
+    fn open_url(&self, url: &str);
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
+    fn prompt_for_paths(
+        &self,
+        options: PathPromptOptions,
+    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
+    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
+    fn reveal_path(&self, path: &Path);
+
+    fn on_become_active(&self, callback: Box<dyn FnMut()>);
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
+    fn on_quit(&self, callback: Box<dyn FnMut()>);
+    fn on_reopen(&self, callback: Box<dyn FnMut()>);
+    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
+
+    fn os_name(&self) -> &'static str;
+    fn os_version(&self) -> Result<SemanticVersion>;
+    fn app_version(&self) -> Result<SemanticVersion>;
+    fn app_path(&self) -> Result<PathBuf>;
+    fn local_timezone(&self) -> UtcOffset;
+    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
+
+    fn set_cursor_style(&self, style: CursorStyle);
+    fn should_auto_hide_scrollbars(&self) -> bool;
+
+    fn write_to_clipboard(&self, item: ClipboardItem);
+    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
+
+    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
+    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
+    fn delete_credentials(&self, url: &str) -> Result<()>;
+}
+
+pub trait PlatformScreen: Debug {
+    fn id(&self) -> Option<ScreenId>;
+    fn handle(&self) -> PlatformScreenHandle;
+    fn as_any(&self) -> &dyn Any;
+    fn bounds(&self) -> Bounds<Pixels>;
+    fn content_bounds(&self) -> Bounds<Pixels>;
+}
+
+pub struct PlatformScreenHandle(pub *mut c_void);
+
+impl Debug for PlatformScreenHandle {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "PlatformScreenHandle({:p})", self.0)
+    }
+}
+
+unsafe impl Send for PlatformScreenHandle {}
+
+pub trait PlatformWindow {
+    fn bounds(&self) -> WindowBounds;
+    fn content_size(&self) -> Size<Pixels>;
+    fn scale_factor(&self) -> f32;
+    fn titlebar_height(&self) -> Pixels;
+    fn appearance(&self) -> WindowAppearance;
+    fn screen(&self) -> Rc<dyn PlatformScreen>;
+    fn mouse_position(&self) -> Point<Pixels>;
+    fn as_any_mut(&mut self) -> &mut dyn Any;
+    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
+    fn prompt(
+        &self,
+        level: WindowPromptLevel,
+        msg: &str,
+        answers: &[&str],
+    ) -> oneshot::Receiver<usize>;
+    fn activate(&self);
+    fn set_title(&mut self, title: &str);
+    fn set_edited(&mut self, edited: bool);
+    fn show_character_palette(&self);
+    fn minimize(&self);
+    fn zoom(&self);
+    fn toggle_full_screen(&self);
+    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
+    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
+    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
+    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
+    fn on_moved(&self, callback: Box<dyn FnMut()>);
+    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
+    fn on_close(&self, callback: Box<dyn FnOnce()>);
+    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
+    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
+    fn draw(&self, scene: Scene);
+}
+
+pub trait PlatformDispatcher: Send + Sync {
+    fn is_main_thread(&self) -> bool;
+    fn run_on_main_thread(&self, task: Runnable);
+}
+
+pub trait PlatformTextSystem: Send + Sync {
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
+    fn all_font_families(&self) -> Vec<String>;
+    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
+    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
+    fn rasterize_glyph(
+        &self,
+        font_id: FontId,
+        font_size: f32,
+        glyph_id: GlyphId,
+        subpixel_shift: Point<Pixels>,
+        scale_factor: f32,
+        options: RasterizationOptions,
+    ) -> Option<(Bounds<u32>, Vec<u8>)>;
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize>;
+}
+
+pub trait InputHandler {
+    fn selected_text_range(&self) -> Option<Range<usize>>;
+    fn marked_text_range(&self) -> Option<Range<usize>>;
+    fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
+    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
+    fn replace_and_mark_text_in_range(
+        &mut self,
+        range_utf16: Option<Range<usize>>,
+        new_text: &str,
+        new_selected_range: Option<Range<usize>>,
+    );
+    fn unmark_text(&mut self);
+    fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub struct ScreenId(pub(crate) Uuid);
+
+#[derive(Copy, Clone, Debug)]
+pub enum RasterizationOptions {
+    Alpha,
+    Bgra,
+}
+
+#[derive(Debug)]
+pub struct WindowOptions {
+    pub bounds: WindowBounds,
+    pub titlebar: Option<TitlebarOptions>,
+    pub center: bool,
+    pub focus: bool,
+    pub show: bool,
+    pub kind: WindowKind,
+    pub is_movable: bool,
+    pub screen: Option<PlatformScreenHandle>,
+}
+
+impl Default for WindowOptions {
+    fn default() -> Self {
+        Self {
+            bounds: WindowBounds::default(),
+            titlebar: Some(TitlebarOptions {
+                title: Default::default(),
+                appears_transparent: Default::default(),
+                traffic_light_position: Default::default(),
+            }),
+            center: false,
+            focus: true,
+            show: true,
+            kind: WindowKind::Normal,
+            is_movable: true,
+            screen: None,
+        }
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct TitlebarOptions {
+    pub title: Option<SharedString>,
+    pub appears_transparent: bool,
+    pub traffic_light_position: Option<Point<Pixels>>,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum Appearance {
+    Light,
+    VibrantLight,
+    Dark,
+    VibrantDark,
+}
+
+impl Default for Appearance {
+    fn default() -> Self {
+        Self::Light
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum WindowKind {
+    Normal,
+    PopUp,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Default)]
+pub enum WindowBounds {
+    Fullscreen,
+    #[default]
+    Maximized,
+    Fixed(Bounds<Pixels>),
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum WindowAppearance {
+    Light,
+    VibrantLight,
+    Dark,
+    VibrantDark,
+}
+
+impl Default for WindowAppearance {
+    fn default() -> Self {
+        Self::Light
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Default)]
+pub enum WindowPromptLevel {
+    #[default]
+    Info,
+    Warning,
+    Critical,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct PathPromptOptions {
+    pub files: bool,
+    pub directories: bool,
+    pub multiple: bool,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum PromptLevel {
+    Info,
+    Warning,
+    Critical,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum CursorStyle {
+    Arrow,
+    ResizeLeftRight,
+    ResizeUpDown,
+    PointingHand,
+    IBeam,
+}
+
+impl Default for CursorStyle {
+    fn default() -> Self {
+        Self::Arrow
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct SemanticVersion {
+    major: usize,
+    minor: usize,
+    patch: usize,
+}
+
+impl FromStr for SemanticVersion {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        let mut components = s.trim().split('.');
+        let major = components
+            .next()
+            .ok_or_else(|| anyhow!("missing major version number"))?
+            .parse()?;
+        let minor = components
+            .next()
+            .ok_or_else(|| anyhow!("missing minor version number"))?
+            .parse()?;
+        let patch = components
+            .next()
+            .ok_or_else(|| anyhow!("missing patch version number"))?
+            .parse()?;
+        Ok(Self {
+            major,
+            minor,
+            patch,
+        })
+    }
+}
+
+impl Display for SemanticVersion {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ClipboardItem {
+    pub(crate) text: String,
+    pub(crate) metadata: Option<String>,
+}
+
+impl ClipboardItem {
+    pub fn new(text: String) -> Self {
+        Self {
+            text,
+            metadata: None,
+        }
+    }
+
+    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
+        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
+        self
+    }
+
+    pub fn text(&self) -> &String {
+        &self.text
+    }
+
+    pub fn metadata<T>(&self) -> Option<T>
+    where
+        T: for<'a> Deserialize<'a>,
+    {
+        self.metadata
+            .as_ref()
+            .and_then(|m| serde_json::from_str(m).ok())
+    }
+
+    pub(crate) fn text_hash(text: &str) -> u64 {
+        let mut hasher = SeaHasher::new();
+        text.hash(&mut hasher);
+        hasher.finish()
+    }
+}

crates/gpui3/src/platform/events.rs 🔗

@@ -0,0 +1,204 @@
+use crate::{point, Keystroke, Modifiers, Pixels, Point};
+use std::{any::Any, ops::Deref};
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct KeyDownEvent {
+    pub keystroke: Keystroke,
+    pub is_held: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct KeyUpEvent {
+    pub keystroke: Keystroke,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ModifiersChangedEvent {
+    pub modifiers: Modifiers,
+}
+
+impl Deref for ModifiersChangedEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
+/// The phase of a touch motion event.
+/// Based on the winit enum of the same name,
+#[derive(Clone, Copy, Debug)]
+pub enum TouchPhase {
+    Started,
+    Moved,
+    Ended,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum ScrollDelta {
+    Pixels(Point<Pixels>),
+    Lines(Point<f32>),
+}
+
+impl Default for ScrollDelta {
+    fn default() -> Self {
+        Self::Lines(Default::default())
+    }
+}
+
+impl ScrollDelta {
+    pub fn precise(&self) -> bool {
+        match self {
+            ScrollDelta::Pixels(_) => true,
+            ScrollDelta::Lines(_) => false,
+        }
+    }
+
+    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
+        match self {
+            ScrollDelta::Pixels(delta) => *delta,
+            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ScrollWheelEvent {
+    pub position: Point<Pixels>,
+    pub delta: ScrollDelta,
+    pub modifiers: Modifiers,
+    /// If the platform supports returning the phase of a scroll wheel event, it will be stored here
+    pub phase: Option<TouchPhase>,
+}
+
+impl Deref for ScrollWheelEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
+#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
+pub enum NavigationDirection {
+    Back,
+    Forward,
+}
+
+impl Default for NavigationDirection {
+    fn default() -> Self {
+        Self::Back
+    }
+}
+
+#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
+pub enum MouseButton {
+    Left,
+    Right,
+    Middle,
+    Navigate(NavigationDirection),
+}
+
+impl MouseButton {
+    pub fn all() -> Vec<Self> {
+        vec![
+            MouseButton::Left,
+            MouseButton::Right,
+            MouseButton::Middle,
+            MouseButton::Navigate(NavigationDirection::Back),
+            MouseButton::Navigate(NavigationDirection::Forward),
+        ]
+    }
+}
+
+impl Default for MouseButton {
+    fn default() -> Self {
+        Self::Left
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseDownEvent {
+    pub button: MouseButton,
+    pub position: Point<Pixels>,
+    pub modifiers: Modifiers,
+    pub click_count: usize,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseUpEvent {
+    pub button: MouseButton,
+    pub position: Point<Pixels>,
+    pub modifiers: Modifiers,
+    pub click_count: usize,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseUp {
+    pub button: MouseButton,
+    pub position: Point<Pixels>,
+    pub modifiers: Modifiers,
+    pub click_count: usize,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseMovedEvent {
+    pub position: Point<Pixels>,
+    pub pressed_button: Option<MouseButton>,
+    pub modifiers: Modifiers,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct MouseExitedEvent {
+    pub position: Point<Pixels>,
+    pub pressed_button: Option<MouseButton>,
+    pub modifiers: Modifiers,
+}
+
+impl Deref for MouseExitedEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum Event {
+    KeyDown(KeyDownEvent),
+    KeyUp(KeyUpEvent),
+    ModifiersChanged(ModifiersChangedEvent),
+    MouseDown(MouseDownEvent),
+    MouseUp(MouseUpEvent),
+    MouseMoved(MouseMovedEvent),
+    MouseExited(MouseExitedEvent),
+    ScrollWheel(ScrollWheelEvent),
+}
+
+impl Event {
+    pub fn position(&self) -> Option<Point<Pixels>> {
+        match self {
+            Event::KeyDown { .. } => None,
+            Event::KeyUp { .. } => None,
+            Event::ModifiersChanged { .. } => None,
+            Event::MouseDown(event) => Some(event.position),
+            Event::MouseUp(event) => Some(event.position),
+            Event::MouseMoved(event) => Some(event.position),
+            Event::MouseExited(event) => Some(event.position),
+            Event::ScrollWheel(event) => Some(event.position),
+        }
+    }
+
+    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
+        match self {
+            Event::KeyDown { .. } => None,
+            Event::KeyUp { .. } => None,
+            Event::ModifiersChanged { .. } => None,
+            Event::MouseDown(event) => Some(event),
+            Event::MouseUp(event) => Some(event),
+            Event::MouseMoved(event) => Some(event),
+            Event::MouseExited(event) => Some(event),
+            Event::ScrollWheel(event) => Some(event),
+        }
+    }
+}

crates/gpui3/src/platform/keystroke.rs 🔗

@@ -0,0 +1,121 @@
+use anyhow::anyhow;
+use serde::Deserialize;
+use std::fmt::Write;
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
+pub struct Modifiers {
+    pub control: bool,
+    pub alt: bool,
+    pub shift: bool,
+    pub command: bool,
+    pub function: bool,
+}
+
+impl Modifiers {
+    pub fn modified(&self) -> bool {
+        self.control || self.alt || self.shift || self.command || self.function
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Hash)]
+pub struct Keystroke {
+    pub key: String,
+    pub modifiers: Modifiers,
+}
+
+impl Keystroke {
+    pub fn parse(source: &str) -> anyhow::Result<Self> {
+        let mut control = false;
+        let mut alt = false;
+        let mut shift = false;
+        let mut command = false;
+        let mut function = false;
+        let mut key = None;
+
+        let mut components = source.split('-').peekable();
+        while let Some(component) = components.next() {
+            match component {
+                "ctrl" => control = true,
+                "alt" => alt = true,
+                "shift" => shift = true,
+                "cmd" => command = true,
+                "fn" => function = true,
+                _ => {
+                    if let Some(component) = components.peek() {
+                        if component.is_empty() && source.ends_with('-') {
+                            key = Some(String::from("-"));
+                            break;
+                        } else {
+                            return Err(anyhow!("Invalid keystroke `{}`", source));
+                        }
+                    } else {
+                        key = Some(String::from(component));
+                    }
+                }
+            }
+        }
+
+        let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
+
+        Ok(Keystroke {
+            modifiers: Modifiers {
+                control,
+                alt,
+                shift,
+                command,
+                function,
+            },
+            key,
+        })
+    }
+
+    pub fn modified(&self) -> bool {
+        self.modifiers.modified()
+    }
+}
+
+impl std::fmt::Display for Keystroke {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let Modifiers {
+            control,
+            alt,
+            shift,
+            command,
+            function,
+        } = self.modifiers;
+
+        if control {
+            f.write_char('^')?;
+        }
+        if alt {
+            f.write_char('⎇')?;
+        }
+        if command {
+            f.write_char('⌘')?;
+        }
+        if shift {
+            f.write_char('⇧')?;
+        }
+        if function {
+            f.write_char('𝙛')?;
+        }
+
+        let key = match self.key.as_str() {
+            "backspace" => '⌫',
+            "up" => '↑',
+            "down" => '↓',
+            "left" => '←',
+            "right" => '→',
+            "tab" => '⇥',
+            "escape" => '⎋',
+            key => {
+                if key.len() == 1 {
+                    key.chars().next().unwrap().to_ascii_uppercase()
+                } else {
+                    return f.write_str(key);
+                }
+            }
+        };
+        f.write_char(key)
+    }
+}

crates/gpui3/src/platform/mac.rs 🔗

@@ -0,0 +1,151 @@
+///! Macos screen have a y axis that goings up from the bottom of the screen and
+///! an origin at the bottom left of the main display.
+mod dispatcher;
+mod events;
+mod metal_renderer;
+mod open_type;
+mod platform;
+mod screen;
+mod text_system;
+mod window;
+mod window_appearence;
+
+use crate::{px, size, Pixels, Size};
+use anyhow::anyhow;
+use cocoa::{
+    base::{id, nil},
+    foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger, NSURL},
+};
+use metal_renderer::*;
+use objc::{
+    msg_send,
+    runtime::{BOOL, NO, YES},
+    sel, sel_impl,
+};
+use std::{
+    ffi::{c_char, CStr, OsStr},
+    ops::Range,
+    os::unix::prelude::OsStrExt,
+    path::PathBuf,
+};
+
+pub use dispatcher::*;
+pub use platform::*;
+pub use screen::*;
+pub use text_system::*;
+pub use window::*;
+
+trait BoolExt {
+    fn to_objc(self) -> BOOL;
+}
+
+impl BoolExt for bool {
+    fn to_objc(self) -> BOOL {
+        if self {
+            YES
+        } else {
+            NO
+        }
+    }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
+struct NSRange {
+    pub location: NSUInteger,
+    pub length: NSUInteger,
+}
+
+impl NSRange {
+    fn invalid() -> Self {
+        Self {
+            location: NSNotFound as NSUInteger,
+            length: 0,
+        }
+    }
+
+    fn is_valid(&self) -> bool {
+        self.location != NSNotFound as NSUInteger
+    }
+
+    fn to_range(self) -> Option<Range<usize>> {
+        if self.is_valid() {
+            let start = self.location as usize;
+            let end = start + self.length as usize;
+            Some(start..end)
+        } else {
+            None
+        }
+    }
+}
+
+impl From<Range<usize>> for NSRange {
+    fn from(range: Range<usize>) -> Self {
+        NSRange {
+            location: range.start as NSUInteger,
+            length: range.len() as NSUInteger,
+        }
+    }
+}
+
+unsafe impl objc::Encode for NSRange {
+    fn encode() -> objc::Encoding {
+        let encoding = format!(
+            "{{NSRange={}{}}}",
+            NSUInteger::encode().as_str(),
+            NSUInteger::encode().as_str()
+        );
+        unsafe { objc::Encoding::from_str(&encoding) }
+    }
+}
+
+unsafe fn ns_string(string: &str) -> id {
+    NSString::alloc(nil).init_str(string).autorelease()
+}
+
+impl From<NSSize> for Size<Pixels> {
+    fn from(value: NSSize) -> Self {
+        Size {
+            width: px(value.width as f32),
+            height: px(value.height as f32),
+        }
+    }
+}
+
+pub trait NSRectExt {
+    fn size(&self) -> Size<Pixels>;
+    fn intersects(&self, other: Self) -> bool;
+}
+
+impl NSRectExt for NSRect {
+    fn size(&self) -> Size<Pixels> {
+        size(px(self.size.width as f32), px(self.size.height as f32))
+    }
+
+    fn intersects(&self, other: Self) -> bool {
+        self.size.width > 0.
+            && self.size.height > 0.
+            && other.size.width > 0.
+            && other.size.height > 0.
+            && self.origin.x <= other.origin.x + other.size.width
+            && self.origin.x + self.size.width >= other.origin.x
+            && self.origin.y <= other.origin.y + other.size.height
+            && self.origin.y + self.size.height >= other.origin.y
+    }
+}
+
+// todo!
+#[allow(unused)]
+unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
+    let path: *mut c_char = msg_send![url, fileSystemRepresentation];
+    if path.is_null() {
+        Err(anyhow!(
+            "url is not a file path: {}",
+            CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
+        ))
+    } else {
+        Ok(PathBuf::from(OsStr::from_bytes(
+            CStr::from_ptr(path).to_bytes(),
+        )))
+    }
+}

crates/gpui3/src/platform/mac/dispatcher.rs 🔗

@@ -0,0 +1,42 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use crate::PlatformDispatcher;
+use async_task::Runnable;
+use objc::{
+    class, msg_send,
+    runtime::{BOOL, YES},
+    sel, sel_impl,
+};
+use std::ffi::c_void;
+
+include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
+
+pub fn dispatch_get_main_queue() -> dispatch_queue_t {
+    unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
+}
+
+pub struct MacDispatcher;
+
+impl PlatformDispatcher for MacDispatcher {
+    fn is_main_thread(&self) -> bool {
+        let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
+        is_main_thread == YES
+    }
+
+    fn run_on_main_thread(&self, runnable: Runnable) {
+        unsafe {
+            dispatch_async_f(
+                dispatch_get_main_queue(),
+                runnable.into_raw() as *mut c_void,
+                Some(trampoline),
+            );
+        }
+
+        extern "C" fn trampoline(runnable: *mut c_void) {
+            let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
+            task.run();
+        }
+    }
+}

crates/gpui3/src/platform/mac/events.rs 🔗

@@ -0,0 +1,356 @@
+use crate::{
+    point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
+    MouseButton, MouseDownEvent, MouseExitedEvent, MouseMovedEvent, MouseUpEvent,
+    NavigationDirection, Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
+};
+use cocoa::{
+    appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
+    base::{id, YES},
+    foundation::NSString as _,
+};
+use core_graphics::{
+    event::{CGEvent, CGEventFlags, CGKeyCode},
+    event_source::{CGEventSource, CGEventSourceStateID},
+};
+use ctor::ctor;
+use foreign_types::ForeignType;
+use objc::{class, msg_send, sel, sel_impl};
+use std::{borrow::Cow, ffi::CStr, mem, os::raw::c_char, ptr};
+
+const BACKSPACE_KEY: u16 = 0x7f;
+const SPACE_KEY: u16 = b' ' as u16;
+const ENTER_KEY: u16 = 0x0d;
+const NUMPAD_ENTER_KEY: u16 = 0x03;
+const ESCAPE_KEY: u16 = 0x1b;
+const TAB_KEY: u16 = 0x09;
+const SHIFT_TAB_KEY: u16 = 0x19;
+
+static mut EVENT_SOURCE: core_graphics::sys::CGEventSourceRef = ptr::null_mut();
+
+#[ctor]
+unsafe fn build_event_source() {
+    let source = CGEventSource::new(CGEventSourceStateID::Private).unwrap();
+    EVENT_SOURCE = source.as_ptr();
+    mem::forget(source);
+}
+
+// todo!
+#[allow(unused)]
+pub fn key_to_native(key: &str) -> Cow<str> {
+    use cocoa::appkit::*;
+    let code = match key {
+        "space" => SPACE_KEY,
+        "backspace" => BACKSPACE_KEY,
+        "up" => NSUpArrowFunctionKey,
+        "down" => NSDownArrowFunctionKey,
+        "left" => NSLeftArrowFunctionKey,
+        "right" => NSRightArrowFunctionKey,
+        "pageup" => NSPageUpFunctionKey,
+        "pagedown" => NSPageDownFunctionKey,
+        "home" => NSHomeFunctionKey,
+        "end" => NSEndFunctionKey,
+        "delete" => NSDeleteFunctionKey,
+        "f1" => NSF1FunctionKey,
+        "f2" => NSF2FunctionKey,
+        "f3" => NSF3FunctionKey,
+        "f4" => NSF4FunctionKey,
+        "f5" => NSF5FunctionKey,
+        "f6" => NSF6FunctionKey,
+        "f7" => NSF7FunctionKey,
+        "f8" => NSF8FunctionKey,
+        "f9" => NSF9FunctionKey,
+        "f10" => NSF10FunctionKey,
+        "f11" => NSF11FunctionKey,
+        "f12" => NSF12FunctionKey,
+        _ => return Cow::Borrowed(key),
+    };
+    Cow::Owned(String::from_utf16(&[code]).unwrap())
+}
+
+unsafe fn read_modifiers(native_event: id) -> Modifiers {
+    let modifiers = native_event.modifierFlags();
+    let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
+    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
+    let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
+    let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
+    let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
+
+    Modifiers {
+        control,
+        alt,
+        shift,
+        command,
+        function,
+    }
+}
+
+impl Event {
+    pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
+        let event_type = native_event.eventType();
+
+        // Filter out event types that aren't in the NSEventType enum.
+        // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
+        match event_type as u64 {
+            0 | 21 | 32 | 33 | 35 | 36 | 37 => {
+                return None;
+            }
+            _ => {}
+        }
+
+        match event_type {
+            NSEventType::NSFlagsChanged => Some(Self::ModifiersChanged(ModifiersChangedEvent {
+                modifiers: read_modifiers(native_event),
+            })),
+            NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
+                keystroke: parse_keystroke(native_event),
+                is_held: native_event.isARepeat() == YES,
+            })),
+            NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
+                keystroke: parse_keystroke(native_event),
+            })),
+            NSEventType::NSLeftMouseDown
+            | NSEventType::NSRightMouseDown
+            | NSEventType::NSOtherMouseDown => {
+                let button = match native_event.buttonNumber() {
+                    0 => MouseButton::Left,
+                    1 => MouseButton::Right,
+                    2 => MouseButton::Middle,
+                    3 => MouseButton::Navigate(NavigationDirection::Back),
+                    4 => MouseButton::Navigate(NavigationDirection::Forward),
+                    // Other mouse buttons aren't tracked currently
+                    _ => return None,
+                };
+                window_height.map(|window_height| {
+                    Self::MouseDown(MouseDownEvent {
+                        button,
+                        position: point(
+                            px(native_event.locationInWindow().x as f32),
+                            // MacOS screen coordinates are relative to bottom left
+                            window_height - px(native_event.locationInWindow().y as f32),
+                        ),
+                        modifiers: read_modifiers(native_event),
+                        click_count: native_event.clickCount() as usize,
+                    })
+                })
+            }
+            NSEventType::NSLeftMouseUp
+            | NSEventType::NSRightMouseUp
+            | NSEventType::NSOtherMouseUp => {
+                let button = match native_event.buttonNumber() {
+                    0 => MouseButton::Left,
+                    1 => MouseButton::Right,
+                    2 => MouseButton::Middle,
+                    3 => MouseButton::Navigate(NavigationDirection::Back),
+                    4 => MouseButton::Navigate(NavigationDirection::Forward),
+                    // Other mouse buttons aren't tracked currently
+                    _ => return None,
+                };
+
+                window_height.map(|window_height| {
+                    Self::MouseUp(MouseUpEvent {
+                        button,
+                        position: point(
+                            px(native_event.locationInWindow().x as f32),
+                            window_height - px(native_event.locationInWindow().y as f32),
+                        ),
+                        modifiers: read_modifiers(native_event),
+                        click_count: native_event.clickCount() as usize,
+                    })
+                })
+            }
+            NSEventType::NSScrollWheel => window_height.map(|window_height| {
+                let phase = match native_event.phase() {
+                    NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
+                        Some(TouchPhase::Started)
+                    }
+                    NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
+                    _ => Some(TouchPhase::Moved),
+                };
+
+                let raw_data = point(
+                    native_event.scrollingDeltaX() as f32,
+                    native_event.scrollingDeltaY() as f32,
+                );
+
+                let delta = if native_event.hasPreciseScrollingDeltas() == YES {
+                    ScrollDelta::Pixels(raw_data.map(px))
+                } else {
+                    ScrollDelta::Lines(raw_data)
+                };
+
+                Self::ScrollWheel(ScrollWheelEvent {
+                    position: point(
+                        px(native_event.locationInWindow().x as f32),
+                        window_height - px(native_event.locationInWindow().y as f32),
+                    ),
+                    delta,
+                    phase,
+                    modifiers: read_modifiers(native_event),
+                })
+            }),
+            NSEventType::NSLeftMouseDragged
+            | NSEventType::NSRightMouseDragged
+            | NSEventType::NSOtherMouseDragged => {
+                let pressed_button = match native_event.buttonNumber() {
+                    0 => MouseButton::Left,
+                    1 => MouseButton::Right,
+                    2 => MouseButton::Middle,
+                    3 => MouseButton::Navigate(NavigationDirection::Back),
+                    4 => MouseButton::Navigate(NavigationDirection::Forward),
+                    // Other mouse buttons aren't tracked currently
+                    _ => return None,
+                };
+
+                window_height.map(|window_height| {
+                    Self::MouseMoved(MouseMovedEvent {
+                        pressed_button: Some(pressed_button),
+                        position: point(
+                            px(native_event.locationInWindow().x as f32),
+                            window_height - px(native_event.locationInWindow().y as f32),
+                        ),
+                        modifiers: read_modifiers(native_event),
+                    })
+                })
+            }
+            NSEventType::NSMouseMoved => window_height.map(|window_height| {
+                Self::MouseMoved(MouseMovedEvent {
+                    position: point(
+                        px(native_event.locationInWindow().x as f32),
+                        window_height - px(native_event.locationInWindow().y as f32),
+                    ),
+                    pressed_button: None,
+                    modifiers: read_modifiers(native_event),
+                })
+            }),
+            NSEventType::NSMouseExited => window_height.map(|window_height| {
+                Self::MouseExited(MouseExitedEvent {
+                    position: point(
+                        px(native_event.locationInWindow().x as f32),
+                        window_height - px(native_event.locationInWindow().y as f32),
+                    ),
+
+                    pressed_button: None,
+                    modifiers: read_modifiers(native_event),
+                })
+            }),
+            _ => None,
+        }
+    }
+}
+
+unsafe fn parse_keystroke(native_event: id) -> Keystroke {
+    use cocoa::appkit::*;
+
+    let mut chars_ignoring_modifiers =
+        CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
+            .to_str()
+            .unwrap()
+            .to_string();
+    let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
+    let modifiers = native_event.modifierFlags();
+
+    let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
+    let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
+    let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
+    let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
+    let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
+        && first_char.map_or(true, |ch| {
+            !(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
+        });
+
+    #[allow(non_upper_case_globals)]
+    let key = match first_char {
+        Some(SPACE_KEY) => "space".to_string(),
+        Some(BACKSPACE_KEY) => "backspace".to_string(),
+        Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(),
+        Some(ESCAPE_KEY) => "escape".to_string(),
+        Some(TAB_KEY) => "tab".to_string(),
+        Some(SHIFT_TAB_KEY) => "tab".to_string(),
+        Some(NSUpArrowFunctionKey) => "up".to_string(),
+        Some(NSDownArrowFunctionKey) => "down".to_string(),
+        Some(NSLeftArrowFunctionKey) => "left".to_string(),
+        Some(NSRightArrowFunctionKey) => "right".to_string(),
+        Some(NSPageUpFunctionKey) => "pageup".to_string(),
+        Some(NSPageDownFunctionKey) => "pagedown".to_string(),
+        Some(NSHomeFunctionKey) => "home".to_string(),
+        Some(NSEndFunctionKey) => "end".to_string(),
+        Some(NSDeleteFunctionKey) => "delete".to_string(),
+        Some(NSF1FunctionKey) => "f1".to_string(),
+        Some(NSF2FunctionKey) => "f2".to_string(),
+        Some(NSF3FunctionKey) => "f3".to_string(),
+        Some(NSF4FunctionKey) => "f4".to_string(),
+        Some(NSF5FunctionKey) => "f5".to_string(),
+        Some(NSF6FunctionKey) => "f6".to_string(),
+        Some(NSF7FunctionKey) => "f7".to_string(),
+        Some(NSF8FunctionKey) => "f8".to_string(),
+        Some(NSF9FunctionKey) => "f9".to_string(),
+        Some(NSF10FunctionKey) => "f10".to_string(),
+        Some(NSF11FunctionKey) => "f11".to_string(),
+        Some(NSF12FunctionKey) => "f12".to_string(),
+        _ => {
+            let mut chars_ignoring_modifiers_and_shift =
+                chars_for_modified_key(native_event.keyCode(), false, false);
+
+            // Honor ⌘ when Dvorak-QWERTY is used.
+            let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
+            if command && chars_ignoring_modifiers_and_shift != chars_with_cmd {
+                chars_ignoring_modifiers =
+                    chars_for_modified_key(native_event.keyCode(), true, shift);
+                chars_ignoring_modifiers_and_shift = chars_with_cmd;
+            }
+
+            if shift {
+                if chars_ignoring_modifiers_and_shift
+                    == chars_ignoring_modifiers.to_ascii_lowercase()
+                {
+                    chars_ignoring_modifiers_and_shift
+                } else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
+                    shift = false;
+                    chars_ignoring_modifiers
+                } else {
+                    chars_ignoring_modifiers
+                }
+            } else {
+                chars_ignoring_modifiers
+            }
+        }
+    };
+
+    Keystroke {
+        modifiers: Modifiers {
+            control,
+            alt,
+            shift,
+            command,
+            function,
+        },
+        key,
+    }
+}
+
+fn chars_for_modified_key(code: CGKeyCode, cmd: bool, shift: bool) -> String {
+    // Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
+    // always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
+    // an event with the given flags instead lets us access `characters`, which always
+    // returns a valid string.
+    let source = unsafe { core_graphics::event_source::CGEventSource::from_ptr(EVENT_SOURCE) };
+    let event = CGEvent::new_keyboard_event(source.clone(), code, true).unwrap();
+    mem::forget(source);
+
+    let mut flags = CGEventFlags::empty();
+    if cmd {
+        flags |= CGEventFlags::CGEventFlagCommand;
+    }
+    if shift {
+        flags |= CGEventFlags::CGEventFlagShift;
+    }
+    event.set_flags(flags);
+
+    unsafe {
+        let event: id = msg_send![class!(NSEvent), eventWithCGEvent: &*event];
+        CStr::from_ptr(event.characters().UTF8String())
+            .to_str()
+            .unwrap()
+            .to_string()
+    }
+}

crates/gpui3/src/platform/mac/metal_renderer.rs 🔗

@@ -0,0 +1,294 @@
+use crate::{point, size, DevicePixels, Quad, Scene, Size};
+use bytemuck::{Pod, Zeroable};
+use cocoa::{
+    base::{NO, YES},
+    foundation::NSUInteger,
+    quartzcore::AutoresizingMask,
+};
+use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
+use objc::{self, msg_send, sel, sel_impl};
+use std::{ffi::c_void, mem, ptr};
+
+const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
+const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
+
+pub struct MetalRenderer {
+    device: metal::Device,
+    layer: metal::MetalLayer,
+    command_queue: CommandQueue,
+    quad_pipeline_state: metal::RenderPipelineState,
+    unit_vertices: metal::Buffer,
+    instances: metal::Buffer,
+}
+
+impl MetalRenderer {
+    pub fn new(is_opaque: bool) -> Self {
+        const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
+
+        let device: metal::Device = if let Some(device) = metal::Device::system_default() {
+            device
+        } else {
+            log::error!("unable to access a compatible graphics device");
+            std::process::exit(1);
+        };
+
+        let layer = metal::MetalLayer::new();
+        layer.set_device(&device);
+        layer.set_pixel_format(PIXEL_FORMAT);
+        layer.set_presents_with_transaction(true);
+        layer.set_opaque(is_opaque);
+        unsafe {
+            let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
+            let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
+            let _: () = msg_send![
+                &*layer,
+                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
+                    | AutoresizingMask::HEIGHT_SIZABLE
+            ];
+        }
+
+        let library = device
+            .new_library_with_data(SHADERS_METALLIB)
+            .expect("error building metal library");
+
+        fn to_float2_bits(point: crate::PointF) -> u64 {
+            unsafe {
+                let mut output = mem::transmute::<_, u32>(point.y.to_bits()) as u64;
+                output <<= 32;
+                output |= mem::transmute::<_, u32>(point.x.to_bits()) as u64;
+                output
+            }
+        }
+
+        let unit_vertices = [
+            to_float2_bits(point(0., 0.)),
+            to_float2_bits(point(1., 0.)),
+            to_float2_bits(point(0., 1.)),
+            to_float2_bits(point(0., 1.)),
+            to_float2_bits(point(1., 0.)),
+            to_float2_bits(point(1., 1.)),
+        ];
+        let unit_vertices = device.new_buffer_with_data(
+            unit_vertices.as_ptr() as *const c_void,
+            (unit_vertices.len() * mem::size_of::<u64>()) as u64,
+            MTLResourceOptions::StorageModeManaged,
+        );
+        let instances = device.new_buffer(
+            INSTANCE_BUFFER_SIZE as u64,
+            MTLResourceOptions::StorageModeManaged,
+        );
+
+        let quad_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "quad",
+            "quad_vertex",
+            "quad_fragment",
+            PIXEL_FORMAT,
+        );
+
+        let command_queue = device.new_command_queue();
+        Self {
+            device,
+            layer,
+            command_queue,
+            quad_pipeline_state,
+            unit_vertices,
+            instances,
+        }
+    }
+
+    pub fn layer(&self) -> &metal::MetalLayerRef {
+        &*self.layer
+    }
+
+    pub fn draw(&mut self, scene: &Scene) {
+        dbg!(scene);
+
+        let layer = self.layer.clone();
+        let viewport_size = layer.drawable_size();
+        let viewport_size: Size<DevicePixels> = size(
+            (viewport_size.width.ceil() as u32).into(),
+            (viewport_size.height.ceil() as u32).into(),
+        );
+        let drawable = if let Some(drawable) = layer.next_drawable() {
+            drawable
+        } else {
+            log::error!(
+                "failed to retrieve next drawable, drawable size: {:?}",
+                viewport_size
+            );
+            return;
+        };
+        let command_queue = self.command_queue.clone();
+        let command_buffer = command_queue.new_command_buffer();
+
+        let render_pass_descriptor = metal::RenderPassDescriptor::new();
+
+        let depth_texture_desc = metal::TextureDescriptor::new();
+        depth_texture_desc.set_pixel_format(metal::MTLPixelFormat::Depth32Float);
+        depth_texture_desc.set_storage_mode(metal::MTLStorageMode::Private);
+        depth_texture_desc.set_usage(metal::MTLTextureUsage::RenderTarget);
+        depth_texture_desc.set_width(u32::from(viewport_size.width) as u64);
+        depth_texture_desc.set_height(u32::from(viewport_size.height) as u64);
+        let depth_texture = self.device.new_texture(&depth_texture_desc);
+        let depth_attachment = render_pass_descriptor.depth_attachment().unwrap();
+
+        depth_attachment.set_texture(Some(&depth_texture));
+        depth_attachment.set_clear_depth(1.);
+        depth_attachment.set_store_action(metal::MTLStoreAction::Store);
+
+        let color_attachment = render_pass_descriptor
+            .color_attachments()
+            .object_at(0)
+            .unwrap();
+
+        color_attachment.set_texture(Some(drawable.texture()));
+        color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+        color_attachment.set_store_action(metal::MTLStoreAction::Store);
+        let alpha = if self.layer.is_opaque() { 1. } else { 0. };
+        color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha));
+        let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
+
+        command_encoder.set_viewport(metal::MTLViewport {
+            originX: 0.0,
+            originY: 0.0,
+            width: u32::from(viewport_size.width) as f64,
+            height: u32::from(viewport_size.height) as f64,
+            znear: 0.0,
+            zfar: 1.0,
+        });
+
+        let mut buffer_offset = 0;
+        for layer in scene.layers() {
+            self.draw_quads(
+                &layer.quads,
+                &mut buffer_offset,
+                viewport_size,
+                command_encoder,
+            );
+        }
+
+        command_encoder.end_encoding();
+
+        self.instances.did_modify_range(NSRange {
+            location: 0,
+            length: buffer_offset as NSUInteger,
+        });
+
+        command_buffer.commit();
+        command_buffer.wait_until_completed();
+        drawable.present();
+    }
+
+    fn draw_quads(
+        &mut self,
+        quads: &[Quad],
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        if quads.is_empty() {
+            return;
+        }
+        align_offset(offset);
+
+        command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            QuadInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_buffer(
+            QuadInputIndex::Quads as u64,
+            Some(&self.instances),
+            *offset as u64,
+        );
+        command_encoder.set_fragment_buffer(
+            QuadInputIndex::Quads as u64,
+            Some(&self.instances),
+            *offset as u64,
+        );
+        let quad_uniforms = QuadUniforms { viewport_size };
+
+        let quad_uniform_bytes = bytemuck::bytes_of(&quad_uniforms);
+        command_encoder.set_vertex_bytes(
+            QuadInputIndex::Uniforms as u64,
+            quad_uniform_bytes.len() as u64,
+            quad_uniform_bytes.as_ptr() as *const c_void,
+        );
+
+        let quad_bytes = bytemuck::cast_slice(quads);
+        let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+        unsafe {
+            ptr::copy_nonoverlapping(quad_bytes.as_ptr(), buffer_contents, quad_bytes.len());
+        }
+
+        let next_offset = *offset + quad_bytes.len();
+        assert!(
+            next_offset <= INSTANCE_BUFFER_SIZE,
+            "instance buffer exhausted"
+        );
+
+        command_encoder.draw_primitives_instanced(
+            metal::MTLPrimitiveType::Triangle,
+            0,
+            6,
+            quads.len() as u64,
+        );
+        *offset = next_offset;
+    }
+}
+
+fn build_pipeline_state(
+    device: &metal::DeviceRef,
+    library: &metal::LibraryRef,
+    label: &str,
+    vertex_fn_name: &str,
+    fragment_fn_name: &str,
+    pixel_format: metal::MTLPixelFormat,
+) -> metal::RenderPipelineState {
+    let vertex_fn = library
+        .get_function(vertex_fn_name, None)
+        .expect("error locating vertex function");
+    let fragment_fn = library
+        .get_function(fragment_fn_name, None)
+        .expect("error locating fragment function");
+
+    let descriptor = metal::RenderPipelineDescriptor::new();
+    descriptor.set_label(label);
+    descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
+    descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
+    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
+    color_attachment.set_pixel_format(pixel_format);
+    color_attachment.set_blending_enabled(true);
+    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::SourceAlpha);
+    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
+    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
+    // descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Depth32Float);
+
+    device
+        .new_render_pipeline_state(&descriptor)
+        .expect("could not create render pipeline state")
+}
+
+// Align to multiples of 256 make Metal happy.
+fn align_offset(offset: &mut usize) {
+    *offset = ((*offset + 255) / 256) * 256;
+}
+
+#[repr(C)]
+enum QuadInputIndex {
+    Vertices = 0,
+    Quads = 1,
+    Uniforms = 2,
+}
+
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+#[repr(C)]
+pub(crate) struct QuadUniforms {
+    viewport_size: Size<DevicePixels>,
+}

crates/gpui3/src/platform/mac/open_type.rs 🔗

@@ -0,0 +1,394 @@
+#![allow(unused, non_upper_case_globals)]
+
+use crate::FontFeatures;
+use cocoa::appkit::CGFloat;
+use core_foundation::{base::TCFType, number::CFNumber};
+use core_graphics::geometry::CGAffineTransform;
+use core_text::{
+    font::{CTFont, CTFontRef},
+    font_descriptor::{
+        CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
+    },
+};
+use font_kit::font::Font;
+use std::ptr;
+
+const kCaseSensitiveLayoutOffSelector: i32 = 1;
+const kCaseSensitiveLayoutOnSelector: i32 = 0;
+const kCaseSensitiveLayoutType: i32 = 33;
+const kCaseSensitiveSpacingOffSelector: i32 = 3;
+const kCaseSensitiveSpacingOnSelector: i32 = 2;
+const kCharacterAlternativesType: i32 = 17;
+const kCommonLigaturesOffSelector: i32 = 3;
+const kCommonLigaturesOnSelector: i32 = 2;
+const kContextualAlternatesOffSelector: i32 = 1;
+const kContextualAlternatesOnSelector: i32 = 0;
+const kContextualAlternatesType: i32 = 36;
+const kContextualLigaturesOffSelector: i32 = 19;
+const kContextualLigaturesOnSelector: i32 = 18;
+const kContextualSwashAlternatesOffSelector: i32 = 5;
+const kContextualSwashAlternatesOnSelector: i32 = 4;
+const kDefaultLowerCaseSelector: i32 = 0;
+const kDefaultUpperCaseSelector: i32 = 0;
+const kDiagonalFractionsSelector: i32 = 2;
+const kFractionsType: i32 = 11;
+const kHistoricalLigaturesOffSelector: i32 = 21;
+const kHistoricalLigaturesOnSelector: i32 = 20;
+const kHojoCharactersSelector: i32 = 12;
+const kInferiorsSelector: i32 = 2;
+const kJIS2004CharactersSelector: i32 = 11;
+const kLigaturesType: i32 = 1;
+const kLowerCasePetiteCapsSelector: i32 = 2;
+const kLowerCaseSmallCapsSelector: i32 = 1;
+const kLowerCaseType: i32 = 37;
+const kLowerCaseNumbersSelector: i32 = 0;
+const kMathematicalGreekOffSelector: i32 = 11;
+const kMathematicalGreekOnSelector: i32 = 10;
+const kMonospacedNumbersSelector: i32 = 0;
+const kNLCCharactersSelector: i32 = 13;
+const kNoFractionsSelector: i32 = 0;
+const kNormalPositionSelector: i32 = 0;
+const kNoStyleOptionsSelector: i32 = 0;
+const kNumberCaseType: i32 = 21;
+const kNumberSpacingType: i32 = 6;
+const kOrdinalsSelector: i32 = 3;
+const kProportionalNumbersSelector: i32 = 1;
+const kQuarterWidthTextSelector: i32 = 4;
+const kScientificInferiorsSelector: i32 = 4;
+const kSlashedZeroOffSelector: i32 = 5;
+const kSlashedZeroOnSelector: i32 = 4;
+const kStyleOptionsType: i32 = 19;
+const kStylisticAltEighteenOffSelector: i32 = 37;
+const kStylisticAltEighteenOnSelector: i32 = 36;
+const kStylisticAltEightOffSelector: i32 = 17;
+const kStylisticAltEightOnSelector: i32 = 16;
+const kStylisticAltElevenOffSelector: i32 = 23;
+const kStylisticAltElevenOnSelector: i32 = 22;
+const kStylisticAlternativesType: i32 = 35;
+const kStylisticAltFifteenOffSelector: i32 = 31;
+const kStylisticAltFifteenOnSelector: i32 = 30;
+const kStylisticAltFiveOffSelector: i32 = 11;
+const kStylisticAltFiveOnSelector: i32 = 10;
+const kStylisticAltFourOffSelector: i32 = 9;
+const kStylisticAltFourOnSelector: i32 = 8;
+const kStylisticAltFourteenOffSelector: i32 = 29;
+const kStylisticAltFourteenOnSelector: i32 = 28;
+const kStylisticAltNineOffSelector: i32 = 19;
+const kStylisticAltNineOnSelector: i32 = 18;
+const kStylisticAltNineteenOffSelector: i32 = 39;
+const kStylisticAltNineteenOnSelector: i32 = 38;
+const kStylisticAltOneOffSelector: i32 = 3;
+const kStylisticAltOneOnSelector: i32 = 2;
+const kStylisticAltSevenOffSelector: i32 = 15;
+const kStylisticAltSevenOnSelector: i32 = 14;
+const kStylisticAltSeventeenOffSelector: i32 = 35;
+const kStylisticAltSeventeenOnSelector: i32 = 34;
+const kStylisticAltSixOffSelector: i32 = 13;
+const kStylisticAltSixOnSelector: i32 = 12;
+const kStylisticAltSixteenOffSelector: i32 = 33;
+const kStylisticAltSixteenOnSelector: i32 = 32;
+const kStylisticAltTenOffSelector: i32 = 21;
+const kStylisticAltTenOnSelector: i32 = 20;
+const kStylisticAltThirteenOffSelector: i32 = 27;
+const kStylisticAltThirteenOnSelector: i32 = 26;
+const kStylisticAltThreeOffSelector: i32 = 7;
+const kStylisticAltThreeOnSelector: i32 = 6;
+const kStylisticAltTwelveOffSelector: i32 = 25;
+const kStylisticAltTwelveOnSelector: i32 = 24;
+const kStylisticAltTwentyOffSelector: i32 = 41;
+const kStylisticAltTwentyOnSelector: i32 = 40;
+const kStylisticAltTwoOffSelector: i32 = 5;
+const kStylisticAltTwoOnSelector: i32 = 4;
+const kSuperiorsSelector: i32 = 1;
+const kSwashAlternatesOffSelector: i32 = 3;
+const kSwashAlternatesOnSelector: i32 = 2;
+const kTitlingCapsSelector: i32 = 4;
+const kTypographicExtrasType: i32 = 14;
+const kVerticalFractionsSelector: i32 = 1;
+const kVerticalPositionType: i32 = 10;
+
+pub fn apply_features(font: &mut Font, features: FontFeatures) {
+    // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
+    // for a reference implementation.
+    toggle_open_type_feature(
+        font,
+        features.calt(),
+        kContextualAlternatesType,
+        kContextualAlternatesOnSelector,
+        kContextualAlternatesOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.case(),
+        kCaseSensitiveLayoutType,
+        kCaseSensitiveLayoutOnSelector,
+        kCaseSensitiveLayoutOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.cpsp(),
+        kCaseSensitiveLayoutType,
+        kCaseSensitiveSpacingOnSelector,
+        kCaseSensitiveSpacingOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.frac(),
+        kFractionsType,
+        kDiagonalFractionsSelector,
+        kNoFractionsSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.liga(),
+        kLigaturesType,
+        kCommonLigaturesOnSelector,
+        kCommonLigaturesOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.onum(),
+        kNumberCaseType,
+        kLowerCaseNumbersSelector,
+        2,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ordn(),
+        kVerticalPositionType,
+        kOrdinalsSelector,
+        kNormalPositionSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.pnum(),
+        kNumberSpacingType,
+        kProportionalNumbersSelector,
+        4,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss01(),
+        kStylisticAlternativesType,
+        kStylisticAltOneOnSelector,
+        kStylisticAltOneOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss02(),
+        kStylisticAlternativesType,
+        kStylisticAltTwoOnSelector,
+        kStylisticAltTwoOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss03(),
+        kStylisticAlternativesType,
+        kStylisticAltThreeOnSelector,
+        kStylisticAltThreeOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss04(),
+        kStylisticAlternativesType,
+        kStylisticAltFourOnSelector,
+        kStylisticAltFourOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss05(),
+        kStylisticAlternativesType,
+        kStylisticAltFiveOnSelector,
+        kStylisticAltFiveOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss06(),
+        kStylisticAlternativesType,
+        kStylisticAltSixOnSelector,
+        kStylisticAltSixOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss07(),
+        kStylisticAlternativesType,
+        kStylisticAltSevenOnSelector,
+        kStylisticAltSevenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss08(),
+        kStylisticAlternativesType,
+        kStylisticAltEightOnSelector,
+        kStylisticAltEightOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss09(),
+        kStylisticAlternativesType,
+        kStylisticAltNineOnSelector,
+        kStylisticAltNineOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss10(),
+        kStylisticAlternativesType,
+        kStylisticAltTenOnSelector,
+        kStylisticAltTenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss11(),
+        kStylisticAlternativesType,
+        kStylisticAltElevenOnSelector,
+        kStylisticAltElevenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss12(),
+        kStylisticAlternativesType,
+        kStylisticAltTwelveOnSelector,
+        kStylisticAltTwelveOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss13(),
+        kStylisticAlternativesType,
+        kStylisticAltThirteenOnSelector,
+        kStylisticAltThirteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss14(),
+        kStylisticAlternativesType,
+        kStylisticAltFourteenOnSelector,
+        kStylisticAltFourteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss15(),
+        kStylisticAlternativesType,
+        kStylisticAltFifteenOnSelector,
+        kStylisticAltFifteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss16(),
+        kStylisticAlternativesType,
+        kStylisticAltSixteenOnSelector,
+        kStylisticAltSixteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss17(),
+        kStylisticAlternativesType,
+        kStylisticAltSeventeenOnSelector,
+        kStylisticAltSeventeenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss18(),
+        kStylisticAlternativesType,
+        kStylisticAltEighteenOnSelector,
+        kStylisticAltEighteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss19(),
+        kStylisticAlternativesType,
+        kStylisticAltNineteenOnSelector,
+        kStylisticAltNineteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss20(),
+        kStylisticAlternativesType,
+        kStylisticAltTwentyOnSelector,
+        kStylisticAltTwentyOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.subs(),
+        kVerticalPositionType,
+        kInferiorsSelector,
+        kNormalPositionSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.sups(),
+        kVerticalPositionType,
+        kSuperiorsSelector,
+        kNormalPositionSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.swsh(),
+        kContextualAlternatesType,
+        kSwashAlternatesOnSelector,
+        kSwashAlternatesOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.titl(),
+        kStyleOptionsType,
+        kTitlingCapsSelector,
+        kNoStyleOptionsSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.tnum(),
+        kNumberSpacingType,
+        kMonospacedNumbersSelector,
+        4,
+    );
+    toggle_open_type_feature(
+        font,
+        features.zero(),
+        kTypographicExtrasType,
+        kSlashedZeroOnSelector,
+        kSlashedZeroOffSelector,
+    );
+}
+
+fn toggle_open_type_feature(
+    font: &mut Font,
+    enabled: Option<bool>,
+    type_identifier: i32,
+    on_selector_identifier: i32,
+    off_selector_identifier: i32,
+) {
+    if let Some(enabled) = enabled {
+        let native_font = font.native_font();
+        unsafe {
+            let selector_identifier = if enabled {
+                on_selector_identifier
+            } else {
+                off_selector_identifier
+            };
+            let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
+                native_font.copy_descriptor().as_concrete_TypeRef(),
+                CFNumber::from(type_identifier).as_concrete_TypeRef(),
+                CFNumber::from(selector_identifier).as_concrete_TypeRef(),
+            );
+            let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
+            let new_font = CTFontCreateCopyWithAttributes(
+                font.native_font().as_concrete_TypeRef(),
+                0.0,
+                ptr::null(),
+                new_descriptor.as_concrete_TypeRef(),
+            );
+            let new_font = CTFont::wrap_under_create_rule(new_font);
+            *font = Font::from_native_font(new_font);
+        }
+    }
+}
+
+#[link(name = "CoreText", kind = "framework")]
+extern "C" {
+    fn CTFontCreateCopyWithAttributes(
+        font: CTFontRef,
+        size: CGFloat,
+        matrix: *const CGAffineTransform,
+        attributes: CTFontDescriptorRef,
+    ) -> CTFontRef;
+}

crates/gpui3/src/platform/mac/platform.rs 🔗

@@ -0,0 +1,1123 @@
+use super::BoolExt;
+use crate::{
+    AnyWindowHandle, ClipboardItem, CursorStyle, Event, MacDispatcher, MacScreen, MacTextSystem,
+    MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem, PlatformWindow,
+    Result, ScreenId, SemanticVersion, WindowOptions,
+};
+use anyhow::anyhow;
+use block::ConcreteBlock;
+use cocoa::{
+    appkit::{
+        NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
+        NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow,
+    },
+    base::{id, nil, BOOL, YES},
+    foundation::{
+        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
+        NSUInteger, NSURL,
+    },
+};
+use core_foundation::{
+    base::{CFType, CFTypeRef, OSStatus, TCFType as _},
+    boolean::CFBoolean,
+    data::CFData,
+    dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
+    string::{CFString, CFStringRef},
+};
+use ctor::ctor;
+use futures::channel::oneshot;
+use objc::{
+    class,
+    declare::ClassDecl,
+    msg_send,
+    runtime::{Class, Object, Sel},
+    sel, sel_impl,
+};
+use parking_lot::Mutex;
+use ptr::null_mut;
+use std::{
+    cell::Cell,
+    convert::TryInto,
+    ffi::{c_void, CStr, OsStr},
+    os::{raw::c_char, unix::ffi::OsStrExt},
+    path::{Path, PathBuf},
+    process::Command,
+    ptr,
+    rc::Rc,
+    slice, str,
+    sync::Arc,
+};
+use time::UtcOffset;
+
+#[allow(non_upper_case_globals)]
+const NSUTF8StringEncoding: NSUInteger = 4;
+
+#[allow(non_upper_case_globals)]
+pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+
+const MAC_PLATFORM_IVAR: &str = "platform";
+static mut APP_CLASS: *const Class = ptr::null();
+static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
+
+#[ctor]
+unsafe fn build_classes() {
+    APP_CLASS = {
+        let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
+        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+        decl.add_method(
+            sel!(sendEvent:),
+            send_event as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.register()
+    };
+
+    APP_DELEGATE_CLASS = {
+        let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
+        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+        decl.add_method(
+            sel!(applicationDidFinishLaunching:),
+            did_finish_launching as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationShouldHandleReopen:hasVisibleWindows:),
+            should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
+        );
+        decl.add_method(
+            sel!(applicationDidBecomeActive:),
+            did_become_active as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationDidResignActive:),
+            did_resign_active as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationWillTerminate:),
+            will_terminate as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(handleGPUIMenuItem:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        // Add menu item handlers so that OS save panels have the correct key commands
+        decl.add_method(
+            sel!(cut:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(copy:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(paste:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(selectAll:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(undo:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(redo:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(validateMenuItem:),
+            validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
+        );
+        decl.add_method(
+            sel!(menuWillOpen:),
+            menu_will_open as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(application:openURLs:),
+            open_urls as extern "C" fn(&mut Object, Sel, id, id),
+        );
+        decl.register()
+    }
+}
+
+pub struct MacPlatform(Mutex<MacPlatformState>);
+
+pub struct MacPlatformState {
+    dispatcher: Arc<MacDispatcher>,
+    text_system: Arc<MacTextSystem>,
+    pasteboard: id,
+    text_hash_pasteboard_type: id,
+    metadata_pasteboard_type: id,
+    become_active: Option<Box<dyn FnMut()>>,
+    resign_active: Option<Box<dyn FnMut()>>,
+    reopen: Option<Box<dyn FnMut()>>,
+    quit: Option<Box<dyn FnMut()>>,
+    event: Option<Box<dyn FnMut(Event) -> bool>>,
+    // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
+    // validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
+    will_open_menu: Option<Box<dyn FnMut()>>,
+    open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
+    finish_launching: Option<Box<dyn FnOnce()>>,
+    // menu_actions: Vec<Box<dyn Action>>,
+}
+
+impl MacPlatform {
+    pub fn new() -> Self {
+        Self(Mutex::new(MacPlatformState {
+            dispatcher: Arc::new(MacDispatcher),
+            text_system: Arc::new(MacTextSystem::new()),
+            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
+            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
+            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
+            become_active: None,
+            resign_active: None,
+            reopen: None,
+            quit: None,
+            event: None,
+            will_open_menu: None,
+            open_urls: None,
+            finish_launching: None,
+            // menu_command: None,
+            // validate_menu_command: None,
+            // menu_actions: Default::default(),
+        }))
+    }
+
+    unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
+        let pasteboard = self.0.lock().pasteboard;
+        let data = pasteboard.dataForType(kind);
+        if data == nil {
+            None
+        } else {
+            Some(slice::from_raw_parts(
+                data.bytes() as *mut u8,
+                data.length() as usize,
+            ))
+        }
+    }
+
+    // unsafe fn create_menu_bar(
+    //     &self,
+    //     menus: Vec<Menu>,
+    //     delegate: id,
+    //     actions: &mut Vec<Box<dyn Action>>,
+    //     keystroke_matcher: &KeymapMatcher,
+    // ) -> id {
+    //     let application_menu = NSMenu::new(nil).autorelease();
+    //     application_menu.setDelegate_(delegate);
+
+    //     for menu_config in menus {
+    //         let menu = NSMenu::new(nil).autorelease();
+    //         menu.setTitle_(ns_string(menu_config.name));
+    //         menu.setDelegate_(delegate);
+
+    //         for item_config in menu_config.items {
+    //             menu.addItem_(self.create_menu_item(
+    //                 item_config,
+    //                 delegate,
+    //                 actions,
+    //                 keystroke_matcher,
+    //             ));
+    //         }
+
+    //         let menu_item = NSMenuItem::new(nil).autorelease();
+    //         menu_item.setSubmenu_(menu);
+    //         application_menu.addItem_(menu_item);
+
+    //         if menu_config.name == "Window" {
+    //             let app: id = msg_send![APP_CLASS, sharedApplication];
+    //             app.setWindowsMenu_(menu);
+    //         }
+    //     }
+
+    //     application_menu
+    // }
+
+    // unsafe fn create_menu_item(
+    //     &self,
+    //     item: MenuItem,
+    //     delegate: id,
+    //     actions: &mut Vec<Box<dyn Action>>,
+    //     keystroke_matcher: &KeymapMatcher,
+    // ) -> id {
+    //     match item {
+    //         MenuItem::Separator => NSMenuItem::separatorItem(nil),
+    //         MenuItem::Action {
+    //             name,
+    //             action,
+    //             os_action,
+    //         } => {
+    //             // TODO
+    //             let keystrokes = keystroke_matcher
+    //                 .bindings_for_action(action.id())
+    //                 .find(|binding| binding.action().eq(action.as_ref()))
+    //                 .map(|binding| binding.keystrokes());
+    //             let selector = match os_action {
+    //                 Some(crate::OsAction::Cut) => selector("cut:"),
+    //                 Some(crate::OsAction::Copy) => selector("copy:"),
+    //                 Some(crate::OsAction::Paste) => selector("paste:"),
+    //                 Some(crate::OsAction::SelectAll) => selector("selectAll:"),
+    //                 Some(crate::OsAction::Undo) => selector("undo:"),
+    //                 Some(crate::OsAction::Redo) => selector("redo:"),
+    //                 None => selector("handleGPUIMenuItem:"),
+    //             };
+
+    //             let item;
+    //             if let Some(keystrokes) = keystrokes {
+    //                 if keystrokes.len() == 1 {
+    //                     let keystroke = &keystrokes[0];
+    //                     let mut mask = NSEventModifierFlags::empty();
+    //                     for (modifier, flag) in &[
+    //                         (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
+    //                         (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
+    //                         (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
+    //                         (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
+    //                     ] {
+    //                         if *modifier {
+    //                             mask |= *flag;
+    //                         }
+    //                     }
+
+    //                     item = NSMenuItem::alloc(nil)
+    //                         .initWithTitle_action_keyEquivalent_(
+    //                             ns_string(name),
+    //                             selector,
+    //                             ns_string(key_to_native(&keystroke.key).as_ref()),
+    //                         )
+    //                         .autorelease();
+    //                     item.setKeyEquivalentModifierMask_(mask);
+    //                 }
+    //                 // For multi-keystroke bindings, render the keystroke as part of the title.
+    //                 else {
+    //                     use std::fmt::Write;
+
+    //                     let mut name = format!("{name} [");
+    //                     for (i, keystroke) in keystrokes.iter().enumerate() {
+    //                         if i > 0 {
+    //                             name.push(' ');
+    //                         }
+    //                         write!(&mut name, "{}", keystroke).unwrap();
+    //                     }
+    //                     name.push(']');
+
+    //                     item = NSMenuItem::alloc(nil)
+    //                         .initWithTitle_action_keyEquivalent_(
+    //                             ns_string(&name),
+    //                             selector,
+    //                             ns_string(""),
+    //                         )
+    //                         .autorelease();
+    //                 }
+    //             } else {
+    //                 item = NSMenuItem::alloc(nil)
+    //                     .initWithTitle_action_keyEquivalent_(
+    //                         ns_string(name),
+    //                         selector,
+    //                         ns_string(""),
+    //                     )
+    //                     .autorelease();
+    //             }
+
+    //             let tag = actions.len() as NSInteger;
+    //             let _: () = msg_send![item, setTag: tag];
+    //             actions.push(action);
+    //             item
+    //         }
+    //         MenuItem::Submenu(Menu { name, items }) => {
+    //             let item = NSMenuItem::new(nil).autorelease();
+    //             let submenu = NSMenu::new(nil).autorelease();
+    //             submenu.setDelegate_(delegate);
+    //             for item in items {
+    //                 submenu.addItem_(self.create_menu_item(
+    //                     item,
+    //                     delegate,
+    //                     actions,
+    //                     keystroke_matcher,
+    //                 ));
+    //             }
+    //             item.setSubmenu_(submenu);
+    //             item.setTitle_(ns_string(name));
+    //             item
+    //         }
+    //     }
+    // }
+}
+
+impl Platform for MacPlatform {
+    fn dispatcher(&self) -> Arc<dyn crate::PlatformDispatcher> {
+        Arc::new(MacDispatcher)
+    }
+
+    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
+        self.0.lock().text_system.clone()
+    }
+
+    fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
+        self.0.lock().finish_launching = Some(on_finish_launching);
+
+        unsafe {
+            let app: id = msg_send![APP_CLASS, sharedApplication];
+            let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
+            app.setDelegate_(app_delegate);
+
+            let self_ptr = self as *const Self as *const c_void;
+            (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+            (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+
+            let pool = NSAutoreleasePool::new(nil);
+            app.run();
+            pool.drain();
+
+            (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+            (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+        }
+    }
+
+    fn quit(&self) {
+        // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
+        // synchronously before this method terminates. If we call `Platform::quit` while holding a
+        // borrow of the app state (which most of the time we will do), we will end up
+        // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
+        // this, we make quitting the application asynchronous so that we aren't holding borrows to
+        // the app state on the stack when we actually terminate the app.
+
+        use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
+
+        unsafe {
+            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+        }
+
+        unsafe extern "C" fn quit(_: *mut c_void) {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, terminate: nil];
+        }
+    }
+
+    fn restart(&self) {
+        use std::os::unix::process::CommandExt as _;
+
+        let app_pid = std::process::id().to_string();
+        let app_path = self
+            .app_path()
+            .ok()
+            // When the app is not bundled, `app_path` returns the
+            // directory containing the executable. Disregard this
+            // and get the path to the executable itself.
+            .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
+            .unwrap_or_else(|| std::env::current_exe().unwrap());
+
+        // Wait until this process has exited and then re-open this path.
+        let script = r#"
+            while kill -0 $0 2> /dev/null; do
+                sleep 0.1
+            done
+            open "$1"
+        "#;
+
+        let restart_process = Command::new("/bin/bash")
+            .arg("-c")
+            .arg(script)
+            .arg(app_pid)
+            .arg(app_path)
+            .process_group(0)
+            .spawn();
+
+        match restart_process {
+            Ok(_) => self.quit(),
+            Err(e) => log::error!("failed to spawn restart script: {:?}", e),
+        }
+    }
+
+    fn activate(&self, ignoring_other_apps: bool) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+        }
+    }
+
+    fn hide(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, hide: nil];
+        }
+    }
+
+    fn hide_other_apps(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, hideOtherApplications: nil];
+        }
+    }
+
+    fn unhide_other_apps(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, unhideAllApplications: nil];
+        }
+    }
+
+    fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
+        MacScreen::all()
+            .into_iter()
+            .map(|screen| Rc::new(screen) as Rc<_>)
+            .collect()
+    }
+
+    fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>> {
+        MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+    }
+
+    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
+    //     Box::new(StatusItem::add(self.fonts()))
+    // }
+
+    fn main_window(&self) -> Option<AnyWindowHandle> {
+        MacWindow::main_window()
+    }
+
+    fn open_window(
+        &self,
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+    ) -> Box<dyn PlatformWindow> {
+        Box::new(MacWindow::open(handle, options, self))
+    }
+
+    fn open_url(&self, url: &str) {
+        unsafe {
+            let url = NSURL::alloc(nil)
+                .initWithString_(ns_string(url))
+                .autorelease();
+            let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+            msg_send![workspace, openURL: url]
+        }
+    }
+
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
+        self.0.lock().open_urls = Some(callback);
+    }
+
+    fn prompt_for_paths(
+        &self,
+        options: PathPromptOptions,
+    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
+        unsafe {
+            let panel = NSOpenPanel::openPanel(nil);
+            panel.setCanChooseDirectories_(options.directories.to_objc());
+            panel.setCanChooseFiles_(options.files.to_objc());
+            panel.setAllowsMultipleSelection_(options.multiple.to_objc());
+            panel.setResolvesAliases_(false.to_objc());
+            let (done_tx, done_rx) = oneshot::channel();
+            let done_tx = Cell::new(Some(done_tx));
+            let block = ConcreteBlock::new(move |response: NSModalResponse| {
+                let result = if response == NSModalResponse::NSModalResponseOk {
+                    let mut result = Vec::new();
+                    let urls = panel.URLs();
+                    for i in 0..urls.count() {
+                        let url = urls.objectAtIndex(i);
+                        if url.isFileURL() == YES {
+                            if let Ok(path) = ns_url_to_path(url) {
+                                result.push(path)
+                            }
+                        }
+                    }
+                    Some(result)
+                } else {
+                    None
+                };
+
+                if let Some(done_tx) = done_tx.take() {
+                    let _ = done_tx.send(result);
+                }
+            });
+            let block = block.copy();
+            let _: () = msg_send![panel, beginWithCompletionHandler: block];
+            done_rx
+        }
+    }
+
+    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
+        unsafe {
+            let panel = NSSavePanel::savePanel(nil);
+            let path = ns_string(directory.to_string_lossy().as_ref());
+            let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
+            panel.setDirectoryURL(url);
+
+            let (done_tx, done_rx) = oneshot::channel();
+            let done_tx = Cell::new(Some(done_tx));
+            let block = ConcreteBlock::new(move |response: NSModalResponse| {
+                let mut result = None;
+                if response == NSModalResponse::NSModalResponseOk {
+                    let url = panel.URL();
+                    if url.isFileURL() == YES {
+                        result = ns_url_to_path(panel.URL()).ok()
+                    }
+                }
+
+                if let Some(done_tx) = done_tx.take() {
+                    let _ = done_tx.send(result);
+                }
+            });
+            let block = block.copy();
+            let _: () = msg_send![panel, beginWithCompletionHandler: block];
+            done_rx
+        }
+    }
+
+    fn reveal_path(&self, path: &Path) {
+        unsafe {
+            let path = path.to_path_buf();
+            let dispatcher = self.0.lock().dispatcher.clone();
+            let _ = crate::spawn_on_main_local(dispatcher, async move {
+                let full_path = ns_string(path.to_str().unwrap_or(""));
+                let root_full_path = ns_string("");
+                let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+                let _: BOOL = msg_send![
+                    workspace,
+                    selectFile: full_path
+                    inFileViewerRootedAtPath: root_full_path
+                ];
+            });
+        }
+    }
+
+    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().become_active = Some(callback);
+    }
+
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().resign_active = Some(callback);
+    }
+
+    fn on_quit(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().quit = Some(callback);
+    }
+
+    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().reopen = Some(callback);
+    }
+
+    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) {
+        self.0.lock().event = Some(callback);
+    }
+
+    fn os_name(&self) -> &'static str {
+        "macOS"
+    }
+
+    fn os_version(&self) -> Result<SemanticVersion> {
+        unsafe {
+            let process_info = NSProcessInfo::processInfo(nil);
+            let version = process_info.operatingSystemVersion();
+            Ok(SemanticVersion {
+                major: version.majorVersion as usize,
+                minor: version.minorVersion as usize,
+                patch: version.patchVersion as usize,
+            })
+        }
+    }
+
+    fn app_version(&self) -> Result<SemanticVersion> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
+                let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+                let bytes = version.UTF8String() as *const u8;
+                let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+                version.parse()
+            }
+        }
+    }
+
+    fn app_path(&self) -> Result<PathBuf> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                Ok(path_from_objc(msg_send![bundle, bundlePath]))
+            }
+        }
+    }
+
+    fn local_timezone(&self) -> UtcOffset {
+        unsafe {
+            let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
+            let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
+            UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
+        }
+    }
+
+    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                let name = ns_string(name);
+                let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
+                if url.is_null() {
+                    Err(anyhow!("resource not found"))
+                } else {
+                    ns_url_to_path(url)
+                }
+            }
+        }
+    }
+
+    fn set_cursor_style(&self, style: CursorStyle) {
+        unsafe {
+            let new_cursor: id = match style {
+                CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
+                CursorStyle::ResizeLeftRight => {
+                    msg_send![class!(NSCursor), resizeLeftRightCursor]
+                }
+                CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
+                CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
+                CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
+            };
+
+            let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
+            if new_cursor != old_cursor {
+                let _: () = msg_send![new_cursor, set];
+            }
+        }
+    }
+
+    fn should_auto_hide_scrollbars(&self) -> bool {
+        #[allow(non_upper_case_globals)]
+        const NSScrollerStyleOverlay: NSInteger = 1;
+
+        unsafe {
+            let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
+            style == NSScrollerStyleOverlay
+        }
+    }
+
+    fn write_to_clipboard(&self, item: ClipboardItem) {
+        let state = self.0.lock();
+        unsafe {
+            state.pasteboard.clearContents();
+
+            let text_bytes = NSData::dataWithBytes_length_(
+                nil,
+                item.text.as_ptr() as *const c_void,
+                item.text.len() as u64,
+            );
+            state
+                .pasteboard
+                .setData_forType(text_bytes, NSPasteboardTypeString);
+
+            if let Some(metadata) = item.metadata.as_ref() {
+                let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
+                let hash_bytes = NSData::dataWithBytes_length_(
+                    nil,
+                    hash_bytes.as_ptr() as *const c_void,
+                    hash_bytes.len() as u64,
+                );
+                state
+                    .pasteboard
+                    .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
+
+                let metadata_bytes = NSData::dataWithBytes_length_(
+                    nil,
+                    metadata.as_ptr() as *const c_void,
+                    metadata.len() as u64,
+                );
+                state
+                    .pasteboard
+                    .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
+            }
+        }
+    }
+
+    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+        let state = self.0.lock();
+        unsafe {
+            if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
+                let text = String::from_utf8_lossy(text_bytes).to_string();
+                let hash_bytes = self
+                    .read_from_pasteboard(state.text_hash_pasteboard_type)
+                    .and_then(|bytes| bytes.try_into().ok())
+                    .map(u64::from_be_bytes);
+                let metadata_bytes = self
+                    .read_from_pasteboard(state.metadata_pasteboard_type)
+                    .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
+
+                if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
+                    if hash == ClipboardItem::text_hash(&text) {
+                        Some(ClipboardItem {
+                            text,
+                            metadata: Some(metadata),
+                        })
+                    } else {
+                        Some(ClipboardItem {
+                            text,
+                            metadata: None,
+                        })
+                    }
+                } else {
+                    Some(ClipboardItem {
+                        text,
+                        metadata: None,
+                    })
+                }
+            } else {
+                None
+            }
+        }
+    }
+
+    // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+    //     self.0.lock().menu_command = Some(callback);
+    // }
+
+    // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
+    //     self.0.lock().will_open_menu = Some(callback);
+    // }
+
+    // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+    //     self.0.lock().validate_menu_command = Some(callback);
+    // }
+
+    // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
+    //     unsafe {
+    //         let app: id = msg_send![APP_CLASS, sharedApplication];
+    //         let mut state = self.0.lock();
+    //         let actions = &mut state.menu_actions;
+    //         app.setMainMenu_(self.create_menu_bar(
+    //             menus,
+    //             app.delegate(),
+    //             actions,
+    //             keystroke_matcher,
+    //         ));
+    //     }
+    // }
+
+    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
+        let url = CFString::from(url);
+        let username = CFString::from(username);
+        let password = CFData::from_buffer(password);
+
+        unsafe {
+            use security::*;
+
+            // First, check if there are already credentials for the given server. If so, then
+            // update the username and password.
+            let mut verb = "updating";
+            let mut query_attrs = CFMutableDictionary::with_capacity(2);
+            query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+
+            let mut attrs = CFMutableDictionary::with_capacity(4);
+            attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+            attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
+            attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
+
+            let mut status = SecItemUpdate(
+                query_attrs.as_concrete_TypeRef(),
+                attrs.as_concrete_TypeRef(),
+            );
+
+            // If there were no existing credentials for the given server, then create them.
+            if status == errSecItemNotFound {
+                verb = "creating";
+                status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
+            }
+
+            if status != errSecSuccess {
+                return Err(anyhow!("{} password failed: {}", verb, status));
+            }
+        }
+        Ok(())
+    }
+
+    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
+        let url = CFString::from(url);
+        let cf_true = CFBoolean::true_value().as_CFTypeRef();
+
+        unsafe {
+            use security::*;
+
+            // Find any credentials for the given server URL.
+            let mut attrs = CFMutableDictionary::with_capacity(5);
+            attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+            attrs.set(kSecReturnAttributes as *const _, cf_true);
+            attrs.set(kSecReturnData as *const _, cf_true);
+
+            let mut result = CFTypeRef::from(ptr::null());
+            let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
+            match status {
+                security::errSecSuccess => {}
+                security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
+                _ => return Err(anyhow!("reading password failed: {}", status)),
+            }
+
+            let result = CFType::wrap_under_create_rule(result)
+                .downcast::<CFDictionary>()
+                .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
+            let username = result
+                .find(kSecAttrAccount as *const _)
+                .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
+            let username = CFType::wrap_under_get_rule(*username)
+                .downcast::<CFString>()
+                .ok_or_else(|| anyhow!("account was not a string"))?;
+            let password = result
+                .find(kSecValueData as *const _)
+                .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
+            let password = CFType::wrap_under_get_rule(*password)
+                .downcast::<CFData>()
+                .ok_or_else(|| anyhow!("password was not a string"))?;
+
+            Ok(Some((username.to_string(), password.bytes().to_vec())))
+        }
+    }
+
+    fn delete_credentials(&self, url: &str) -> Result<()> {
+        let url = CFString::from(url);
+
+        unsafe {
+            use security::*;
+
+            let mut query_attrs = CFMutableDictionary::with_capacity(2);
+            query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+
+            let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
+
+            if status != errSecSuccess {
+                return Err(anyhow!("delete password failed: {}", status));
+            }
+        }
+        Ok(())
+    }
+}
+
+unsafe fn path_from_objc(path: id) -> PathBuf {
+    let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+    let bytes = path.UTF8String() as *const u8;
+    let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+    PathBuf::from(path)
+}
+
+unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
+    let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
+    assert!(!platform_ptr.is_null());
+    &*(platform_ptr as *const MacPlatform)
+}
+
+extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
+    unsafe {
+        if let Some(event) = Event::from_native(native_event, None) {
+            let platform = get_foreground_platform(this);
+            if let Some(callback) = platform.0.lock().event.as_mut() {
+                if callback(event) {
+                    return;
+                }
+            }
+        }
+        msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
+    }
+}
+
+extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
+    unsafe {
+        let app: id = msg_send![APP_CLASS, sharedApplication];
+        app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
+
+        let platform = get_foreground_platform(this);
+        let callback = platform.0.lock().finish_launching.take();
+        if let Some(callback) = callback {
+            callback();
+        }
+    }
+}
+
+extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
+    if !has_open_windows {
+        let platform = unsafe { get_foreground_platform(this) };
+        if let Some(callback) = platform.0.lock().reopen.as_mut() {
+            callback();
+        }
+    }
+}
+
+extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.lock().become_active.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.lock().resign_active.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.lock().quit.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
+    let urls = unsafe {
+        (0..urls.count())
+            .into_iter()
+            .filter_map(|i| {
+                let url = urls.objectAtIndex(i);
+                match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
+                    Ok(string) => Some(string.to_string()),
+                    Err(err) => {
+                        log::error!("error converting path to string: {}", err);
+                        None
+                    }
+                }
+            })
+            .collect::<Vec<_>>()
+    };
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.lock().open_urls.as_mut() {
+        callback(urls);
+    }
+}
+
+extern "C" fn handle_menu_item(__this: &mut Object, _: Sel, __item: id) {
+    todo!()
+    // unsafe {
+    //     let platform = get_foreground_platform(this);
+    //     let mut platform = platform.0.lock();
+    //     if let Some(mut callback) = platform.menu_command.take() {
+    //         let tag: NSInteger = msg_send![item, tag];
+    //         let index = tag as usize;
+    //         if let Some(action) = platform.menu_actions.get(index) {
+    //             callback(action.as_ref());
+    //         }
+    //         platform.menu_command = Some(callback);
+    //     }
+    // }
+}
+
+extern "C" fn validate_menu_item(__this: &mut Object, _: Sel, __item: id) -> bool {
+    todo!()
+    // unsafe {
+    //     let mut result = false;
+    //     let platform = get_foreground_platform(this);
+    //     let mut platform = platform.0.lock();
+    //     if let Some(mut callback) = platform.validate_menu_command.take() {
+    //         let tag: NSInteger = msg_send![item, tag];
+    //         let index = tag as usize;
+    //         if let Some(action) = platform.menu_actions.get(index) {
+    //             result = callback(action.as_ref());
+    //         }
+    //         platform.validate_menu_command = Some(callback);
+    //     }
+    //     result
+    // }
+}
+
+extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
+    unsafe {
+        let platform = get_foreground_platform(this);
+        let mut platform = platform.0.lock();
+        if let Some(mut callback) = platform.will_open_menu.take() {
+            callback();
+            platform.will_open_menu = Some(callback);
+        }
+    }
+}
+
+unsafe fn ns_string(string: &str) -> id {
+    NSString::alloc(nil).init_str(string).autorelease()
+}
+
+unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
+    let path: *mut c_char = msg_send![url, fileSystemRepresentation];
+    if path.is_null() {
+        Err(anyhow!(
+            "url is not a file path: {}",
+            CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
+        ))
+    } else {
+        Ok(PathBuf::from(OsStr::from_bytes(
+            CStr::from_ptr(path).to_bytes(),
+        )))
+    }
+}
+
+mod security {
+    #![allow(non_upper_case_globals)]
+    use super::*;
+
+    #[link(name = "Security", kind = "framework")]
+    extern "C" {
+        pub static kSecClass: CFStringRef;
+        pub static kSecClassInternetPassword: CFStringRef;
+        pub static kSecAttrServer: CFStringRef;
+        pub static kSecAttrAccount: CFStringRef;
+        pub static kSecValueData: CFStringRef;
+        pub static kSecReturnAttributes: CFStringRef;
+        pub static kSecReturnData: CFStringRef;
+
+        pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
+        pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
+        pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
+        pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
+    }
+
+    pub const errSecSuccess: OSStatus = 0;
+    pub const errSecUserCanceled: OSStatus = -128;
+    pub const errSecItemNotFound: OSStatus = -25300;
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::ClipboardItem;
+
+    use super::*;
+
+    #[test]
+    fn test_clipboard() {
+        let platform = build_platform();
+        assert_eq!(platform.read_from_clipboard(), None);
+
+        let item = ClipboardItem::new("1".to_string());
+        platform.write_to_clipboard(item.clone());
+        assert_eq!(platform.read_from_clipboard(), Some(item));
+
+        let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
+        platform.write_to_clipboard(item.clone());
+        assert_eq!(platform.read_from_clipboard(), Some(item));
+
+        let text_from_other_app = "text from other app";
+        unsafe {
+            let bytes = NSData::dataWithBytes_length_(
+                nil,
+                text_from_other_app.as_ptr() as *const c_void,
+                text_from_other_app.len() as u64,
+            );
+            platform
+                .0
+                .lock()
+                .pasteboard
+                .setData_forType(bytes, NSPasteboardTypeString);
+        }
+        assert_eq!(
+            platform.read_from_clipboard(),
+            Some(ClipboardItem::new(text_from_other_app.to_string()))
+        );
+    }
+
+    fn build_platform() -> MacPlatform {
+        let platform = MacPlatform::new();
+        platform.0.lock().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
+        platform
+    }
+}

crates/gpui3/src/platform/mac/screen.rs 🔗

@@ -0,0 +1,156 @@
+use super::ns_string;
+use crate::{point, px, size, Bounds, Pixels, PlatformScreen, PlatformScreenHandle, ScreenId};
+use cocoa::{
+    appkit::NSScreen,
+    base::{id, nil},
+    foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
+};
+use core_foundation::{
+    number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
+    uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
+};
+use core_graphics::display::CGDirectDisplayID;
+use objc::runtime::Object;
+use std::{any::Any, ffi::c_void};
+use uuid::Uuid;
+
+#[link(name = "ApplicationServices", kind = "framework")]
+extern "C" {
+    pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
+}
+
+#[derive(Debug)]
+pub struct MacScreen {
+    pub(crate) native_screen: id,
+}
+
+unsafe impl Send for MacScreen {}
+
+impl MacScreen {
+    pub(crate) fn from_handle(handle: PlatformScreenHandle) -> Self {
+        Self {
+            native_screen: handle.0 as *mut Object,
+        }
+    }
+
+    /// Get the screen with the given UUID.
+    pub fn find_by_id(id: ScreenId) -> Option<Self> {
+        Self::all().find(|screen| screen.id() == Some(id))
+    }
+
+    /// Get the primary screen - the one with the menu bar, and whose bottom left
+    /// corner is at the origin of the AppKit coordinate system.
+    fn primary() -> Self {
+        Self::all().next().unwrap()
+    }
+
+    pub fn all() -> impl Iterator<Item = Self> {
+        unsafe {
+            let native_screens = NSScreen::screens(nil);
+            (0..NSArray::count(native_screens)).map(move |ix| MacScreen {
+                native_screen: native_screens.objectAtIndex(ix),
+            })
+        }
+    }
+
+    /// Convert the given rectangle in screen coordinates from GPUI's
+    /// coordinate system to the AppKit coordinate system.
+    ///
+    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
+    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
+    /// bottom left of the primary screen, with the Y axis pointing upward.
+    pub(crate) fn screen_bounds_to_native(bounds: Bounds<Pixels>) -> NSRect {
+        let primary_screen_height =
+            px(unsafe { Self::primary().native_screen.frame().size.height } as f32);
+
+        NSRect::new(
+            NSPoint::new(
+                bounds.origin.x.into(),
+                (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
+            ),
+            NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
+        )
+    }
+
+    /// Convert the given rectangle in screen coordinates from the AppKit
+    /// coordinate system to GPUI's coordinate system.
+    ///
+    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
+    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
+    /// bottom left of the primary screen, with the Y axis pointing upward.
+    pub(crate) fn screen_bounds_from_native(rect: NSRect) -> Bounds<Pixels> {
+        let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
+        Bounds {
+            origin: point(
+                px(rect.origin.x as f32),
+                px((primary_screen_height - rect.origin.y - rect.size.height) as f32),
+            ),
+            size: size(px(rect.size.width as f32), px(rect.size.height as f32)),
+        }
+    }
+}
+
+impl PlatformScreen for MacScreen {
+    fn id(&self) -> Option<ScreenId> {
+        unsafe {
+            // This approach is similar to that which winit takes
+            // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
+            let device_description = self.native_screen.deviceDescription();
+
+            let key = ns_string("NSScreenNumber");
+            let device_id_obj = device_description.objectForKey_(key);
+            if device_id_obj.is_null() {
+                // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
+                // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
+                return None;
+            }
+
+            let mut device_id: u32 = 0;
+            CFNumberGetValue(
+                device_id_obj as CFNumberRef,
+                kCFNumberIntType,
+                (&mut device_id) as *mut _ as *mut c_void,
+            );
+            let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
+            if cfuuid.is_null() {
+                return None;
+            }
+
+            let bytes = CFUUIDGetUUIDBytes(cfuuid);
+            Some(ScreenId(Uuid::from_bytes([
+                bytes.byte0,
+                bytes.byte1,
+                bytes.byte2,
+                bytes.byte3,
+                bytes.byte4,
+                bytes.byte5,
+                bytes.byte6,
+                bytes.byte7,
+                bytes.byte8,
+                bytes.byte9,
+                bytes.byte10,
+                bytes.byte11,
+                bytes.byte12,
+                bytes.byte13,
+                bytes.byte14,
+                bytes.byte15,
+            ])))
+        }
+    }
+
+    fn handle(&self) -> PlatformScreenHandle {
+        PlatformScreenHandle(self.native_screen as *mut c_void)
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn bounds(&self) -> Bounds<Pixels> {
+        unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) }
+    }
+
+    fn content_bounds(&self) -> Bounds<Pixels> {
+        unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) }
+    }
+}

crates/gpui3/src/platform/mac/shaders.metal 🔗

@@ -0,0 +1,180 @@
+#include <metal_stdlib>
+#include <simd/simd.h>
+
+using namespace metal;
+
+float4 hsla_to_rgba(Hsla hsla);
+float4 to_device_position(float2 pixel_position, float2 viewport_size);
+
+struct QuadVertexOutput {
+    float4 position [[position]];
+    float4 background_color;
+    float4 border_color;
+    uint quad_id;
+};
+
+vertex QuadVertexOutput quad_vertex(
+    uint unit_vertex_id [[vertex_id]],
+    uint quad_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(QuadInputIndex_Vertices)]],
+    constant Quad *quads [[buffer(QuadInputIndex_Quads)]],
+    constant QuadUniforms *uniforms [[buffer(QuadInputIndex_Uniforms)]]
+) {
+    float2 unit_vertex = unit_vertices[unit_vertex_id];
+    Quad quad = quads[quad_id];
+    float2 position_2d = unit_vertex * float2(quad.bounds.size.width, quad.bounds.size.height) + float2(quad.bounds.origin.x, quad.bounds.origin.y);
+    position_2d.x = max(quad.clip_bounds.origin.x, position_2d.x);
+    position_2d.x = min(quad.clip_bounds.origin.x + quad.clip_bounds.size.width, position_2d.x);
+    position_2d.y = max(quad.clip_bounds.origin.y, position_2d.y);
+    position_2d.y = min(quad.clip_bounds.origin.y + quad.clip_bounds.size.height, position_2d.y);
+
+    float2 viewport_size = float2((float)uniforms->viewport_size.width, (float)uniforms->viewport_size.height);
+    float4 device_position = to_device_position(position_2d, viewport_size);
+    float4 background_color = hsla_to_rgba(quad.background);
+    float4 border_color = hsla_to_rgba(quad.border_color);
+    return QuadVertexOutput {
+        device_position,
+        background_color,
+        border_color,
+        quad_id
+    };
+}
+
+float quad_sdf(float2 point, Bounds_Pixels bounds, Corners_Pixels corner_radii) {
+    float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
+    float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
+    float2 center_to_point = point - center;
+    float corner_radius;
+    if (center_to_point.x < 0.) {
+        if (center_to_point.y < 0.) {
+            corner_radius = corner_radii.top_left;
+        } else {
+            corner_radius = corner_radii.bottom_left;
+        }
+    } else {
+        if (center_to_point.y < 0.) {
+            corner_radius = corner_radii.top_right;
+        } else {
+            corner_radius = corner_radii.bottom_right;
+        }
+    }
+
+    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
+    float distance = length(max(0., rounded_edge_to_point))
+                     + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y))
+                     - corner_radius;
+
+    return distance;
+}
+
+fragment float4 quad_fragment(
+    QuadVertexOutput input [[stage_in]],
+    constant Quad *quads [[buffer(QuadInputIndex_Quads)]]
+) {
+    Quad quad = quads[input.quad_id];
+    float2 half_size = float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
+    float2 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
+    float2 center_to_point = input.position.xy - center;
+    float corner_radius;
+    if (center_to_point.x < 0.) {
+        if (center_to_point.y < 0.) {
+            corner_radius = quad.corner_radii.top_left;
+        } else {
+            corner_radius = quad.corner_radii.bottom_left;
+        }
+    } else {
+        if (center_to_point.y < 0.) {
+            corner_radius = quad.corner_radii.top_right;
+        } else {
+            corner_radius = quad.corner_radii.bottom_right;
+        }
+    }
+
+    float2 rounded_edge_to_point = fabs(center_to_point) - half_size + corner_radius;
+    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
+
+    float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left : quad.border_widths.right;
+    float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top : quad.border_widths.bottom;
+    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
+    float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
+    float border_width;
+    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
+        border_width = 0.;
+    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
+        border_width = horizontal_border;
+    } else {
+        border_width = vertical_border;
+    }
+
+    float4 color;
+    if (border_width == 0.) {
+        color = input.background_color;
+    } else {
+        float inset_distance = distance + border_width;
+
+        // Decrease border's opacity as we move inside the background.
+        input.border_color.a *= 1. - saturate(0.5 - inset_distance);
+
+        // Alpha-blend the border and the background.
+        float output_alpha = quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
+        float3 premultiplied_border_rgb = input.border_color.rgb * quad.border_color.a;
+        float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
+        float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
+        color = float4(premultiplied_output_rgb, output_alpha);
+    }
+
+    float clip_distance = quad_sdf(input.position.xy, quad.clip_bounds, quad.clip_corner_radii);
+    return color * float4(1., 1., 1., saturate(0.5 - distance) * saturate(0.5 - clip_distance));
+}
+
+float4 hsla_to_rgba(Hsla hsla) {
+    float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
+    float s = hsla.s;
+    float l = hsla.l;
+    float a = hsla.a;
+
+    float c = (1.0 - fabs(2.0*l - 1.0)) * s;
+    float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
+    float m = l - c/2.0;
+
+    float r = 0.0;
+    float g = 0.0;
+    float b = 0.0;
+
+    if (h >= 0.0 && h < 1.0) {
+        r = c;
+        g = x;
+        b = 0.0;
+    } else if (h >= 1.0 && h < 2.0) {
+        r = x;
+        g = c;
+        b = 0.0;
+    } else if (h >= 2.0 && h < 3.0) {
+        r = 0.0;
+        g = c;
+        b = x;
+    } else if (h >= 3.0 && h < 4.0) {
+        r = 0.0;
+        g = x;
+        b = c;
+    } else if (h >= 4.0 && h < 5.0) {
+        r = x;
+        g = 0.0;
+        b = c;
+    } else {
+        r = c;
+        g = 0.0;
+        b = x;
+    }
+
+    float4 rgba;
+    rgba.x = (r + m);
+    rgba.y = (g + m);
+    rgba.z = (b + m);
+    rgba.w = a;
+    return rgba;
+}
+
+float4 to_device_position(float2 pixel_position, float2 viewport_size) {
+    return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
+}

crates/gpui3/src/platform/mac/text_system.rs 🔗

@@ -0,0 +1,754 @@
+use crate::{
+    point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
+    GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, Run,
+    SharedString, Size,
+};
+use cocoa::appkit::{CGFloat, CGPoint};
+use collections::HashMap;
+use core_foundation::{
+    array::CFIndex,
+    attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
+    base::{CFRange, TCFType},
+    string::CFString,
+};
+use core_graphics::{
+    base::{kCGImageAlphaPremultipliedLast, CGGlyph},
+    color_space::CGColorSpace,
+    context::CGContext,
+};
+use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
+use font_kit::{
+    font::Font as FontKitFont,
+    handle::Handle,
+    hinting::HintingOptions,
+    metrics::Metrics,
+    properties::{Style as FontkitStyle, Weight as FontkitWeight},
+    source::SystemSource,
+    sources::mem::MemSource,
+};
+use parking_lot::{RwLock, RwLockUpgradableReadGuard};
+use pathfinder_geometry::{
+    rect::{RectF, RectI},
+    transform2d::Transform2F,
+    vector::{Vector2F, Vector2I},
+};
+use smallvec::SmallVec;
+use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
+
+use super::open_type;
+
+#[allow(non_upper_case_globals)]
+const kCGImageAlphaOnly: u32 = 7;
+
+pub struct MacTextSystem(RwLock<MacTextSystemState>);
+
+struct MacTextSystemState {
+    memory_source: MemSource,
+    system_source: SystemSource,
+    fonts: Vec<FontKitFont>,
+    font_selections: HashMap<Font, FontId>,
+    font_ids_by_postscript_name: HashMap<String, FontId>,
+    font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
+    postscript_names_by_font_id: HashMap<FontId, String>,
+}
+
+impl MacTextSystem {
+    pub fn new() -> Self {
+        Self(RwLock::new(MacTextSystemState {
+            memory_source: MemSource::empty(),
+            system_source: SystemSource::new(),
+            fonts: Vec::new(),
+            font_selections: HashMap::default(),
+            font_ids_by_postscript_name: HashMap::default(),
+            font_ids_by_family_name: HashMap::default(),
+            postscript_names_by_font_id: HashMap::default(),
+        }))
+    }
+}
+
+impl Default for MacTextSystem {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl PlatformTextSystem for MacTextSystem {
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
+        self.0.write().add_fonts(fonts)
+    }
+
+    fn all_font_families(&self) -> Vec<String> {
+        self.0
+            .read()
+            .system_source
+            .all_families()
+            .expect("core text should never return an error")
+    }
+
+    fn font_id(&self, font: &Font) -> Result<FontId> {
+        let lock = self.0.upgradable_read();
+        if let Some(font_id) = lock.font_selections.get(font) {
+            Ok(*font_id)
+        } else {
+            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
+            let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
+            {
+                font_ids.as_slice()
+            } else {
+                let font_ids = lock.load_family(&font.family, font.features)?;
+                lock.font_ids_by_family_name
+                    .insert(font.family.clone(), font_ids);
+                lock.font_ids_by_family_name[&font.family].as_ref()
+            };
+
+            let candidate_properties = candidates
+                .iter()
+                .map(|font_id| lock.fonts[font_id.0].properties())
+                .collect::<SmallVec<[_; 4]>>();
+
+            let ix = font_kit::matching::find_best_match(
+                &candidate_properties,
+                &font_kit::properties::Properties {
+                    style: font.style.into(),
+                    weight: font.weight.into(),
+                    stretch: Default::default(),
+                },
+            )?;
+
+            Ok(candidates[ix])
+        }
+    }
+
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics {
+        self.0.read().fonts[font_id.0].metrics().into()
+    }
+
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
+        Ok(self.0.read().fonts[font_id.0]
+            .typographic_bounds(glyph_id.into())?
+            .into())
+    }
+
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
+        self.0.read().advance(font_id, glyph_id)
+    }
+
+    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
+        self.0.read().glyph_for_char(font_id, ch)
+    }
+
+    fn rasterize_glyph(
+        &self,
+        font_id: FontId,
+        font_size: f32,
+        glyph_id: GlyphId,
+        subpixel_shift: Point<Pixels>,
+        scale_factor: f32,
+        options: RasterizationOptions,
+    ) -> Option<(Bounds<u32>, Vec<u8>)> {
+        self.0.read().rasterize_glyph(
+            font_id,
+            font_size,
+            glyph_id,
+            subpixel_shift,
+            scale_factor,
+            options,
+        )
+    }
+
+    fn layout_line(
+        &self,
+        text: &str,
+        font_size: Pixels,
+        font_runs: &[(usize, FontId)],
+    ) -> LineLayout {
+        self.0.write().layout_line(text, font_size, font_runs)
+    }
+
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize> {
+        self.0.read().wrap_line(text, font_id, font_size, width)
+    }
+}
+
+impl MacTextSystemState {
+    fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
+        self.memory_source.add_fonts(
+            fonts
+                .iter()
+                .map(|bytes| Handle::from_memory(bytes.clone(), 0)),
+        )?;
+        Ok(())
+    }
+
+    fn load_family(
+        &mut self,
+        name: &SharedString,
+        features: FontFeatures,
+    ) -> Result<SmallVec<[FontId; 4]>> {
+        let mut font_ids = SmallVec::new();
+        let family = self
+            .memory_source
+            .select_family_by_name(name.as_ref())
+            .or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
+        for font in family.fonts() {
+            let mut font = font.load()?;
+            open_type::apply_features(&mut font, features);
+            let font_id = FontId(self.fonts.len());
+            font_ids.push(font_id);
+            let postscript_name = font.postscript_name().unwrap();
+            self.font_ids_by_postscript_name
+                .insert(postscript_name.clone(), font_id);
+            self.postscript_names_by_font_id
+                .insert(font_id, postscript_name);
+            self.fonts.push(font);
+        }
+        Ok(font_ids)
+    }
+
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
+        Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
+    }
+
+    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
+        self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
+    }
+
+    fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
+        let postscript_name = requested_font.postscript_name();
+        if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
+            *font_id
+        } else {
+            let font_id = FontId(self.fonts.len());
+            self.font_ids_by_postscript_name
+                .insert(postscript_name.clone(), font_id);
+            self.postscript_names_by_font_id
+                .insert(font_id, postscript_name);
+            self.fonts
+                .push(font_kit::font::Font::from_core_graphics_font(
+                    requested_font.copy_to_CGFont(),
+                ));
+            font_id
+        }
+    }
+
+    fn is_emoji(&self, font_id: FontId) -> bool {
+        self.postscript_names_by_font_id
+            .get(&font_id)
+            .map_or(false, |postscript_name| {
+                postscript_name == "AppleColorEmoji"
+            })
+    }
+
+    fn rasterize_glyph(
+        &self,
+        font_id: FontId,
+        font_size: f32,
+        glyph_id: GlyphId,
+        subpixel_shift: Point<Pixels>,
+        scale_factor: f32,
+        options: RasterizationOptions,
+    ) -> Option<(Bounds<u32>, Vec<u8>)> {
+        let font = &self.fonts[font_id.0];
+        let scale = Transform2F::from_scale(scale_factor);
+        let glyph_bounds = font
+            .raster_bounds(
+                glyph_id.into(),
+                font_size,
+                scale,
+                HintingOptions::None,
+                font_kit::canvas::RasterizationOptions::GrayscaleAa,
+            )
+            .ok()?;
+
+        if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
+            None
+        } else {
+            // Make room for subpixel variants.
+            let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
+            let cx_bounds = RectI::new(
+                glyph_bounds.origin(),
+                glyph_bounds.size() + Vector2I::from(subpixel_padding),
+            );
+
+            let mut bytes;
+            let cx;
+            match options {
+                RasterizationOptions::Alpha => {
+                    bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
+                    cx = CGContext::create_bitmap_context(
+                        Some(bytes.as_mut_ptr() as *mut _),
+                        cx_bounds.width() as usize,
+                        cx_bounds.height() as usize,
+                        8,
+                        cx_bounds.width() as usize,
+                        &CGColorSpace::create_device_gray(),
+                        kCGImageAlphaOnly,
+                    );
+                }
+                RasterizationOptions::Bgra => {
+                    bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
+                    cx = CGContext::create_bitmap_context(
+                        Some(bytes.as_mut_ptr() as *mut _),
+                        cx_bounds.width() as usize,
+                        cx_bounds.height() as usize,
+                        8,
+                        cx_bounds.width() as usize * 4,
+                        &CGColorSpace::create_device_rgb(),
+                        kCGImageAlphaPremultipliedLast,
+                    );
+                }
+            }
+
+            // Move the origin to bottom left and account for scaling, this
+            // makes drawing text consistent with the font-kit's raster_bounds.
+            cx.translate(
+                -glyph_bounds.origin_x() as CGFloat,
+                (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
+            );
+            cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
+
+            cx.set_allows_font_subpixel_positioning(true);
+            cx.set_should_subpixel_position_fonts(true);
+            cx.set_allows_font_subpixel_quantization(false);
+            cx.set_should_subpixel_quantize_fonts(false);
+            font.native_font()
+                .clone_with_font_size(font_size as CGFloat)
+                .draw_glyphs(
+                    &[u32::from(glyph_id) as CGGlyph],
+                    &[CGPoint::new(
+                        (f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
+                        (f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
+                    )],
+                    cx,
+                );
+
+            if let RasterizationOptions::Bgra = options {
+                // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
+                for pixel in bytes.chunks_exact_mut(4) {
+                    pixel.swap(0, 2);
+                    let a = pixel[3] as f32 / 255.;
+                    pixel[0] = (pixel[0] as f32 / a) as u8;
+                    pixel[1] = (pixel[1] as f32 / a) as u8;
+                    pixel[2] = (pixel[2] as f32 / a) as u8;
+                }
+            }
+
+            Some((cx_bounds.into(), bytes))
+        }
+    }
+
+    fn layout_line(
+        &mut self,
+        text: &str,
+        font_size: Pixels,
+        font_runs: &[(usize, FontId)],
+    ) -> LineLayout {
+        // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
+        let mut string = CFMutableAttributedString::new();
+        {
+            string.replace_str(&CFString::new(text), CFRange::init(0, 0));
+            let utf16_line_len = string.char_len() as usize;
+
+            let mut ix_converter = StringIndexConverter::new(text);
+            for (run_len, font_id) in font_runs {
+                let utf8_end = ix_converter.utf8_ix + run_len;
+                let utf16_start = ix_converter.utf16_ix;
+
+                if utf16_start >= utf16_line_len {
+                    break;
+                }
+
+                ix_converter.advance_to_utf8_ix(utf8_end);
+                let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
+
+                let cf_range =
+                    CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
+
+                let font: &FontKitFont = &self.fonts[font_id.0];
+                unsafe {
+                    string.set_attribute(
+                        cf_range,
+                        kCTFontAttributeName,
+                        &font.native_font().clone_with_font_size(font_size.into()),
+                    );
+                }
+
+                if utf16_end == utf16_line_len {
+                    break;
+                }
+            }
+        }
+
+        // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
+        let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
+
+        let mut runs = Vec::new();
+        for run in line.glyph_runs().into_iter() {
+            let attributes = run.attributes().unwrap();
+            let font = unsafe {
+                attributes
+                    .get(kCTFontAttributeName)
+                    .downcast::<CTFont>()
+                    .unwrap()
+            };
+            let font_id = self.id_for_native_font(font);
+
+            let mut ix_converter = StringIndexConverter::new(text);
+            let mut glyphs = Vec::new();
+            for ((glyph_id, position), glyph_utf16_ix) in run
+                .glyphs()
+                .iter()
+                .zip(run.positions().iter())
+                .zip(run.string_indices().iter())
+            {
+                let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
+                ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
+                glyphs.push(Glyph {
+                    id: (*glyph_id).into(),
+                    position: point(position.x as f32, position.y as f32).map(px),
+                    index: ix_converter.utf8_ix,
+                    is_emoji: self.is_emoji(font_id),
+                });
+            }
+
+            runs.push(Run { font_id, glyphs })
+        }
+
+        let typographic_bounds = line.get_typographic_bounds();
+        LineLayout {
+            width: typographic_bounds.width.into(),
+            ascent: typographic_bounds.ascent.into(),
+            descent: typographic_bounds.descent.into(),
+            runs,
+            font_size,
+            len: text.len(),
+        }
+    }
+
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize> {
+        let mut string = CFMutableAttributedString::new();
+        string.replace_str(&CFString::new(text), CFRange::init(0, 0));
+        let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
+        let font = &self.fonts[font_id.0];
+        unsafe {
+            string.set_attribute(
+                cf_range,
+                kCTFontAttributeName,
+                &font.native_font().clone_with_font_size(font_size.into()),
+            );
+
+            let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
+            let mut ix_converter = StringIndexConverter::new(text);
+            let mut break_indices = Vec::new();
+            while ix_converter.utf8_ix < text.len() {
+                let utf16_len = CTTypesetterSuggestLineBreak(
+                    typesetter,
+                    ix_converter.utf16_ix as isize,
+                    width.into(),
+                ) as usize;
+                ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
+                if ix_converter.utf8_ix >= text.len() {
+                    break;
+                }
+                break_indices.push(ix_converter.utf8_ix as usize);
+            }
+            break_indices
+        }
+    }
+}
+
+#[derive(Clone)]
+struct StringIndexConverter<'a> {
+    text: &'a str,
+    utf8_ix: usize,
+    utf16_ix: usize,
+}
+
+impl<'a> StringIndexConverter<'a> {
+    fn new(text: &'a str) -> Self {
+        Self {
+            text,
+            utf8_ix: 0,
+            utf16_ix: 0,
+        }
+    }
+
+    fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
+        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
+            if self.utf8_ix + ix >= utf8_target {
+                self.utf8_ix += ix;
+                return;
+            }
+            self.utf16_ix += c.len_utf16();
+        }
+        self.utf8_ix = self.text.len();
+    }
+
+    fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
+        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
+            if self.utf16_ix >= utf16_target {
+                self.utf8_ix += ix;
+                return;
+            }
+            self.utf16_ix += c.len_utf16();
+        }
+        self.utf8_ix = self.text.len();
+    }
+}
+
+#[repr(C)]
+pub struct __CFTypesetter(c_void);
+
+pub type CTTypesetterRef = *const __CFTypesetter;
+
+#[link(name = "CoreText", kind = "framework")]
+extern "C" {
+    fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
+
+    fn CTTypesetterSuggestLineBreak(
+        typesetter: CTTypesetterRef,
+        start_index: CFIndex,
+        width: f64,
+    ) -> CFIndex;
+}
+
+impl From<Metrics> for FontMetrics {
+    fn from(metrics: Metrics) -> Self {
+        FontMetrics {
+            units_per_em: metrics.units_per_em,
+            ascent: metrics.ascent,
+            descent: metrics.descent,
+            line_gap: metrics.line_gap,
+            underline_position: metrics.underline_position,
+            underline_thickness: metrics.underline_thickness,
+            cap_height: metrics.cap_height,
+            x_height: metrics.x_height,
+            bounding_box: metrics.bounding_box.into(),
+        }
+    }
+}
+
+impl From<RectF> for Bounds<f32> {
+    fn from(rect: RectF) -> Self {
+        Bounds {
+            origin: point(rect.origin_x(), rect.origin_y()),
+            size: size(rect.width(), rect.height()),
+        }
+    }
+}
+
+impl From<RectI> for Bounds<u32> {
+    fn from(rect: RectI) -> Self {
+        Bounds {
+            origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
+            size: size(rect.width() as u32, rect.height() as u32),
+        }
+    }
+}
+
+impl From<Point<u32>> for Vector2I {
+    fn from(size: Point<u32>) -> Self {
+        Vector2I::new(size.x as i32, size.y as i32)
+    }
+}
+
+impl From<Vector2F> for Size<f32> {
+    fn from(vec: Vector2F) -> Self {
+        size(vec.x(), vec.y())
+    }
+}
+
+impl From<FontWeight> for FontkitWeight {
+    fn from(value: FontWeight) -> Self {
+        FontkitWeight(value.0)
+    }
+}
+
+impl From<FontStyle> for FontkitStyle {
+    fn from(style: FontStyle) -> Self {
+        match style {
+            FontStyle::Normal => FontkitStyle::Normal,
+            FontStyle::Italic => FontkitStyle::Italic,
+            FontStyle::Oblique => FontkitStyle::Oblique,
+        }
+    }
+}
+
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use crate::AppContext;
+//     use font_kit::properties::{Style, Weight};
+//     use platform::FontSystem as _;
+
+//     #[crate::test(self, retries = 5)]
+//     fn test_layout_str(_: &mut AppContext) {
+//         // This is failing intermittently on CI and we don't have time to figure it out
+//         let fonts = FontSystem::new();
+//         let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
+//         let menlo_regular = RunStyle {
+//             font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         let menlo_italic = RunStyle {
+//             font_id: fonts
+//                 .select_font(&menlo, Properties::new().style(Style::Italic))
+//                 .unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         let menlo_bold = RunStyle {
+//             font_id: fonts
+//                 .select_font(&menlo, Properties::new().weight(Weight::BOLD))
+//                 .unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         assert_ne!(menlo_regular, menlo_italic);
+//         assert_ne!(menlo_regular, menlo_bold);
+//         assert_ne!(menlo_italic, menlo_bold);
+
+//         let line = fonts.layout_line(
+//             "hello world",
+//             16.0,
+//             &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
+//         );
+//         assert_eq!(line.runs.len(), 3);
+//         assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
+//         assert_eq!(line.runs[0].glyphs.len(), 2);
+//         assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
+//         assert_eq!(line.runs[1].glyphs.len(), 4);
+//         assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
+//         assert_eq!(line.runs[2].glyphs.len(), 5);
+//     }
+
+//     #[test]
+//     fn test_glyph_offsets() -> crate::Result<()> {
+//         let fonts = FontSystem::new();
+//         let zapfino = fonts.load_family("Zapfino", &Default::default())?;
+//         let zapfino_regular = RunStyle {
+//             font_id: fonts.select_font(&zapfino, &Properties::new())?,
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         let menlo = fonts.load_family("Menlo", &Default::default())?;
+//         let menlo_regular = RunStyle {
+//             font_id: fonts.select_font(&menlo, &Properties::new())?,
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+
+//         let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
+//         let line = fonts.layout_line(
+//             text,
+//             16.0,
+//             &[
+//                 (9, zapfino_regular),
+//                 (13, menlo_regular),
+//                 (text.len() - 22, zapfino_regular),
+//             ],
+//         );
+//         assert_eq!(
+//             line.runs
+//                 .iter()
+//                 .flat_map(|r| r.glyphs.iter())
+//                 .map(|g| g.index)
+//                 .collect::<Vec<_>>(),
+//             vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
+//         );
+//         Ok(())
+//     }
+
+//     #[test]
+//     #[ignore]
+//     fn test_rasterize_glyph() {
+//         use std::{fs::File, io::BufWriter, path::Path};
+
+//         let fonts = FontSystem::new();
+//         let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
+//         let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
+//         let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
+
+//         const VARIANTS: usize = 1;
+//         for i in 0..VARIANTS {
+//             let variant = i as f32 / VARIANTS as f32;
+//             let (bounds, bytes) = fonts
+//                 .rasterize_glyph(
+//                     font_id,
+//                     16.0,
+//                     glyph_id,
+//                     vec2f(variant, variant),
+//                     2.,
+//                     RasterizationOptions::Alpha,
+//                 )
+//                 .unwrap();
+
+//             let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
+//             let path = Path::new(&name);
+//             let file = File::create(path).unwrap();
+//             let w = &mut BufWriter::new(file);
+
+//             let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
+//             encoder.set_color(png::ColorType::Grayscale);
+//             encoder.set_depth(png::BitDepth::Eight);
+//             let mut writer = encoder.write_header().unwrap();
+//             writer.write_image_data(&bytes).unwrap();
+//         }
+//     }
+
+//     #[test]
+//     fn test_wrap_line() {
+//         let fonts = FontSystem::new();
+//         let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
+//         let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
+
+//         let line = "one two three four five\n";
+//         let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
+//         assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
+
+//         let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
+//         let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
+//         assert_eq!(
+//             wrap_boundaries,
+//             &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
+//         );
+//     }
+
+//     #[test]
+//     fn test_layout_line_bom_char() {
+//         let fonts = FontSystem::new();
+//         let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
+//         let style = RunStyle {
+//             font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+
+//         let line = "\u{feff}";
+//         let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
+//         assert_eq!(layout.len, line.len());
+//         assert!(layout.runs.is_empty());
+
+//         let line = "a\u{feff}b";
+//         let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
+//         assert_eq!(layout.len, line.len());
+//         assert_eq!(layout.runs.len(), 1);
+//         assert_eq!(layout.runs[0].glyphs.len(), 2);
+//         assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
+//                                                      // There's no glyph for \u{feff}
+//         assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
+//     }
+// }

crates/gpui3/src/platform/mac/window.rs 🔗

@@ -0,0 +1,1595 @@
+use super::{ns_string, MetalRenderer, NSRange};
+use crate::{
+    point, px, size, AnyWindowHandle, Bounds, Event, InputHandler, KeyDownEvent, Keystroke,
+    MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent,
+    MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen, PlatformWindow,
+    Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
+    WindowPromptLevel,
+};
+use block::ConcreteBlock;
+use cocoa::{
+    appkit::{
+        CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
+        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior,
+        NSWindowStyleMask, NSWindowTitleVisibility,
+    },
+    base::{id, nil},
+    foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
+};
+use core_graphics::display::CGRect;
+use ctor::ctor;
+use foreign_types::ForeignTypeRef;
+use futures::channel::oneshot;
+use objc::{
+    class,
+    declare::ClassDecl,
+    msg_send,
+    runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
+    sel, sel_impl,
+};
+use parking_lot::Mutex;
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    ffi::{c_void, CStr},
+    mem,
+    ops::Range,
+    os::raw::c_char,
+    ptr,
+    rc::Rc,
+    sync::{Arc, Weak},
+    time::Duration,
+};
+
+const WINDOW_STATE_IVAR: &str = "windowState";
+
+static mut WINDOW_CLASS: *const Class = ptr::null();
+static mut PANEL_CLASS: *const Class = ptr::null();
+static mut VIEW_CLASS: *const Class = ptr::null();
+
+#[allow(non_upper_case_globals)]
+const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
+    unsafe { NSWindowStyleMask::from_bits_unchecked(1 << 7) };
+#[allow(non_upper_case_globals)]
+const NSNormalWindowLevel: NSInteger = 0;
+#[allow(non_upper_case_globals)]
+const NSPopUpWindowLevel: NSInteger = 101;
+#[allow(non_upper_case_globals)]
+const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
+#[allow(non_upper_case_globals)]
+const NSTrackingMouseMoved: NSUInteger = 0x02;
+#[allow(non_upper_case_globals)]
+const NSTrackingActiveAlways: NSUInteger = 0x80;
+#[allow(non_upper_case_globals)]
+const NSTrackingInVisibleRect: NSUInteger = 0x200;
+#[allow(non_upper_case_globals)]
+const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
+#[allow(non_upper_case_globals)]
+const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+
+#[ctor]
+unsafe fn build_classes() {
+    WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
+    PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
+    VIEW_CLASS = {
+        let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
+        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+
+        decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
+
+        decl.add_method(
+            sel!(performKeyEquivalent:),
+            handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
+        );
+        decl.add_method(
+            sel!(keyDown:),
+            handle_key_down as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseDown:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseUp:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(rightMouseDown:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(rightMouseUp:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(otherMouseDown:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(otherMouseUp:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseMoved:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseExited:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseDragged:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(scrollWheel:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(flagsChanged:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(cancelOperation:),
+            cancel_operation as extern "C" fn(&Object, Sel, id),
+        );
+
+        decl.add_method(
+            sel!(makeBackingLayer),
+            make_backing_layer as extern "C" fn(&Object, Sel) -> id,
+        );
+
+        decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
+        decl.add_method(
+            sel!(viewDidChangeBackingProperties),
+            view_did_change_backing_properties as extern "C" fn(&Object, Sel),
+        );
+        decl.add_method(
+            sel!(setFrameSize:),
+            set_frame_size as extern "C" fn(&Object, Sel, NSSize),
+        );
+        decl.add_method(
+            sel!(displayLayer:),
+            display_layer as extern "C" fn(&Object, Sel, id),
+        );
+
+        decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
+        decl.add_method(
+            sel!(validAttributesForMarkedText),
+            valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
+        );
+        decl.add_method(
+            sel!(hasMarkedText),
+            has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
+        );
+        decl.add_method(
+            sel!(markedRange),
+            marked_range as extern "C" fn(&Object, Sel) -> NSRange,
+        );
+        decl.add_method(
+            sel!(selectedRange),
+            selected_range as extern "C" fn(&Object, Sel) -> NSRange,
+        );
+        decl.add_method(
+            sel!(firstRectForCharacterRange:actualRange:),
+            first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
+        );
+        decl.add_method(
+            sel!(insertText:replacementRange:),
+            insert_text as extern "C" fn(&Object, Sel, id, NSRange),
+        );
+        decl.add_method(
+            sel!(setMarkedText:selectedRange:replacementRange:),
+            set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
+        );
+        decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
+        decl.add_method(
+            sel!(attributedSubstringForProposedRange:actualRange:),
+            attributed_substring_for_proposed_range
+                as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
+        );
+        decl.add_method(
+            sel!(viewDidChangeEffectiveAppearance),
+            view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
+        );
+
+        // Suppress beep on keystrokes with modifier keys.
+        decl.add_method(
+            sel!(doCommandBySelector:),
+            do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
+        );
+
+        decl.add_method(
+            sel!(acceptsFirstMouse:),
+            accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
+        );
+
+        decl.register()
+    };
+}
+
+pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
+    point(
+        px(position.x as f32),
+        // MacOS screen coordinates are relative to bottom left
+        window_height - px(position.y as f32),
+    )
+}
+
+unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
+    let mut decl = ClassDecl::new(name, superclass).unwrap();
+    decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+    decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
+    decl.add_method(
+        sel!(canBecomeMainWindow),
+        yes as extern "C" fn(&Object, Sel) -> BOOL,
+    );
+    decl.add_method(
+        sel!(canBecomeKeyWindow),
+        yes as extern "C" fn(&Object, Sel) -> BOOL,
+    );
+    decl.add_method(
+        sel!(windowDidResize:),
+        window_did_resize as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowWillEnterFullScreen:),
+        window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowWillExitFullScreen:),
+        window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidMove:),
+        window_did_move as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidBecomeKey:),
+        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowDidResignKey:),
+        window_did_change_key_status as extern "C" fn(&Object, Sel, id),
+    );
+    decl.add_method(
+        sel!(windowShouldClose:),
+        window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
+    );
+    decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
+    decl.register()
+}
+
+///Used to track what the IME does when we send it a keystroke.
+///This is only used to handle the case where the IME mysteriously
+///swallows certain keys.
+///
+///Basically a direct copy of the approach that WezTerm uses in:
+///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs
+enum ImeState {
+    Continue,
+    Acted,
+    None,
+}
+
+struct InsertText {
+    replacement_range: Option<Range<usize>>,
+    text: String,
+}
+
+struct MacWindowState {
+    handle: AnyWindowHandle,
+    dispatcher: Arc<dyn PlatformDispatcher>,
+    native_window: id,
+    renderer: MetalRenderer,
+    scene_to_render: Option<Scene>,
+    kind: WindowKind,
+    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+    activate_callback: Option<Box<dyn FnMut(bool)>>,
+    resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
+    fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
+    moved_callback: Option<Box<dyn FnMut()>>,
+    should_close_callback: Option<Box<dyn FnMut() -> bool>>,
+    close_callback: Option<Box<dyn FnOnce()>>,
+    appearance_changed_callback: Option<Box<dyn FnMut()>>,
+    input_handler: Option<Box<dyn InputHandler>>,
+    pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
+    last_key_equivalent: Option<KeyDownEvent>,
+    synthetic_drag_counter: usize,
+    last_fresh_keydown: Option<Keystroke>,
+    traffic_light_position: Option<Point<Pixels>>,
+    previous_modifiers_changed_event: Option<Event>,
+    // State tracking what the IME did after the last request
+    ime_state: ImeState,
+    // Retains the last IME Text
+    ime_text: Option<String>,
+}
+
+impl MacWindowState {
+    fn move_traffic_light(&self) {
+        if let Some(traffic_light_position) = self.traffic_light_position {
+            let titlebar_height = self.titlebar_height();
+
+            unsafe {
+                let close_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowCloseButton
+                ];
+                let min_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
+                ];
+                let zoom_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowZoomButton
+                ];
+
+                let mut close_button_frame: CGRect = msg_send![close_button, frame];
+                let mut min_button_frame: CGRect = msg_send![min_button, frame];
+                let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
+                let mut origin = point(
+                    traffic_light_position.x,
+                    titlebar_height
+                        - traffic_light_position.y
+                        - px(close_button_frame.size.height as f32),
+                );
+                let button_spacing =
+                    px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
+
+                close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
+                let _: () = msg_send![close_button, setFrame: close_button_frame];
+                origin.x += button_spacing;
+
+                min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
+                let _: () = msg_send![min_button, setFrame: min_button_frame];
+                origin.x += button_spacing;
+
+                zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
+                let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
+                origin.x += button_spacing;
+            }
+        }
+    }
+
+    fn is_fullscreen(&self) -> bool {
+        unsafe {
+            let style_mask = self.native_window.styleMask();
+            style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
+        }
+    }
+
+    fn bounds(&self) -> WindowBounds {
+        unsafe {
+            if self.is_fullscreen() {
+                return WindowBounds::Fullscreen;
+            }
+
+            let frame = self.frame();
+            let screen_size = self.native_window.screen().visibleFrame().size();
+            if frame.size == screen_size {
+                WindowBounds::Maximized
+            } else {
+                WindowBounds::Fixed(frame)
+            }
+        }
+    }
+
+    fn frame(&self) -> Bounds<Pixels> {
+        unsafe {
+            let frame = NSWindow::frame(self.native_window);
+            MacScreen::screen_bounds_from_native(frame)
+        }
+    }
+
+    fn content_size(&self) -> Size<Pixels> {
+        let NSSize { width, height, .. } =
+            unsafe { NSView::frame(self.native_window.contentView()) }.size;
+        size(px(width as f32), px(height as f32))
+    }
+
+    fn scale_factor(&self) -> f32 {
+        get_scale_factor(self.native_window)
+    }
+
+    fn titlebar_height(&self) -> Pixels {
+        unsafe {
+            let frame = NSWindow::frame(self.native_window);
+            let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
+            px((frame.size.height - content_layout_rect.size.height) as f32)
+        }
+    }
+
+    fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
+        unsafe {
+            let point = NSPoint::new(
+                point.x.into(),
+                (self.content_size().height - point.y).into(),
+            );
+            msg_send![self.native_window, convertPointToScreen: point]
+        }
+    }
+}
+
+unsafe impl Send for MacWindowState {}
+
+pub struct MacWindow(Arc<Mutex<MacWindowState>>);
+
+impl MacWindow {
+    pub fn open(handle: AnyWindowHandle, options: WindowOptions, platform: &dyn Platform) -> Self {
+        unsafe {
+            let pool = NSAutoreleasePool::new(nil);
+
+            let mut style_mask;
+            if let Some(titlebar) = options.titlebar.as_ref() {
+                style_mask = NSWindowStyleMask::NSClosableWindowMask
+                    | NSWindowStyleMask::NSMiniaturizableWindowMask
+                    | NSWindowStyleMask::NSResizableWindowMask
+                    | NSWindowStyleMask::NSTitledWindowMask;
+
+                if titlebar.appears_transparent {
+                    style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
+                }
+            } else {
+                style_mask = NSWindowStyleMask::NSTitledWindowMask
+                    | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
+            }
+
+            let native_window: id = match options.kind {
+                WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
+                WindowKind::PopUp => {
+                    style_mask |= NSWindowStyleMaskNonactivatingPanel;
+                    msg_send![PANEL_CLASS, alloc]
+                }
+            };
+            let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
+                NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
+                style_mask,
+                NSBackingStoreBuffered,
+                NO,
+                options
+                    .screen
+                    .map(|screen| MacScreen::from_handle(screen).native_screen)
+                    .unwrap_or(nil),
+            );
+            assert!(!native_window.is_null());
+
+            let screen = native_window.screen();
+            match options.bounds {
+                WindowBounds::Fullscreen => {
+                    native_window.toggleFullScreen_(nil);
+                }
+                WindowBounds::Maximized => {
+                    native_window.setFrame_display_(screen.visibleFrame(), YES);
+                }
+                WindowBounds::Fixed(bounds) => {
+                    let bounds = MacScreen::screen_bounds_to_native(bounds);
+                    let screen_bounds = screen.visibleFrame();
+                    if bounds.intersects(screen_bounds) {
+                        native_window.setFrame_display_(bounds, YES);
+                    } else {
+                        native_window.setFrame_display_(screen_bounds, YES);
+                    }
+                }
+            }
+
+            let native_view: id = msg_send![VIEW_CLASS, alloc];
+            let native_view = NSView::init(native_view);
+
+            assert!(!native_view.is_null());
+
+            let window = Self(Arc::new(Mutex::new(MacWindowState {
+                handle,
+                dispatcher: platform.dispatcher(),
+                native_window,
+                renderer: MetalRenderer::new(true),
+                scene_to_render: None,
+                kind: options.kind,
+                event_callback: None,
+                activate_callback: None,
+                resize_callback: None,
+                fullscreen_callback: None,
+                moved_callback: None,
+                should_close_callback: None,
+                close_callback: None,
+                appearance_changed_callback: None,
+                input_handler: None,
+                pending_key_down: None,
+                last_key_equivalent: None,
+                synthetic_drag_counter: 0,
+                last_fresh_keydown: None,
+                traffic_light_position: options
+                    .titlebar
+                    .as_ref()
+                    .and_then(|titlebar| titlebar.traffic_light_position),
+                previous_modifiers_changed_event: None,
+                ime_state: ImeState::None,
+                ime_text: None,
+            })));
+
+            (*native_window).set_ivar(
+                WINDOW_STATE_IVAR,
+                Arc::into_raw(window.0.clone()) as *const c_void,
+            );
+            native_window.setDelegate_(native_window);
+            (*native_view).set_ivar(
+                WINDOW_STATE_IVAR,
+                Arc::into_raw(window.0.clone()) as *const c_void,
+            );
+
+            if let Some(title) = options
+                .titlebar
+                .as_ref()
+                .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
+            {
+                native_window.setTitle_(NSString::alloc(nil).init_str(title));
+            }
+
+            native_window.setMovable_(options.is_movable as BOOL);
+
+            if options
+                .titlebar
+                .map_or(true, |titlebar| titlebar.appears_transparent)
+            {
+                native_window.setTitlebarAppearsTransparent_(YES);
+                native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
+            }
+
+            native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
+            native_view.setWantsBestResolutionOpenGLSurface_(YES);
+
+            // From winit crate: On Mojave, views automatically become layer-backed shortly after
+            // being added to a native_window. Changing the layer-backedness of a view breaks the
+            // association between the view and its associated OpenGL context. To work around this,
+            // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
+            // itself and break the association with its context.
+            native_view.setWantsLayer(YES);
+            let _: () = msg_send![
+                native_view,
+                setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
+            ];
+
+            native_window.setContentView_(native_view.autorelease());
+            native_window.makeFirstResponder_(native_view);
+
+            if options.center {
+                native_window.center();
+            }
+
+            match options.kind {
+                WindowKind::Normal => {
+                    native_window.setLevel_(NSNormalWindowLevel);
+                    native_window.setAcceptsMouseMovedEvents_(YES);
+                }
+                WindowKind::PopUp => {
+                    // Use a tracking area to allow receiving MouseMoved events even when
+                    // the window or application aren't active, which is often the case
+                    // e.g. for notification windows.
+                    let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
+                    let _: () = msg_send![
+                        tracking_area,
+                        initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
+                        options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
+                        owner: native_view
+                        userInfo: nil
+                    ];
+                    let _: () =
+                        msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
+
+                    native_window.setLevel_(NSPopUpWindowLevel);
+                    let _: () = msg_send![
+                        native_window,
+                        setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
+                    ];
+                    native_window.setCollectionBehavior_(
+                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
+                        NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
+                    );
+                }
+            }
+            if options.focus {
+                native_window.makeKeyAndOrderFront_(nil);
+            } else if options.show {
+                native_window.orderFront_(nil);
+            }
+
+            window.0.lock().move_traffic_light();
+            pool.drain();
+
+            window
+        }
+    }
+
+    pub fn main_window() -> Option<AnyWindowHandle> {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let main_window: id = msg_send![app, mainWindow];
+            if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
+                let handle = get_window_state(&*main_window).lock().handle;
+                Some(handle)
+            } else {
+                None
+            }
+        }
+    }
+}
+
+impl Drop for MacWindow {
+    fn drop(&mut self) {
+        let this = self.0.clone();
+        let dispatcher = self.0.lock().dispatcher.clone();
+        let _ = crate::spawn_on_main(dispatcher, || async move {
+            unsafe {
+                this.lock().native_window.close();
+            }
+        });
+    }
+}
+
+impl PlatformWindow for MacWindow {
+    fn bounds(&self) -> WindowBounds {
+        self.0.as_ref().lock().bounds()
+    }
+
+    fn content_size(&self) -> Size<Pixels> {
+        self.0.as_ref().lock().content_size().into()
+    }
+
+    fn scale_factor(&self) -> f32 {
+        self.0.as_ref().lock().scale_factor()
+    }
+
+    fn titlebar_height(&self) -> Pixels {
+        self.0.as_ref().lock().titlebar_height()
+    }
+
+    fn appearance(&self) -> WindowAppearance {
+        unsafe {
+            let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
+            WindowAppearance::from_native(appearance)
+        }
+    }
+
+    fn screen(&self) -> Rc<dyn PlatformScreen> {
+        unsafe {
+            Rc::new(MacScreen {
+                native_screen: self.0.as_ref().lock().native_window.screen(),
+            })
+        }
+    }
+
+    fn mouse_position(&self) -> Point<Pixels> {
+        let position = unsafe {
+            self.0
+                .lock()
+                .native_window
+                .mouseLocationOutsideOfEventStream()
+        };
+        convert_mouse_position(position, self.content_size().height)
+    }
+
+    fn as_any_mut(&mut self) -> &mut dyn Any {
+        self
+    }
+
+    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
+        self.0.as_ref().lock().input_handler = Some(input_handler);
+    }
+
+    fn prompt(
+        &self,
+        level: WindowPromptLevel,
+        msg: &str,
+        answers: &[&str],
+    ) -> oneshot::Receiver<usize> {
+        // macOs applies overrides to modal window buttons after they are added.
+        // Two most important for this logic are:
+        // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
+        // * Last button added to the modal via `addButtonWithTitle` stays focused
+        // * Focused buttons react on "space"/" " keypresses
+        // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
+        //
+        // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
+        // ```
+        // By default, the first button has a key equivalent of Return,
+        // any button with a title of “Cancel” has a key equivalent of Escape,
+        // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button).
+        // ```
+        //
+        // To avoid situations when the last element added is "Cancel" and it gets the focus
+        // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
+        // last, so it gets focus and a Space shortcut.
+        // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
+        let latest_non_cancel_label = answers
+            .iter()
+            .enumerate()
+            .rev()
+            .find(|(_, &label)| label != "Cancel")
+            .filter(|&(label_index, _)| label_index > 0);
+
+        unsafe {
+            let alert: id = msg_send![class!(NSAlert), alloc];
+            let alert: id = msg_send![alert, init];
+            let alert_style = match level {
+                WindowPromptLevel::Info => 1,
+                WindowPromptLevel::Warning => 0,
+                WindowPromptLevel::Critical => 2,
+            };
+            let _: () = msg_send![alert, setAlertStyle: alert_style];
+            let _: () = msg_send![alert, setMessageText: ns_string(msg)];
+
+            for (ix, answer) in answers
+                .iter()
+                .enumerate()
+                .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
+            {
+                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
+                let _: () = msg_send![button, setTag: ix as NSInteger];
+            }
+            if let Some((ix, answer)) = latest_non_cancel_label {
+                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
+                let _: () = msg_send![button, setTag: ix as NSInteger];
+            }
+
+            let (done_tx, done_rx) = oneshot::channel();
+            let done_tx = Cell::new(Some(done_tx));
+            let block = ConcreteBlock::new(move |answer: NSInteger| {
+                if let Some(done_tx) = done_tx.take() {
+                    let _ = done_tx.send(answer.try_into().unwrap());
+                }
+            });
+            let block = block.copy();
+            let native_window = self.0.lock().native_window;
+            let dispatcher = self.0.lock().dispatcher.clone();
+            let _ = crate::spawn_on_main_local(dispatcher, async move {
+                let _: () = msg_send![
+                    alert,
+                    beginSheetModalForWindow: native_window
+                    completionHandler: block
+                ];
+            });
+
+            done_rx
+        }
+    }
+
+    fn activate(&self) {
+        let window = self.0.lock().native_window;
+        let dispatcher = self.0.lock().dispatcher.clone();
+        let _ = crate::spawn_on_main_local(dispatcher.clone(), async move {
+            unsafe {
+                let _: () = msg_send![window, makeKeyAndOrderFront: nil];
+            }
+        });
+    }
+
+    fn set_title(&mut self, title: &str) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let window = self.0.lock().native_window;
+            let title = ns_string(title);
+            let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
+            let _: () = msg_send![window, setTitle: title];
+            self.0.lock().move_traffic_light();
+        }
+    }
+
+    fn set_edited(&mut self, edited: bool) {
+        unsafe {
+            let window = self.0.lock().native_window;
+            msg_send![window, setDocumentEdited: edited as BOOL]
+        }
+
+        // Changing the document edited state resets the traffic light position,
+        // so we have to move it again.
+        self.0.lock().move_traffic_light();
+    }
+
+    fn show_character_palette(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let window = self.0.lock().native_window;
+            let _: () = msg_send![app, orderFrontCharacterPalette: window];
+        }
+    }
+
+    fn minimize(&self) {
+        let window = self.0.lock().native_window;
+        unsafe {
+            window.miniaturize_(nil);
+        }
+    }
+
+    fn zoom(&self) {
+        let this = self.0.lock();
+        let window = this.native_window;
+        let dispatcher = this.dispatcher.clone();
+        let _ = crate::spawn_on_main_local(dispatcher, async move {
+            unsafe {
+                window.zoom_(nil);
+            }
+        });
+    }
+
+    fn toggle_full_screen(&self) {
+        let this = self.0.lock();
+        let window = this.native_window;
+        let dispatcher = this.dispatcher.clone();
+        let _ = crate::spawn_on_main_local(dispatcher, async move {
+            unsafe {
+                window.toggleFullScreen_(nil);
+            }
+        });
+    }
+
+    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) {
+        self.0.as_ref().lock().event_callback = Some(callback);
+    }
+
+    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
+        self.0.as_ref().lock().activate_callback = Some(callback);
+    }
+
+    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
+        self.0.as_ref().lock().resize_callback = Some(callback);
+    }
+
+    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
+        self.0.as_ref().lock().fullscreen_callback = Some(callback);
+    }
+
+    fn on_moved(&self, callback: Box<dyn FnMut()>) {
+        self.0.as_ref().lock().moved_callback = Some(callback);
+    }
+
+    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
+        self.0.as_ref().lock().should_close_callback = Some(callback);
+    }
+
+    fn on_close(&self, callback: Box<dyn FnOnce()>) {
+        self.0.as_ref().lock().close_callback = Some(callback);
+    }
+
+    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
+        self.0.lock().appearance_changed_callback = Some(callback);
+    }
+
+    fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
+        let self_borrow = self.0.lock();
+        let self_handle = self_borrow.handle;
+
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+
+            // Convert back to screen coordinates
+            let screen_point = self_borrow.to_screen_ns_point(position);
+
+            let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
+            let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
+
+            let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
+            let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
+            if is_panel == YES || is_window == YES {
+                let topmost_window = get_window_state(&*top_most_window).lock().handle;
+                topmost_window == self_handle
+            } else {
+                // Someone else's window is on top
+                false
+            }
+        }
+    }
+
+    fn draw(&self, scene: crate::Scene) {
+        let mut this = self.0.lock();
+        this.scene_to_render = Some(scene);
+        unsafe {
+            let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
+        }
+    }
+}
+
+fn get_scale_factor(native_window: id) -> f32 {
+    unsafe {
+        let screen: id = msg_send![native_window, screen];
+        NSScreen::backingScaleFactor(screen) as f32
+    }
+}
+
+unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
+    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+    let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
+    let rc2 = rc1.clone();
+    mem::forget(rc1);
+    rc2
+}
+
+unsafe fn drop_window_state(object: &Object) {
+    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+    Rc::from_raw(raw as *mut RefCell<MacWindowState>);
+}
+
+extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
+    YES
+}
+
+extern "C" fn dealloc_window(this: &Object, _: Sel) {
+    unsafe {
+        drop_window_state(this);
+        let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
+    }
+}
+
+extern "C" fn dealloc_view(this: &Object, _: Sel) {
+    unsafe {
+        drop_window_state(this);
+        let _: () = msg_send![super(this, class!(NSView)), dealloc];
+    }
+}
+
+extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
+    handle_key_event(this, native_event, true)
+}
+
+extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
+    handle_key_event(this, native_event, false);
+}
+
+extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
+    let window_state = unsafe { get_window_state(this) };
+    let mut lock = window_state.as_ref().lock();
+
+    let window_height = lock.content_size().height;
+    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+
+    if let Some(Event::KeyDown(event)) = event {
+        // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
+        // If that event isn't handled, it will then dispatch a "key down" event. GPUI
+        // makes no distinction between these two types of events, so we need to ignore
+        // the "key down" event if we've already just processed its "key equivalent" version.
+        if key_equivalent {
+            lock.last_key_equivalent = Some(event.clone());
+        } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
+            return NO;
+        }
+
+        let keydown = event.keystroke.clone();
+        let fn_modifier = keydown.modifiers.function;
+        // Ignore events from held-down keys after some of the initially-pressed keys
+        // were released.
+        if event.is_held {
+            if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
+                return YES;
+            }
+        } else {
+            lock.last_fresh_keydown = Some(keydown);
+        }
+        lock.pending_key_down = Some((event, None));
+        drop(lock);
+
+        // Send the event to the input context for IME handling, unless the `fn` modifier is
+        // being pressed.
+        if !fn_modifier {
+            unsafe {
+                let input_context: id = msg_send![this, inputContext];
+                let _: BOOL = msg_send![input_context, handleEvent: native_event];
+            }
+        }
+
+        let mut handled = false;
+        let mut lock = window_state.lock();
+        let ime_text = lock.ime_text.clone();
+        if let Some((event, insert_text)) = lock.pending_key_down.take() {
+            let is_held = event.is_held;
+            if let Some(mut callback) = lock.event_callback.take() {
+                drop(lock);
+
+                let is_composing =
+                    with_input_handler(this, |input_handler| input_handler.marked_text_range())
+                        .flatten()
+                        .is_some();
+                if !is_composing {
+                    // if the IME has changed the key, we'll first emit an event with the character
+                    // generated by the IME system; then fallback to the keystroke if that is not
+                    // handled.
+                    // cases that we have working:
+                    // - " on a brazillian layout by typing <quote><space>
+                    // - ctrl-` on a brazillian layout by typing <ctrl-`>
+                    // - $ on a czech QWERTY layout by typing <alt-4>
+                    // - 4 on a czech QWERTY layout by typing <shift-4>
+                    // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
+                    if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
+                        let event_with_ime_text = KeyDownEvent {
+                            is_held: false,
+                            keystroke: Keystroke {
+                                // we match ctrl because some use-cases need it.
+                                // we don't match alt because it's often used to generate the optional character
+                                // we don't match shift because we're not here with letters (usually)
+                                // we don't match cmd/fn because they don't seem to use IME
+                                modifiers: Default::default(),
+                                key: ime_text.clone().unwrap(),
+                            },
+                        };
+                        handled = callback(Event::KeyDown(event_with_ime_text));
+                    }
+                    if !handled {
+                        // empty key happens when you type a deadkey in input composition.
+                        // (e.g. on a brazillian keyboard typing quote is a deadkey)
+                        if !event.keystroke.key.is_empty() {
+                            handled = callback(Event::KeyDown(event));
+                        }
+                    }
+                }
+
+                if !handled {
+                    if let Some(insert) = insert_text {
+                        handled = true;
+                        with_input_handler(this, |input_handler| {
+                            input_handler
+                                .replace_text_in_range(insert.replacement_range, &insert.text)
+                        });
+                    } else if !is_composing && is_held {
+                        if let Some(last_insert_text) = ime_text {
+                            //MacOS IME is a bit funky, and even when you've told it there's nothing to
+                            //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
+                            //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
+                            with_input_handler(this, |input_handler| {
+                                if input_handler.selected_text_range().is_none() {
+                                    handled = true;
+                                    input_handler.replace_text_in_range(None, &last_insert_text)
+                                }
+                            });
+                        }
+                    }
+                }
+
+                window_state.lock().event_callback = Some(callback);
+            }
+        } else {
+            handled = true;
+        }
+
+        handled as BOOL
+    } else {
+        NO
+    }
+}
+
+extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let weak_window_state = Arc::downgrade(&window_state);
+    let mut lock = window_state.as_ref().lock();
+    let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
+
+    let window_height = lock.content_size().height;
+    let event = unsafe { Event::from_native(native_event, Some(window_height)) };
+
+    if let Some(mut event) = event {
+        let synthesized_second_event = match &mut event {
+            Event::MouseDown(
+                event @ MouseDownEvent {
+                    button: MouseButton::Left,
+                    modifiers: Modifiers { control: true, .. },
+                    ..
+                },
+            ) => {
+                *event = MouseDownEvent {
+                    button: MouseButton::Right,
+                    modifiers: Modifiers {
+                        control: false,
+                        ..event.modifiers
+                    },
+                    click_count: 1,
+                    ..*event
+                };
+
+                Some(Event::MouseDown(MouseDownEvent {
+                    button: MouseButton::Right,
+                    ..*event
+                }))
+            }
+
+            // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
+            // the ctrl-left_up to avoid having a mismatch in button down/up events if the
+            // user is still holding ctrl when releasing the left mouse button
+            Event::MouseUp(MouseUpEvent {
+                button: MouseButton::Left,
+                modifiers: Modifiers { control: true, .. },
+                ..
+            }) => {
+                lock.synthetic_drag_counter += 1;
+                return;
+            }
+
+            _ => None,
+        };
+
+        match &event {
+            Event::MouseMoved(
+                event @ MouseMovedEvent {
+                    pressed_button: Some(_),
+                    ..
+                },
+            ) => {
+                lock.synthetic_drag_counter += 1;
+                let dispatcher = lock.dispatcher.clone();
+                let _ = crate::spawn_on_main_local(
+                    dispatcher,
+                    synthetic_drag(
+                        weak_window_state,
+                        lock.synthetic_drag_counter,
+                        event.clone(),
+                    ),
+                );
+            }
+
+            Event::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
+
+            Event::MouseUp(MouseUpEvent {
+                button: MouseButton::Left,
+                ..
+            }) => {
+                lock.synthetic_drag_counter += 1;
+            }
+
+            Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
+                // Only raise modifiers changed event when they have actually changed
+                if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
+                    modifiers: prev_modifiers,
+                })) = &lock.previous_modifiers_changed_event
+                {
+                    if prev_modifiers == modifiers {
+                        return;
+                    }
+                }
+
+                lock.previous_modifiers_changed_event = Some(event.clone());
+            }
+
+            _ => {}
+        }
+
+        if let Some(mut callback) = lock.event_callback.take() {
+            drop(lock);
+            callback(event);
+            if let Some(event) = synthesized_second_event {
+                callback(event);
+            }
+            window_state.lock().event_callback = Some(callback);
+        }
+    }
+}
+
+// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
+// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
+extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut lock = window_state.as_ref().lock();
+
+    let keystroke = Keystroke {
+        modifiers: Default::default(),
+        key: ".".into(),
+    };
+    let event = Event::KeyDown(KeyDownEvent {
+        keystroke: keystroke.clone(),
+        is_held: false,
+    });
+
+    lock.last_fresh_keydown = Some(keystroke);
+    if let Some(mut callback) = lock.event_callback.take() {
+        drop(lock);
+        callback(event);
+        window_state.lock().event_callback = Some(callback);
+    }
+}
+
+extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    window_state.as_ref().lock().move_traffic_light();
+}
+
+extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
+    window_fullscreen_changed(this, true);
+}
+
+extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
+    window_fullscreen_changed(this, false);
+}
+
+fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut lock = window_state.as_ref().lock();
+    if let Some(mut callback) = lock.fullscreen_callback.take() {
+        drop(lock);
+        callback(is_fullscreen);
+        window_state.lock().fullscreen_callback = Some(callback);
+    }
+}
+
+extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut lock = window_state.as_ref().lock();
+    if let Some(mut callback) = lock.moved_callback.take() {
+        drop(lock);
+        callback();
+        window_state.lock().moved_callback = Some(callback);
+    }
+}
+
+extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    let lock = window_state.lock();
+    let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
+
+    // When opening a pop-up while the application isn't active, Cocoa sends a spurious
+    // `windowDidBecomeKey` message to the previous key window even though that window
+    // isn't actually key. This causes a bug if the application is later activated while
+    // the pop-up is still open, making it impossible to activate the previous key window
+    // even if the pop-up gets closed. The only way to activate it again is to de-activate
+    // the app and re-activate it, which is a pretty bad UX.
+    // The following code detects the spurious event and invokes `resignKeyWindow`:
+    // in theory, we're not supposed to invoke this method manually but it balances out
+    // the spurious `becomeKeyWindow` event and helps us work around that bug.
+    if selector == sel!(windowDidBecomeKey:) {
+        if !is_active {
+            unsafe {
+                let _: () = msg_send![lock.native_window, resignKeyWindow];
+                return;
+            }
+        }
+    }
+
+    let dispatcher = lock.dispatcher.clone();
+    drop(lock);
+    let _ = crate::spawn_on_main_local(dispatcher, async move {
+        let mut lock = window_state.as_ref().lock();
+        if let Some(mut callback) = lock.activate_callback.take() {
+            drop(lock);
+            callback(is_active);
+            window_state.lock().activate_callback = Some(callback);
+        };
+    });
+}
+
+extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
+    let window_state = unsafe { get_window_state(this) };
+    let mut lock = window_state.as_ref().lock();
+    if let Some(mut callback) = lock.should_close_callback.take() {
+        drop(lock);
+        let should_close = callback();
+        window_state.lock().should_close_callback = Some(callback);
+        should_close as BOOL
+    } else {
+        YES
+    }
+}
+
+extern "C" fn close_window(this: &Object, _: Sel) {
+    unsafe {
+        let close_callback = {
+            let window_state = get_window_state(this);
+            window_state
+                .as_ref()
+                .try_lock()
+                .and_then(|mut window_state| window_state.close_callback.take())
+        };
+
+        if let Some(callback) = close_callback {
+            callback();
+        }
+
+        let _: () = msg_send![super(this, class!(NSWindow)), close];
+    }
+}
+
+extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
+    let window_state = unsafe { get_window_state(this) };
+    let window_state = window_state.as_ref().lock();
+    window_state.renderer.layer().as_ptr() as id
+}
+
+extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut lock = window_state.as_ref().lock();
+
+    unsafe {
+        let scale_factor = lock.scale_factor() as f64;
+        let size = lock.content_size();
+        let drawable_size: NSSize = NSSize {
+            width: f64::from(size.width) * scale_factor,
+            height: f64::from(size.height) * scale_factor,
+        };
+
+        let _: () = msg_send![
+            lock.renderer.layer(),
+            setContentsScale: scale_factor
+        ];
+        let _: () = msg_send![
+            lock.renderer.layer(),
+            setDrawableSize: drawable_size
+        ];
+    }
+
+    if let Some(mut callback) = lock.resize_callback.take() {
+        let content_size = lock.content_size();
+        let scale_factor = lock.scale_factor();
+        drop(lock);
+        callback(content_size, scale_factor);
+        window_state.as_ref().lock().resize_callback = Some(callback);
+    };
+}
+
+extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
+    let window_state = unsafe { get_window_state(this) };
+    let lock = window_state.as_ref().lock();
+
+    if lock.content_size() == size.into() {
+        return;
+    }
+
+    unsafe {
+        let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
+    }
+
+    let scale_factor = lock.scale_factor() as f64;
+    let drawable_size: NSSize = NSSize {
+        width: size.width * scale_factor,
+        height: size.height * scale_factor,
+    };
+
+    unsafe {
+        let _: () = msg_send![
+            lock.renderer.layer(),
+            setDrawableSize: drawable_size
+        ];
+    }
+
+    drop(lock);
+    let mut lock = window_state.lock();
+    if let Some(mut callback) = lock.resize_callback.take() {
+        let content_size = lock.content_size();
+        let scale_factor = lock.scale_factor();
+        drop(lock);
+        callback(content_size, scale_factor);
+        window_state.lock().resize_callback = Some(callback);
+    };
+}
+
+extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
+    unsafe {
+        let window_state = get_window_state(this);
+        let mut window_state = window_state.as_ref().lock();
+        if let Some(scene) = window_state.scene_to_render.take() {
+            dbg!("render", &scene);
+            window_state.renderer.draw(&scene);
+        }
+    }
+}
+
+extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
+    unsafe { msg_send![class!(NSArray), array] }
+}
+
+extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
+    with_input_handler(this, |input_handler| input_handler.marked_text_range())
+        .flatten()
+        .is_some() as BOOL
+}
+
+extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
+    with_input_handler(this, |input_handler| input_handler.marked_text_range())
+        .flatten()
+        .map_or(NSRange::invalid(), |range| range.into())
+}
+
+extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
+    with_input_handler(this, |input_handler| input_handler.selected_text_range())
+        .flatten()
+        .map_or(NSRange::invalid(), |range| range.into())
+}
+
+extern "C" fn first_rect_for_character_range(
+    this: &Object,
+    _: Sel,
+    range: NSRange,
+    _: id,
+) -> NSRect {
+    let frame = unsafe {
+        let window = get_window_state(this).lock().native_window;
+        NSView::frame(window)
+    };
+    with_input_handler(this, |input_handler| {
+        input_handler.bounds_for_range(range.to_range()?)
+    })
+    .flatten()
+    .map_or(
+        NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
+        |bounds| {
+            NSRect::new(
+                NSPoint::new(
+                    frame.origin.x + bounds.origin.x as f64,
+                    frame.origin.y + frame.size.height - bounds.origin.y as f64,
+                ),
+                NSSize::new(bounds.size.width as f64, bounds.size.height as f64),
+            )
+        },
+    )
+}
+
+extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
+    unsafe {
+        let window_state = get_window_state(this);
+        let mut lock = window_state.lock();
+        let pending_key_down = lock.pending_key_down.take();
+        drop(lock);
+
+        let is_attributed_string: BOOL =
+            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
+        let text: id = if is_attributed_string == YES {
+            msg_send![text, string]
+        } else {
+            text
+        };
+        let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
+            .to_str()
+            .unwrap();
+        let replacement_range = replacement_range.to_range();
+
+        window_state.lock().ime_text = Some(text.to_string());
+        window_state.lock().ime_state = ImeState::Acted;
+
+        let is_composing =
+            with_input_handler(this, |input_handler| input_handler.marked_text_range())
+                .flatten()
+                .is_some();
+
+        if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
+            with_input_handler(this, |input_handler| {
+                input_handler.replace_text_in_range(replacement_range, text)
+            });
+        } else {
+            let mut pending_key_down = pending_key_down.unwrap();
+            pending_key_down.1 = Some(InsertText {
+                replacement_range,
+                text: text.to_string(),
+            });
+            window_state.lock().pending_key_down = Some(pending_key_down);
+        }
+    }
+}
+
+extern "C" fn set_marked_text(
+    this: &Object,
+    _: Sel,
+    text: id,
+    selected_range: NSRange,
+    replacement_range: NSRange,
+) {
+    unsafe {
+        let window_state = get_window_state(this);
+        window_state.lock().pending_key_down.take();
+
+        let is_attributed_string: BOOL =
+            msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
+        let text: id = if is_attributed_string == YES {
+            msg_send![text, string]
+        } else {
+            text
+        };
+        let selected_range = selected_range.to_range();
+        let replacement_range = replacement_range.to_range();
+        let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
+            .to_str()
+            .unwrap();
+
+        window_state.lock().ime_state = ImeState::Acted;
+        window_state.lock().ime_text = Some(text.to_string());
+
+        with_input_handler(this, |input_handler| {
+            input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
+        });
+    }
+}
+
+extern "C" fn unmark_text(this: &Object, _: Sel) {
+    unsafe {
+        let state = get_window_state(this);
+        let mut borrow = state.lock();
+        borrow.ime_state = ImeState::Acted;
+        borrow.ime_text.take();
+    }
+
+    with_input_handler(this, |input_handler| input_handler.unmark_text());
+}
+
+extern "C" fn attributed_substring_for_proposed_range(
+    this: &Object,
+    _: Sel,
+    range: NSRange,
+    _actual_range: *mut c_void,
+) -> id {
+    with_input_handler(this, |input_handler| {
+        let range = range.to_range()?;
+        if range.is_empty() {
+            return None;
+        }
+
+        let selected_text = input_handler.text_for_range(range)?;
+        unsafe {
+            let string: id = msg_send![class!(NSAttributedString), alloc];
+            let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
+            Some(string)
+        }
+    })
+    .flatten()
+    .unwrap_or(nil)
+}
+
+extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
+    unsafe {
+        let state = get_window_state(this);
+        let mut borrow = state.lock();
+        borrow.ime_state = ImeState::Continue;
+        borrow.ime_text.take();
+    }
+}
+
+extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
+    unsafe {
+        let state = get_window_state(this);
+        let mut lock = state.as_ref().lock();
+        if let Some(mut callback) = lock.appearance_changed_callback.take() {
+            drop(lock);
+            callback();
+            state.lock().appearance_changed_callback = Some(callback);
+        }
+    }
+}
+
+extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
+    unsafe {
+        let state = get_window_state(this);
+        let lock = state.as_ref().lock();
+        return if lock.kind == WindowKind::PopUp {
+            YES
+        } else {
+            NO
+        };
+    }
+}
+
+async fn synthetic_drag(
+    window_state: Weak<Mutex<MacWindowState>>,
+    drag_id: usize,
+    event: MouseMovedEvent,
+) {
+    loop {
+        Timer::after(Duration::from_millis(16)).await;
+        if let Some(window_state) = window_state.upgrade() {
+            let mut lock = window_state.lock();
+            if lock.synthetic_drag_counter == drag_id {
+                if let Some(mut callback) = lock.event_callback.take() {
+                    drop(lock);
+                    callback(Event::MouseMoved(event.clone()));
+                    window_state.lock().event_callback = Some(callback);
+                }
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
+where
+    F: FnOnce(&mut dyn InputHandler) -> R,
+{
+    let window_state = unsafe { get_window_state(window) };
+    let mut lock = window_state.as_ref().lock();
+    if let Some(mut input_handler) = lock.input_handler.take() {
+        drop(lock);
+        let result = f(input_handler.as_mut());
+        window_state.lock().input_handler = Some(input_handler);
+        Some(result)
+    } else {
+        None
+    }
+}

crates/gpui3/src/platform/mac/window_appearence.rs 🔗

@@ -0,0 +1,35 @@
+use crate::WindowAppearance;
+use cocoa::{
+    appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
+    base::id,
+    foundation::NSString,
+};
+use objc::{msg_send, sel, sel_impl};
+use std::ffi::CStr;
+
+impl WindowAppearance {
+    pub unsafe fn from_native(appearance: id) -> Self {
+        let name: id = msg_send![appearance, name];
+        if name == NSAppearanceNameVibrantLight {
+            Self::VibrantLight
+        } else if name == NSAppearanceNameVibrantDark {
+            Self::VibrantDark
+        } else if name == NSAppearanceNameAqua {
+            Self::Light
+        } else if name == NSAppearanceNameDarkAqua {
+            Self::Dark
+        } else {
+            println!(
+                "unknown appearance: {:?}",
+                CStr::from_ptr(name.UTF8String())
+            );
+            Self::Light
+        }
+    }
+}
+
+#[link(name = "AppKit", kind = "framework")]
+extern "C" {
+    pub static NSAppearanceNameAqua: id;
+    pub static NSAppearanceNameDarkAqua: id;
+}

crates/gpui3/src/platform/test.rs 🔗

@@ -0,0 +1,172 @@
+use super::Platform;
+use crate::ScreenId;
+
+pub struct TestPlatform;
+
+impl TestPlatform {
+    pub fn new() -> Self {
+        TestPlatform
+    }
+}
+
+// todo!("implement out what our tests needed in GPUI 1")
+impl Platform for TestPlatform {
+    fn dispatcher(&self) -> std::sync::Arc<dyn crate::PlatformDispatcher> {
+        unimplemented!()
+    }
+
+    fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
+        unimplemented!()
+    }
+
+    fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
+        unimplemented!()
+    }
+
+    fn quit(&self) {
+        unimplemented!()
+    }
+
+    fn restart(&self) {
+        unimplemented!()
+    }
+
+    fn activate(&self, _ignoring_other_apps: bool) {
+        unimplemented!()
+    }
+
+    fn hide(&self) {
+        unimplemented!()
+    }
+
+    fn hide_other_apps(&self) {
+        unimplemented!()
+    }
+
+    fn unhide_other_apps(&self) {
+        unimplemented!()
+    }
+
+    fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
+        unimplemented!()
+    }
+
+    fn screen_by_id(&self, _id: ScreenId) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> {
+        unimplemented!()
+    }
+
+    fn main_window(&self) -> Option<crate::AnyWindowHandle> {
+        unimplemented!()
+    }
+
+    fn open_window(
+        &self,
+        _handle: crate::AnyWindowHandle,
+        _options: crate::WindowOptions,
+    ) -> Box<dyn crate::PlatformWindow> {
+        unimplemented!()
+    }
+
+    fn open_url(&self, _url: &str) {
+        unimplemented!()
+    }
+
+    fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
+        unimplemented!()
+    }
+
+    fn prompt_for_paths(
+        &self,
+        _options: crate::PathPromptOptions,
+    ) -> futures::channel::oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
+        unimplemented!()
+    }
+
+    fn prompt_for_new_path(
+        &self,
+        _directory: &std::path::Path,
+    ) -> futures::channel::oneshot::Receiver<Option<std::path::PathBuf>> {
+        unimplemented!()
+    }
+
+    fn reveal_path(&self, _path: &std::path::Path) {
+        unimplemented!()
+    }
+
+    fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
+        unimplemented!()
+    }
+
+    fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
+        unimplemented!()
+    }
+
+    fn on_quit(&self, _callback: Box<dyn FnMut()>) {
+        unimplemented!()
+    }
+
+    fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
+        unimplemented!()
+    }
+
+    fn on_event(&self, _callback: Box<dyn FnMut(crate::Event) -> bool>) {
+        unimplemented!()
+    }
+
+    fn os_name(&self) -> &'static str {
+        unimplemented!()
+    }
+
+    fn os_version(&self) -> anyhow::Result<crate::SemanticVersion> {
+        unimplemented!()
+    }
+
+    fn app_version(&self) -> anyhow::Result<crate::SemanticVersion> {
+        unimplemented!()
+    }
+
+    fn app_path(&self) -> anyhow::Result<std::path::PathBuf> {
+        unimplemented!()
+    }
+
+    fn local_timezone(&self) -> time::UtcOffset {
+        unimplemented!()
+    }
+
+    fn path_for_auxiliary_executable(&self, _name: &str) -> anyhow::Result<std::path::PathBuf> {
+        unimplemented!()
+    }
+
+    fn set_cursor_style(&self, _style: crate::CursorStyle) {
+        unimplemented!()
+    }
+
+    fn should_auto_hide_scrollbars(&self) -> bool {
+        unimplemented!()
+    }
+
+    fn write_to_clipboard(&self, _item: crate::ClipboardItem) {
+        unimplemented!()
+    }
+
+    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
+        unimplemented!()
+    }
+
+    fn write_credentials(
+        &self,
+        _url: &str,
+        _username: &str,
+        _password: &[u8],
+    ) -> anyhow::Result<()> {
+        unimplemented!()
+    }
+
+    fn read_credentials(&self, _url: &str) -> anyhow::Result<Option<(String, Vec<u8>)>> {
+        unimplemented!()
+    }
+
+    fn delete_credentials(&self, _url: &str) -> anyhow::Result<()> {
+        unimplemented!()
+    }
+}

crates/gpui3/src/scene.rs 🔗

@@ -0,0 +1,121 @@
+use std::mem;
+
+use super::{Bounds, Hsla, Pixels, Point};
+use crate::{Corners, Edges};
+use bytemuck::{Pod, Zeroable};
+use collections::BTreeMap;
+
+// Exported to metal
+pub type PointF = Point<f32>;
+
+#[derive(Debug)]
+pub struct Scene {
+    layers: BTreeMap<u32, SceneLayer>,
+    pub(crate) scale_factor: f32,
+}
+
+#[derive(Default, Debug)]
+pub struct SceneLayer {
+    pub quads: Vec<Quad>,
+}
+
+impl Scene {
+    pub fn new(scale_factor: f32) -> Scene {
+        Scene {
+            layers: Default::default(),
+            scale_factor,
+        }
+    }
+
+    pub fn take(&mut self) -> Scene {
+        Scene {
+            layers: mem::take(&mut self.layers),
+            scale_factor: self.scale_factor,
+        }
+    }
+
+    pub fn insert(&mut self, primitive: impl Into<Primitive>) {
+        let mut primitive = primitive.into();
+        primitive.scale(self.scale_factor);
+        let layer = self.layers.entry(primitive.order()).or_default();
+        match primitive {
+            Primitive::Quad(quad) => layer.quads.push(quad),
+        }
+    }
+
+    pub fn layers(&self) -> impl Iterator<Item = &SceneLayer> {
+        self.layers.values()
+    }
+}
+
+#[derive(Clone, Debug)]
+pub enum Primitive {
+    Quad(Quad),
+}
+
+impl Primitive {
+    pub fn order(&self) -> u32 {
+        match self {
+            Primitive::Quad(quad) => quad.order,
+        }
+    }
+
+    pub fn is_transparent(&self) -> bool {
+        match self {
+            Primitive::Quad(quad) => {
+                quad.background.is_transparent() && quad.border_color.is_transparent()
+            }
+        }
+    }
+
+    pub fn scale(&mut self, factor: f32) {
+        match self {
+            Primitive::Quad(quad) => {
+                quad.scale(factor);
+            }
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+#[repr(C)]
+pub struct Quad {
+    pub order: u32,
+    pub bounds: Bounds<Pixels>,
+    pub clip_bounds: Bounds<Pixels>,
+    pub clip_corner_radii: Corners<Pixels>,
+    pub background: Hsla,
+    pub border_color: Hsla,
+    pub corner_radii: Corners<Pixels>,
+    pub border_widths: Edges<Pixels>,
+}
+
+impl Quad {
+    pub fn vertices(&self) -> impl Iterator<Item = Point<Pixels>> {
+        let x1 = self.bounds.origin.x;
+        let y1 = self.bounds.origin.y;
+        let x2 = x1 + self.bounds.size.width;
+        let y2 = y1 + self.bounds.size.height;
+        [
+            Point::new(x1, y1),
+            Point::new(x2, y1),
+            Point::new(x2, y2),
+            Point::new(x1, y2),
+        ]
+        .into_iter()
+    }
+
+    pub fn scale(&mut self, factor: f32) {
+        self.bounds *= factor;
+        self.clip_bounds *= factor;
+        self.clip_corner_radii *= factor;
+        self.corner_radii *= factor;
+        self.border_widths *= factor;
+    }
+}
+
+impl From<Quad> for Primitive {
+    fn from(quad: Quad) -> Self {
+        Primitive::Quad(quad)
+    }
+}

crates/gpui3/src/style.rs 🔗

@@ -0,0 +1,338 @@
+use crate::{
+    phi, rems, AbsoluteLength, Bounds, Corners, CornersRefinement, DefiniteLength, Edges,
+    EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point,
+    PointRefinement, Quad, Rems, Result, RunStyle, SharedString, Size, SizeRefinement, ViewContext,
+    WindowContext,
+};
+use refineable::Refineable;
+pub use taffy::style::{
+    AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
+    Overflow, Position,
+};
+
+#[derive(Clone, Refineable, Debug)]
+#[refineable(debug)]
+pub struct Style {
+    /// What layout strategy should be used?
+    pub display: Display,
+
+    // Overflow properties
+    /// How children overflowing their container should affect layout
+    #[refineable]
+    pub overflow: Point<Overflow>,
+    /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
+    pub scrollbar_width: f32,
+
+    // Position properties
+    /// What should the `position` value of this struct use as a base offset?
+    pub position: Position,
+    /// How should the position of this element be tweaked relative to the layout defined?
+    #[refineable]
+    pub inset: Edges<Length>,
+
+    // Size properies
+    /// Sets the initial size of the item
+    #[refineable]
+    pub size: Size<Length>,
+    /// Controls the minimum size of the item
+    #[refineable]
+    pub min_size: Size<Length>,
+    /// Controls the maximum size of the item
+    #[refineable]
+    pub max_size: Size<Length>,
+    /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
+    pub aspect_ratio: Option<f32>,
+
+    // Spacing Properties
+    /// How large should the margin be on each side?
+    #[refineable]
+    pub margin: Edges<Length>,
+    /// How large should the padding be on each side?
+    #[refineable]
+    pub padding: Edges<DefiniteLength>,
+    /// How large should the border be on each side?
+    #[refineable]
+    pub border_widths: Edges<AbsoluteLength>,
+
+    // Alignment properties
+    /// How this node's children aligned in the cross/block axis?
+    pub align_items: Option<AlignItems>,
+    /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
+    pub align_self: Option<AlignSelf>,
+    /// How should content contained within this item be aligned in the cross/block axis
+    pub align_content: Option<AlignContent>,
+    /// How should contained within this item be aligned in the main/inline axis
+    pub justify_content: Option<JustifyContent>,
+    /// How large should the gaps between items in a flex container be?
+    #[refineable]
+    pub gap: Size<DefiniteLength>,
+
+    // Flexbox properies
+    /// Which direction does the main axis flow in?
+    pub flex_direction: FlexDirection,
+    /// Should elements wrap, or stay in a single line?
+    pub flex_wrap: FlexWrap,
+    /// Sets the initial main axis size of the item
+    pub flex_basis: Length,
+    /// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
+    pub flex_grow: f32,
+    /// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
+    pub flex_shrink: f32,
+
+    /// The fill color of this element
+    pub fill: Option<Fill>,
+
+    /// The border color of this element
+    pub border_color: Option<Hsla>,
+
+    /// The radius of the corners of this element
+    #[refineable]
+    pub corner_radii: Corners<AbsoluteLength>,
+
+    /// TEXT
+    pub text: TextStyleRefinement,
+}
+
+#[derive(Refineable, Clone, Debug)]
+#[refineable(debug)]
+pub struct TextStyle {
+    pub color: Hsla,
+    pub font_family: SharedString,
+    pub font_features: FontFeatures,
+    pub font_size: Rems,
+    pub line_height: DefiniteLength,
+    pub font_weight: FontWeight,
+    pub font_style: FontStyle,
+    pub underline: Option<UnderlineStyle>,
+}
+
+impl Default for TextStyle {
+    fn default() -> Self {
+        TextStyle {
+            color: Hsla::default(),
+            font_family: SharedString::default(),
+            font_features: FontFeatures::default(),
+            font_size: rems(1.),
+            line_height: phi(),
+            font_weight: FontWeight::default(),
+            font_style: FontStyle::default(),
+            underline: None,
+        }
+    }
+}
+
+impl TextStyle {
+    pub fn highlight(mut self, style: HighlightStyle) -> Result<Self> {
+        if let Some(weight) = style.font_weight {
+            self.font_weight = weight;
+        }
+        if let Some(style) = style.font_style {
+            self.font_style = style;
+        }
+
+        if let Some(color) = style.color {
+            self.color = self.color.blend(color);
+        }
+
+        if let Some(factor) = style.fade_out {
+            self.color.fade_out(factor);
+        }
+
+        if let Some(underline) = style.underline {
+            self.underline = Some(underline);
+        }
+
+        Ok(self)
+    }
+
+    pub fn to_run(&self) -> RunStyle {
+        RunStyle {
+            font: Font {
+                family: self.font_family.clone(),
+                features: Default::default(),
+                weight: self.font_weight,
+                style: self.font_style,
+            },
+            color: self.color,
+            underline: self.underline.clone(),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct HighlightStyle {
+    pub color: Option<Hsla>,
+    pub font_weight: Option<FontWeight>,
+    pub font_style: Option<FontStyle>,
+    pub underline: Option<UnderlineStyle>,
+    pub fade_out: Option<f32>,
+}
+
+impl Eq for HighlightStyle {}
+
+impl Style {
+    pub fn text_style(&self, _cx: &WindowContext) -> Option<&TextStyleRefinement> {
+        if self.text.is_some() {
+            Some(&self.text)
+        } else {
+            None
+        }
+    }
+
+    /// Paints the background of an element styled with this style.
+    pub fn paint<V: 'static>(&self, order: u32, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
+        let rem_size = cx.rem_size();
+
+        let background_color = self.fill.as_ref().and_then(Fill::color);
+        if background_color.is_some() || self.is_border_visible() {
+            cx.scene().insert(Quad {
+                order,
+                bounds,
+                clip_bounds: bounds, // todo!
+                clip_corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
+                background: background_color.unwrap_or_default(),
+                border_color: self.border_color.unwrap_or_default(),
+                corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
+                border_widths: self.border_widths.map(|length| length.to_pixels(rem_size)),
+            });
+        }
+    }
+
+    fn is_border_visible(&self) -> bool {
+        self.border_color
+            .map_or(false, |color| !color.is_transparent())
+            && self.border_widths.any(|length| !length.is_zero())
+    }
+}
+
+impl Default for Style {
+    fn default() -> Self {
+        Style {
+            display: Display::Block,
+            overflow: Point {
+                x: Overflow::Visible,
+                y: Overflow::Visible,
+            },
+            scrollbar_width: 0.0,
+            position: Position::Relative,
+            inset: Edges::auto(),
+            margin: Edges::<Length>::zero(),
+            padding: Edges::<DefiniteLength>::zero(),
+            border_widths: Edges::<AbsoluteLength>::zero(),
+            size: Size::auto(),
+            min_size: Size::auto(),
+            max_size: Size::auto(),
+            aspect_ratio: None,
+            gap: Size::zero(),
+            // Aligment
+            align_items: None,
+            align_self: None,
+            align_content: None,
+            justify_content: None,
+            // Flexbox
+            flex_direction: FlexDirection::Row,
+            flex_wrap: FlexWrap::NoWrap,
+            flex_grow: 0.0,
+            flex_shrink: 1.0,
+            flex_basis: Length::Auto,
+            fill: None,
+            border_color: None,
+            corner_radii: Corners::default(),
+            text: TextStyleRefinement::default(),
+        }
+    }
+}
+
+#[derive(Refineable, Clone, Default, Debug, PartialEq, Eq)]
+#[refineable(debug)]
+pub struct UnderlineStyle {
+    pub thickness: Pixels,
+    pub color: Option<Hsla>,
+    pub squiggly: bool,
+}
+
+#[derive(Clone, Debug)]
+pub enum Fill {
+    Color(Hsla),
+}
+
+impl Fill {
+    pub fn color(&self) -> Option<Hsla> {
+        match self {
+            Fill::Color(color) => Some(*color),
+        }
+    }
+}
+
+impl Default for Fill {
+    fn default() -> Self {
+        Self::Color(Hsla::default())
+    }
+}
+
+impl From<Hsla> for Fill {
+    fn from(color: Hsla) -> Self {
+        Self::Color(color)
+    }
+}
+
+impl From<TextStyle> for HighlightStyle {
+    fn from(other: TextStyle) -> Self {
+        Self::from(&other)
+    }
+}
+
+impl From<&TextStyle> for HighlightStyle {
+    fn from(other: &TextStyle) -> Self {
+        Self {
+            color: Some(other.color),
+            font_weight: Some(other.font_weight),
+            font_style: Some(other.font_style),
+            underline: other.underline.clone(),
+            fade_out: None,
+        }
+    }
+}
+
+impl HighlightStyle {
+    pub fn highlight(&mut self, other: HighlightStyle) {
+        match (self.color, other.color) {
+            (Some(self_color), Some(other_color)) => {
+                self.color = Some(Hsla::blend(other_color, self_color));
+            }
+            (None, Some(other_color)) => {
+                self.color = Some(other_color);
+            }
+            _ => {}
+        }
+
+        if other.font_weight.is_some() {
+            self.font_weight = other.font_weight;
+        }
+
+        if other.font_style.is_some() {
+            self.font_style = other.font_style;
+        }
+
+        if other.underline.is_some() {
+            self.underline = other.underline;
+        }
+
+        match (other.fade_out, self.fade_out) {
+            (Some(source_fade), None) => self.fade_out = Some(source_fade),
+            (Some(source_fade), Some(dest_fade)) => {
+                self.fade_out = Some((dest_fade * (1. + source_fade)).clamp(0., 1.));
+            }
+            _ => {}
+        }
+    }
+}
+
+impl From<Hsla> for HighlightStyle {
+    fn from(color: Hsla) -> Self {
+        Self {
+            color: Some(color),
+            ..Default::default()
+        }
+    }
+}

crates/gpui3/src/style_helpers.rs 🔗

@@ -0,0 +1,308 @@
+use crate::{
+    self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent,
+    Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement,
+};
+
+pub trait StyleHelpers: Styled<Style = Style> {
+    gpui3_macros::style_helpers!();
+
+    fn h(mut self, height: Length) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().size.height = Some(height);
+        self
+    }
+
+    /// size_{n}: Sets width & height to {n}
+    ///
+    /// Example:
+    /// size_1: Sets width & height to 1
+    fn size(mut self, size: Length) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().size.height = Some(size);
+        self.declared_style().size.width = Some(size);
+        self
+    }
+
+    fn full(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().size.width = Some(relative(1.).into());
+        self.declared_style().size.height = Some(relative(1.).into());
+        self
+    }
+
+    fn relative(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().position = Some(Position::Relative);
+        self
+    }
+
+    fn absolute(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().position = Some(Position::Absolute);
+        self
+    }
+
+    fn block(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().display = Some(Display::Block);
+        self
+    }
+
+    fn flex(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().display = Some(Display::Flex);
+        self
+    }
+
+    fn flex_col(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_direction = Some(FlexDirection::Column);
+        self
+    }
+
+    fn flex_row(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_direction = Some(FlexDirection::Row);
+        self
+    }
+
+    fn flex_1(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(1.);
+        self.declared_style().flex_shrink = Some(1.);
+        self.declared_style().flex_basis = Some(relative(0.).into());
+        self
+    }
+
+    fn flex_auto(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(1.);
+        self.declared_style().flex_shrink = Some(1.);
+        self.declared_style().flex_basis = Some(Length::Auto);
+        self
+    }
+
+    fn flex_initial(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(0.);
+        self.declared_style().flex_shrink = Some(1.);
+        self.declared_style().flex_basis = Some(Length::Auto);
+        self
+    }
+
+    fn flex_none(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(0.);
+        self.declared_style().flex_shrink = Some(0.);
+        self
+    }
+
+    fn grow(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(1.);
+        self
+    }
+
+    fn items_start(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().align_items = Some(AlignItems::FlexStart);
+        self
+    }
+
+    fn items_end(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().align_items = Some(AlignItems::FlexEnd);
+        self
+    }
+
+    fn items_center(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().align_items = Some(AlignItems::Center);
+        self
+    }
+
+    fn justify_between(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
+        self
+    }
+
+    fn justify_center(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::Center);
+        self
+    }
+
+    fn justify_start(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::Start);
+        self
+    }
+
+    fn justify_end(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::End);
+        self
+    }
+
+    fn justify_around(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
+        self
+    }
+
+    fn fill<F>(mut self, fill: F) -> Self
+    where
+        F: Into<Fill>,
+        Self: Sized,
+    {
+        self.declared_style().fill = Some(fill.into());
+        self
+    }
+
+    fn border_color<C>(mut self, border_color: C) -> Self
+    where
+        C: Into<Hsla>,
+        Self: Sized,
+    {
+        self.declared_style().border_color = Some(border_color.into());
+        self
+    }
+
+    fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
+        let style: &mut StyleRefinement = self.declared_style();
+        &mut style.text
+    }
+
+    fn text_color(mut self, color: impl Into<Hsla>) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
+        self
+    }
+
+    fn text_xs(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(0.75));
+        self
+    }
+
+    fn text_sm(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(0.875));
+        self
+    }
+
+    fn text_base(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.0));
+        self
+    }
+
+    fn text_lg(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.125));
+        self
+    }
+
+    fn text_xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.25));
+        self
+    }
+
+    fn text_2xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.5));
+        self
+    }
+
+    fn text_3xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.875));
+        self
+    }
+
+    fn font(mut self, family_name: impl Into<SharedString>) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_family = Some(family_name.into());
+        self
+    }
+}

crates/gpui3/src/styled.rs 🔗

@@ -0,0 +1,26 @@
+use crate::{Refineable, RefinementCascade};
+
+pub trait Styled {
+    type Style: Refineable + Default;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
+
+    fn computed_style(&mut self) -> Self::Style {
+        Self::Style::from_refinement(&self.style_cascade().merged())
+    }
+
+    // fn hover(self) -> Hoverable<Self>
+    // where
+    //     Self: Sized,
+    // {
+    //     hoverable(self)
+    // }
+
+    // fn active(self) -> Pressable<Self>
+    // where
+    //     Self: Sized,
+    // {
+    //     pressable(self)
+    // }
+}

crates/gpui3/src/taffy.rs 🔗

@@ -0,0 +1,383 @@
+use super::{
+    AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
+    Style,
+};
+use collections::HashMap;
+use std::fmt::Debug;
+use taffy::{
+    geometry::Size as TaffySize,
+    style::AvailableSpace as TaffyAvailableSpace,
+    tree::{Measurable, MeasureFunc, NodeId},
+    Taffy,
+};
+
+pub struct TaffyLayoutEngine {
+    taffy: Taffy,
+    children_to_parents: HashMap<LayoutId, LayoutId>,
+    absolute_layouts: HashMap<LayoutId, Layout>,
+}
+
+impl TaffyLayoutEngine {
+    pub fn new() -> Self {
+        TaffyLayoutEngine {
+            taffy: Taffy::new(),
+            children_to_parents: HashMap::default(),
+            absolute_layouts: HashMap::default(),
+        }
+    }
+
+    pub fn request_layout(
+        &mut self,
+        style: Style,
+        rem_size: Pixels,
+        children: &[LayoutId],
+    ) -> Result<LayoutId> {
+        let style = style.to_taffy(rem_size);
+        if children.is_empty() {
+            Ok(self.taffy.new_leaf(style)?.into())
+        } else {
+            let parent_id = self
+                .taffy
+                // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
+                .new_with_children(style, unsafe { std::mem::transmute(children) })?
+                .into();
+            for child_id in children {
+                self.children_to_parents.insert(*child_id, parent_id);
+            }
+            Ok(parent_id)
+        }
+    }
+
+    pub fn request_measured_layout(
+        &mut self,
+        style: Style,
+        rem_size: Pixels,
+        measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
+            + Send
+            + Sync
+            + 'static,
+    ) -> Result<LayoutId> {
+        let style = style.to_taffy(rem_size);
+
+        let measurable = Box::new(Measureable(measure)) as Box<dyn Measurable>;
+        Ok(self
+            .taffy
+            .new_leaf_with_measure(style, MeasureFunc::Boxed(measurable))?
+            .into())
+    }
+
+    pub fn compute_layout(
+        &mut self,
+        id: LayoutId,
+        available_space: Size<AvailableSpace>,
+    ) -> Result<()> {
+        self.taffy
+            .compute_layout(id.into(), available_space.into())?;
+        Ok(())
+    }
+
+    pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
+        if let Some(layout) = self.absolute_layouts.get(&id).cloned() {
+            return Ok(layout);
+        }
+
+        let mut relative_layout: Layout = self.taffy.layout(id.into()).map(Into::into)?;
+        if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
+            let parent_layout = self.layout(parent_id)?;
+            relative_layout.bounds.origin += parent_layout.bounds.origin;
+        }
+        self.absolute_layouts.insert(id, relative_layout.clone());
+
+        Ok(relative_layout)
+    }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq, Debug)]
+#[repr(transparent)]
+pub struct LayoutId(NodeId);
+
+impl std::hash::Hash for LayoutId {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        u64::from(self.0).hash(state);
+    }
+}
+
+impl From<NodeId> for LayoutId {
+    fn from(node_id: NodeId) -> Self {
+        Self(node_id)
+    }
+}
+
+impl From<LayoutId> for NodeId {
+    fn from(layout_id: LayoutId) -> NodeId {
+        layout_id.0
+    }
+}
+
+struct Measureable<F>(F);
+
+impl<F> taffy::tree::Measurable for Measureable<F>
+where
+    F: Send + Sync + Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>,
+{
+    fn measure(
+        &self,
+        known_dimensions: TaffySize<Option<f32>>,
+        available_space: TaffySize<TaffyAvailableSpace>,
+    ) -> TaffySize<f32> {
+        let known_dimensions: Size<Option<f32>> = known_dimensions.into();
+        let known_dimensions: Size<Option<Pixels>> = known_dimensions.map(|d| d.map(Into::into));
+        let available_space = available_space.into();
+        let size = (self.0)(known_dimensions, available_space);
+        size.into()
+    }
+}
+
+trait ToTaffy<Output> {
+    fn to_taffy(&self, rem_size: Pixels) -> Output;
+}
+
+impl ToTaffy<taffy::style::Style> for Style {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
+        taffy::style::Style {
+            display: self.display,
+            overflow: self.overflow.clone().into(),
+            scrollbar_width: self.scrollbar_width,
+            position: self.position,
+            inset: self.inset.to_taffy(rem_size),
+            size: self.size.to_taffy(rem_size),
+            min_size: self.min_size.to_taffy(rem_size),
+            max_size: self.max_size.to_taffy(rem_size),
+            aspect_ratio: self.aspect_ratio,
+            margin: self.margin.to_taffy(rem_size),
+            padding: self.padding.to_taffy(rem_size),
+            border: self.border_widths.to_taffy(rem_size),
+            align_items: self.align_items,
+            align_self: self.align_self,
+            align_content: self.align_content,
+            justify_content: self.justify_content,
+            gap: self.gap.to_taffy(rem_size),
+            flex_direction: self.flex_direction,
+            flex_wrap: self.flex_wrap,
+            flex_basis: self.flex_basis.to_taffy(rem_size),
+            flex_grow: self.flex_grow,
+            flex_shrink: self.flex_shrink,
+            ..Default::default() // Ignore grid properties for now
+        }
+    }
+}
+
+// impl ToTaffy for Bounds<Length> {
+//     type Output = taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto>;
+
+//     fn to_taffy(
+//         &self,
+//         rem_size: Pixels,
+//     ) -> taffy::prelude::Bounds<taffy::prelude::LengthPercentageAuto> {
+//         taffy::prelude::Bounds {
+//             origin: self.origin.to_taffy(rem_size),
+//             size: self.size.to_taffy(rem_size),
+//         }
+//     }
+// }
+
+impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
+        match self {
+            Length::Definite(length) => length.to_taffy(rem_size),
+            Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
+        }
+    }
+}
+
+impl ToTaffy<taffy::style::Dimension> for Length {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::Dimension {
+        match self {
+            Length::Definite(length) => length.to_taffy(rem_size),
+            Length::Auto => taffy::prelude::Dimension::Auto,
+        }
+    }
+}
+
+impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
+        match self {
+            DefiniteLength::Absolute(length) => match length {
+                AbsoluteLength::Pixels(pixels) => {
+                    taffy::style::LengthPercentage::Length(pixels.into())
+                }
+                AbsoluteLength::Rems(rems) => {
+                    taffy::style::LengthPercentage::Length((*rems * rem_size).into())
+                }
+            },
+            DefiniteLength::Fraction(fraction) => {
+                taffy::style::LengthPercentage::Percent(*fraction)
+            }
+        }
+    }
+}
+
+impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentageAuto {
+        match self {
+            DefiniteLength::Absolute(length) => match length {
+                AbsoluteLength::Pixels(pixels) => {
+                    taffy::style::LengthPercentageAuto::Length(pixels.into())
+                }
+                AbsoluteLength::Rems(rems) => {
+                    taffy::style::LengthPercentageAuto::Length((*rems * rem_size).into())
+                }
+            },
+            DefiniteLength::Fraction(fraction) => {
+                taffy::style::LengthPercentageAuto::Percent(*fraction)
+            }
+        }
+    }
+}
+
+impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Dimension {
+        match self {
+            DefiniteLength::Absolute(length) => match length {
+                AbsoluteLength::Pixels(pixels) => taffy::style::Dimension::Length(pixels.into()),
+                AbsoluteLength::Rems(rems) => {
+                    taffy::style::Dimension::Length((*rems * rem_size).into())
+                }
+            },
+            DefiniteLength::Fraction(fraction) => taffy::style::Dimension::Percent(*fraction),
+        }
+    }
+}
+
+impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::style::LengthPercentage {
+        match self {
+            AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(pixels.into()),
+            AbsoluteLength::Rems(rems) => {
+                taffy::style::LengthPercentage::Length((*rems * rem_size).into())
+            }
+        }
+    }
+}
+
+impl<T, T2: Clone + Debug> From<taffy::geometry::Point<T>> for Point<T2>
+where
+    T: Into<T2>,
+{
+    fn from(point: taffy::geometry::Point<T>) -> Point<T2> {
+        Point {
+            x: point.x.into(),
+            y: point.y.into(),
+        }
+    }
+}
+
+impl<T: Clone + Debug, T2> Into<taffy::geometry::Point<T2>> for Point<T>
+where
+    T: Into<T2>,
+{
+    fn into(self) -> taffy::geometry::Point<T2> {
+        taffy::geometry::Point {
+            x: self.x.into(),
+            y: self.y.into(),
+        }
+    }
+}
+
+impl<T: ToTaffy<U> + Clone + Debug, U> ToTaffy<taffy::geometry::Size<U>> for Size<T> {
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Size<U> {
+        taffy::geometry::Size {
+            width: self.width.to_taffy(rem_size).into(),
+            height: self.height.to_taffy(rem_size).into(),
+        }
+    }
+}
+
+impl<T, U> ToTaffy<taffy::geometry::Rect<U>> for Edges<T>
+where
+    T: ToTaffy<U> + Clone + Debug,
+{
+    fn to_taffy(&self, rem_size: Pixels) -> taffy::geometry::Rect<U> {
+        taffy::geometry::Rect {
+            top: self.top.to_taffy(rem_size).into(),
+            right: self.right.to_taffy(rem_size).into(),
+            bottom: self.bottom.to_taffy(rem_size).into(),
+            left: self.left.to_taffy(rem_size).into(),
+        }
+    }
+}
+
+impl<T: Into<U>, U: Clone + Debug> From<TaffySize<T>> for Size<U> {
+    fn from(taffy_size: taffy::geometry::Size<T>) -> Self {
+        Size {
+            width: taffy_size.width.into(),
+            height: taffy_size.height.into(),
+        }
+    }
+}
+
+impl<T: Into<U> + Clone + Debug, U> From<Size<T>> for taffy::geometry::Size<U> {
+    fn from(size: Size<T>) -> Self {
+        taffy::geometry::Size {
+            width: size.width.into(),
+            height: size.height.into(),
+        }
+    }
+}
+
+// impl From<TaffySize<Option<f32>>> for Size<Option<Pixels>> {
+//     fn from(value: TaffySize<Option<f32>>) -> Self {
+//         Self {
+//             width: value.width.map(Into::into),
+//             height: value.height.map(Into::into),
+//         }
+//     }
+// }
+
+#[derive(Copy, Clone, Debug)]
+pub enum AvailableSpace {
+    /// The amount of space available is the specified number of pixels
+    Definite(Pixels),
+    /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
+    MinContent,
+    /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
+    MaxContent,
+}
+
+impl From<AvailableSpace> for TaffyAvailableSpace {
+    fn from(space: AvailableSpace) -> TaffyAvailableSpace {
+        match space {
+            AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
+            AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
+            AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
+        }
+    }
+}
+
+impl From<TaffyAvailableSpace> for AvailableSpace {
+    fn from(space: TaffyAvailableSpace) -> AvailableSpace {
+        match space {
+            TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
+            TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
+            TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
+        }
+    }
+}
+
+impl From<Pixels> for AvailableSpace {
+    fn from(pixels: Pixels) -> Self {
+        AvailableSpace::Definite(pixels)
+    }
+}
+
+impl From<&taffy::tree::Layout> for Layout {
+    fn from(layout: &taffy::tree::Layout) -> Self {
+        Layout {
+            order: layout.order,
+            bounds: Bounds {
+                origin: layout.location.into(),
+                size: layout.size.into(),
+            },
+        }
+    }
+}

crates/gpui3/src/text_system.rs 🔗

@@ -0,0 +1,477 @@
+mod font_features;
+mod line_wrapper;
+mod text_layout_cache;
+
+use anyhow::anyhow;
+pub use font_features::*;
+use line_wrapper::*;
+pub use text_layout_cache::*;
+
+use crate::{
+    px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle,
+};
+use collections::HashMap;
+use core::fmt;
+use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
+use std::{
+    fmt::{Debug, Display, Formatter},
+    hash::{Hash, Hasher},
+    ops::{Deref, DerefMut},
+    sync::Arc,
+};
+
+#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
+pub struct FontId(pub usize);
+
+#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
+pub struct FontFamilyId(pub usize);
+
+pub struct TextSystem {
+    text_layout_cache: Arc<TextLayoutCache>,
+    platform_text_system: Arc<dyn PlatformTextSystem>,
+    font_ids_by_font: RwLock<HashMap<Font, FontId>>,
+    fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
+    font_metrics: RwLock<HashMap<Font, FontMetrics>>,
+    wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
+    font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
+}
+
+impl TextSystem {
+    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
+        TextSystem {
+            text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
+            platform_text_system,
+            font_metrics: RwLock::new(HashMap::default()),
+            font_ids_by_font: RwLock::new(HashMap::default()),
+            fonts_by_font_id: RwLock::new(HashMap::default()),
+            wrapper_pool: Mutex::new(HashMap::default()),
+            font_runs_pool: Default::default(),
+        }
+    }
+
+    pub fn font_id(&self, font: &Font) -> Result<FontId> {
+        let font_id = self.font_ids_by_font.read().get(font).copied();
+
+        if let Some(font_id) = font_id {
+            Ok(font_id)
+        } else {
+            let font_id = self.platform_text_system.font_id(font)?;
+            self.font_ids_by_font.write().insert(font.clone(), font_id);
+            self.fonts_by_font_id.write().insert(font_id, font.clone());
+            Ok(font_id)
+        }
+    }
+
+    pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> {
+        self.fonts_by_font_id
+            .read()
+            .get(&font_id)
+            .ok_or_else(|| anyhow!("font not found"))
+            .map(|font| f(self, font))
+    }
+
+    pub fn bounding_box(&self, font: &Font, font_size: Pixels) -> Result<Bounds<Pixels>> {
+        self.read_metrics(&font, |metrics| metrics.bounding_box(font_size))
+    }
+
+    pub fn typographic_bounds(
+        &self,
+        font: &Font,
+        font_size: Pixels,
+        character: char,
+    ) -> Result<Bounds<Pixels>> {
+        let font_id = self.font_id(font)?;
+        let glyph_id = self
+            .platform_text_system
+            .glyph_for_char(font_id, character)
+            .ok_or_else(|| anyhow!("glyph not found for character '{}'", character))?;
+        let bounds = self
+            .platform_text_system
+            .typographic_bounds(font_id, glyph_id)?;
+        self.read_metrics(font, |metrics| {
+            (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
+        })
+    }
+
+    pub fn advance(&self, font: &Font, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
+        let font_id = self.font_id(font)?;
+        let glyph_id = self
+            .platform_text_system
+            .glyph_for_char(font_id, ch)
+            .ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
+        let result =
+            self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32;
+
+        Ok(result * font_size)
+    }
+
+    pub fn units_per_em(&self, font: &Font) -> Result<u32> {
+        self.read_metrics(font, |metrics| metrics.units_per_em as u32)
+    }
+
+    pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.cap_height(font_size))
+    }
+
+    pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.x_height(font_size))
+    }
+
+    pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.ascent(font_size))
+    }
+
+    pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font, |metrics| metrics.descent(font_size))
+    }
+
+    pub fn baseline_offset(
+        &self,
+        font: &Font,
+        font_size: Pixels,
+        line_height: Pixels,
+    ) -> Result<Pixels> {
+        let ascent = self.ascent(font, font_size)?;
+        let descent = self.descent(font, font_size)?;
+        let padding_top = (line_height - ascent - descent) / 2.;
+        Ok(padding_top + ascent)
+    }
+
+    fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
+        let lock = self.font_metrics.upgradable_read();
+
+        if let Some(metrics) = lock.get(font) {
+            Ok(read(metrics))
+        } else {
+            let font_id = self.platform_text_system.font_id(&font)?;
+            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
+            let metrics = lock
+                .entry(font.clone())
+                .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
+            Ok(read(metrics))
+        }
+    }
+
+    pub fn layout_line(
+        &self,
+        text: &str,
+        font_size: Pixels,
+        runs: &[(usize, RunStyle)],
+    ) -> Result<Line> {
+        let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
+
+        dbg!("got font runs from pool");
+        let mut last_font: Option<&Font> = None;
+        for (len, style) in runs {
+            dbg!(len);
+            if let Some(last_font) = last_font.as_ref() {
+                dbg!("a");
+                if **last_font == style.font {
+                    dbg!("b");
+                    font_runs.last_mut().unwrap().0 += len;
+                    dbg!("c");
+                    continue;
+                }
+                dbg!("d");
+            }
+            dbg!("e");
+            last_font = Some(&style.font);
+            dbg!("f");
+            font_runs.push((*len, self.font_id(&style.font)?));
+            dbg!("g");
+        }
+
+        dbg!("built font runs");
+
+        let layout = self
+            .text_layout_cache
+            .layout_line(text, font_size, &font_runs);
+
+        font_runs.clear();
+        self.font_runs_pool.lock().push(font_runs);
+
+        Ok(Line::new(layout.clone(), runs))
+    }
+
+    pub fn finish_frame(&self) {
+        self.text_layout_cache.finish_frame()
+    }
+
+    pub fn line_wrapper(
+        self: &Arc<Self>,
+        font: Font,
+        font_size: Pixels,
+    ) -> Result<LineWrapperHandle> {
+        let lock = &mut self.wrapper_pool.lock();
+        let font_id = self.font_id(&font)?;
+        let wrappers = lock
+            .entry(FontIdWithSize { font_id, font_size })
+            .or_default();
+        let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| {
+            Ok(LineWrapper::new(
+                font_id,
+                font_size,
+                self.platform_text_system.clone(),
+            ))
+        })?;
+
+        Ok(LineWrapperHandle {
+            wrapper: Some(wrapper),
+            text_system: self.clone(),
+        })
+    }
+}
+
+#[derive(Hash, Eq, PartialEq)]
+struct FontIdWithSize {
+    font_id: FontId,
+    font_size: Pixels,
+}
+
+pub struct LineWrapperHandle {
+    wrapper: Option<LineWrapper>,
+    text_system: Arc<TextSystem>,
+}
+
+impl Drop for LineWrapperHandle {
+    fn drop(&mut self) {
+        let mut state = self.text_system.wrapper_pool.lock();
+        let wrapper = self.wrapper.take().unwrap();
+        state
+            .get_mut(&FontIdWithSize {
+                font_id: wrapper.font_id.clone(),
+                font_size: wrapper.font_size,
+            })
+            .unwrap()
+            .push(wrapper);
+    }
+}
+
+impl Deref for LineWrapperHandle {
+    type Target = LineWrapper;
+
+    fn deref(&self) -> &Self::Target {
+        self.wrapper.as_ref().unwrap()
+    }
+}
+
+impl DerefMut for LineWrapperHandle {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.wrapper.as_mut().unwrap()
+    }
+}
+
+/// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0,
+/// with 400.0 as normal.
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
+pub struct FontWeight(pub f32);
+
+impl Default for FontWeight {
+    #[inline]
+    fn default() -> FontWeight {
+        FontWeight::NORMAL
+    }
+}
+
+impl Hash for FontWeight {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
+    }
+}
+
+impl Eq for FontWeight {}
+
+impl FontWeight {
+    /// Thin weight (100), the thinnest value.
+    pub const THIN: FontWeight = FontWeight(100.0);
+    /// Extra light weight (200).
+    pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
+    /// Light weight (300).
+    pub const LIGHT: FontWeight = FontWeight(300.0);
+    /// Normal (400).
+    pub const NORMAL: FontWeight = FontWeight(400.0);
+    /// Medium weight (500, higher than normal).
+    pub const MEDIUM: FontWeight = FontWeight(500.0);
+    /// Semibold weight (600).
+    pub const SEMIBOLD: FontWeight = FontWeight(600.0);
+    /// Bold weight (700).
+    pub const BOLD: FontWeight = FontWeight(700.0);
+    /// Extra-bold weight (800).
+    pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
+    /// Black weight (900), the thickest value.
+    pub const BLACK: FontWeight = FontWeight(900.0);
+}
+
+/// Allows italic or oblique faces to be selected.
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
+pub enum FontStyle {
+    /// A face that is neither italic not obliqued.
+    Normal,
+    /// A form that is generally cursive in nature.
+    Italic,
+    /// A typically-sloped version of the regular face.
+    Oblique,
+}
+
+impl Default for FontStyle {
+    fn default() -> FontStyle {
+        FontStyle::Normal
+    }
+}
+
+impl Display for FontStyle {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        Debug::fmt(self, f)
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct RunStyle {
+    pub font: Font,
+    pub color: Hsla,
+    pub underline: Option<UnderlineStyle>,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct GlyphId(u32);
+
+impl From<GlyphId> for u32 {
+    fn from(value: GlyphId) -> Self {
+        value.0
+    }
+}
+
+impl From<u16> for GlyphId {
+    fn from(num: u16) -> Self {
+        GlyphId(num as u32)
+    }
+}
+
+impl From<u32> for GlyphId {
+    fn from(num: u32) -> Self {
+        GlyphId(num)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Glyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
+#[derive(Default, Debug)]
+pub struct LineLayout {
+    pub font_size: Pixels,
+    pub width: Pixels,
+    pub ascent: Pixels,
+    pub descent: Pixels,
+    pub runs: Vec<Run>,
+    pub len: usize,
+}
+
+#[derive(Debug)]
+pub struct Run {
+    pub font_id: FontId,
+    pub glyphs: Vec<Glyph>,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct Font {
+    pub family: SharedString,
+    pub features: FontFeatures,
+    pub weight: FontWeight,
+    pub style: FontStyle,
+}
+
+pub fn font(family: impl Into<SharedString>) -> Font {
+    Font {
+        family: family.into(),
+        features: FontFeatures::default(),
+        weight: FontWeight::default(),
+        style: FontStyle::default(),
+    }
+}
+
+impl Font {
+    pub fn bold(mut self) -> Self {
+        self.weight = FontWeight::BOLD;
+        self
+    }
+}
+
+/// A struct for storing font metrics.
+/// It is used to define the measurements of a typeface.
+#[derive(Clone, Copy, Debug)]
+pub struct FontMetrics {
+    /// The number of font units that make up the "em square",
+    /// a scalable grid for determining the size of a typeface.
+    pub(crate) units_per_em: u32,
+
+    /// The vertical distance from the baseline of the font to the top of the glyph covers.
+    pub(crate) ascent: f32,
+
+    /// The vertical distance from the baseline of the font to the bottom of the glyph covers.
+    pub(crate) descent: f32,
+
+    /// The recommended additional space to add between lines of type.
+    pub(crate) line_gap: f32,
+
+    /// The suggested position of the underline.
+    pub(crate) underline_position: f32,
+
+    /// The suggested thickness of the underline.
+    pub(crate) underline_thickness: f32,
+
+    /// The height of a capital letter measured from the baseline of the font.
+    pub(crate) cap_height: f32,
+
+    /// The height of a lowercase x.
+    pub(crate) x_height: f32,
+
+    /// The outer limits of the area that the font covers.
+    pub(crate) bounding_box: Bounds<f32>,
+}
+
+impl FontMetrics {
+    /// Returns the vertical distance from the baseline of the font to the top of the glyph covers in pixels.
+    pub fn ascent(&self, font_size: Pixels) -> Pixels {
+        Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
+    }
+
+    /// Returns the vertical distance from the baseline of the font to the bottom of the glyph covers in pixels.
+    pub fn descent(&self, font_size: Pixels) -> Pixels {
+        Pixels((self.descent / self.units_per_em as f32) * font_size.0)
+    }
+
+    /// Returns the recommended additional space to add between lines of type in pixels.
+    pub fn line_gap(&self, font_size: Pixels) -> Pixels {
+        Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
+    }
+
+    /// Returns the suggested position of the underline in pixels.
+    pub fn underline_position(&self, font_size: Pixels) -> Pixels {
+        Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
+    }
+
+    /// Returns the suggested thickness of the underline in pixels.
+    pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
+        Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
+    }
+
+    /// Returns the height of a capital letter measured from the baseline of the font in pixels.
+    pub fn cap_height(&self, font_size: Pixels) -> Pixels {
+        Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
+    }
+
+    /// Returns the height of a lowercase x in pixels.
+    pub fn x_height(&self, font_size: Pixels) -> Pixels {
+        Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
+    }
+
+    /// Returns the outer limits of the area that the font covers in pixels.
+    pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
+        (self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
+    }
+}

crates/gpui3/src/text_system/font_features.rs 🔗

@@ -0,0 +1,162 @@
+use schemars::{
+    schema::{InstanceType, Schema, SchemaObject, SingleOrVec},
+    JsonSchema,
+};
+
+macro_rules! create_definitions {
+    ($($(#[$meta:meta])* ($name:ident, $idx:expr)),* $(,)?) => {
+        #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
+        pub struct FontFeatures {
+            enabled: u64,
+            disabled: u64,
+        }
+
+        impl FontFeatures {
+            $(
+                pub fn $name(&self) -> Option<bool> {
+                    if (self.enabled & (1 << $idx)) != 0 {
+                        Some(true)
+                    } else if (self.disabled & (1 << $idx)) != 0 {
+                        Some(false)
+                    } else {
+                        None
+                    }
+                }
+            )*
+        }
+
+        impl std::fmt::Debug for FontFeatures {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                let mut debug = f.debug_struct("FontFeatures");
+                $(
+                    if let Some(value) = self.$name() {
+                        debug.field(stringify!($name), &value);
+                    };
+                )*
+                debug.finish()
+            }
+        }
+
+        impl<'de> serde::Deserialize<'de> for FontFeatures {
+            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+            where
+                D: serde::Deserializer<'de>,
+            {
+                use serde::de::{MapAccess, Visitor};
+                use std::fmt;
+
+                struct FontFeaturesVisitor;
+
+                impl<'de> Visitor<'de> for FontFeaturesVisitor {
+                    type Value = FontFeatures;
+
+                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                        formatter.write_str("a map of font features")
+                    }
+
+                    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
+                    where
+                        M: MapAccess<'de>,
+                    {
+                        let mut enabled: u64 = 0;
+                        let mut disabled: u64 = 0;
+
+                        while let Some((key, value)) = access.next_entry::<String, Option<bool>>()? {
+                            let idx = match key.as_str() {
+                                $(stringify!($name) => $idx,)*
+                                _ => continue,
+                            };
+                            match value {
+                                Some(true) => enabled |= 1 << idx,
+                                Some(false) => disabled |= 1 << idx,
+                                None => {}
+                            };
+                        }
+                        Ok(FontFeatures { enabled, disabled })
+                    }
+                }
+
+                let features = deserializer.deserialize_map(FontFeaturesVisitor)?;
+                Ok(features)
+            }
+        }
+
+        impl serde::Serialize for FontFeatures {
+            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+            where
+                S: serde::Serializer,
+            {
+                use serde::ser::SerializeMap;
+
+                let mut map = serializer.serialize_map(None)?;
+
+                $(
+                    let feature = stringify!($name);
+                    if let Some(value) = self.$name() {
+                        map.serialize_entry(feature, &value)?;
+                    }
+                )*
+
+                map.end()
+            }
+        }
+
+        impl JsonSchema for FontFeatures {
+            fn schema_name() -> String {
+                "FontFeatures".into()
+            }
+
+            fn json_schema(_: &mut schemars::gen::SchemaGenerator) -> Schema {
+                let mut schema = SchemaObject::default();
+                let properties = &mut schema.object().properties;
+                let feature_schema = Schema::Object(SchemaObject {
+                    instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
+                    ..Default::default()
+                });
+
+                $(
+                    properties.insert(stringify!($name).to_owned(), feature_schema.clone());
+                )*
+
+                schema.into()
+            }
+        }
+    };
+}
+
+create_definitions!(
+    (calt, 0),
+    (case, 1),
+    (cpsp, 2),
+    (frac, 3),
+    (liga, 4),
+    (onum, 5),
+    (ordn, 6),
+    (pnum, 7),
+    (ss01, 8),
+    (ss02, 9),
+    (ss03, 10),
+    (ss04, 11),
+    (ss05, 12),
+    (ss06, 13),
+    (ss07, 14),
+    (ss08, 15),
+    (ss09, 16),
+    (ss10, 17),
+    (ss11, 18),
+    (ss12, 19),
+    (ss13, 20),
+    (ss14, 21),
+    (ss15, 22),
+    (ss16, 23),
+    (ss17, 24),
+    (ss18, 25),
+    (ss19, 26),
+    (ss20, 27),
+    (subs, 28),
+    (sups, 29),
+    (swsh, 30),
+    (titl, 31),
+    (tnum, 32),
+    (zero, 33)
+);

crates/gpui3/src/text_system/line_wrapper.rs 🔗

@@ -0,0 +1,329 @@
+use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary};
+use collections::HashMap;
+use std::{iter, sync::Arc};
+
+pub struct LineWrapper {
+    platform_text_system: Arc<dyn PlatformTextSystem>,
+    pub(crate) font_id: FontId,
+    pub(crate) font_size: Pixels,
+    cached_ascii_char_widths: [Option<Pixels>; 128],
+    cached_other_char_widths: HashMap<char, Pixels>,
+}
+
+impl LineWrapper {
+    pub const MAX_INDENT: u32 = 256;
+
+    pub fn new(
+        font_id: FontId,
+        font_size: Pixels,
+        text_system: Arc<dyn PlatformTextSystem>,
+    ) -> Self {
+        Self {
+            platform_text_system: text_system,
+            font_id,
+            font_size,
+            cached_ascii_char_widths: [None; 128],
+            cached_other_char_widths: HashMap::default(),
+        }
+    }
+
+    pub fn wrap_line<'a>(
+        &'a mut self,
+        line: &'a str,
+        wrap_width: Pixels,
+    ) -> impl Iterator<Item = Boundary> + 'a {
+        let mut width = px(0.);
+        let mut first_non_whitespace_ix = None;
+        let mut indent = None;
+        let mut last_candidate_ix = 0;
+        let mut last_candidate_width = px(0.);
+        let mut last_wrap_ix = 0;
+        let mut prev_c = '\0';
+        let mut char_indices = line.char_indices();
+        iter::from_fn(move || {
+            for (ix, c) in char_indices.by_ref() {
+                if c == '\n' {
+                    continue;
+                }
+
+                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+                    last_candidate_ix = ix;
+                    last_candidate_width = width;
+                }
+
+                if c != ' ' && first_non_whitespace_ix.is_none() {
+                    first_non_whitespace_ix = Some(ix);
+                }
+
+                let char_width = self.width_for_char(c);
+                width += char_width;
+                if width > wrap_width && ix > last_wrap_ix {
+                    if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
+                    {
+                        indent = Some(
+                            Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
+                        );
+                    }
+
+                    if last_candidate_ix > 0 {
+                        last_wrap_ix = last_candidate_ix;
+                        width -= last_candidate_width;
+                        last_candidate_ix = 0;
+                    } else {
+                        last_wrap_ix = ix;
+                        width = char_width;
+                    }
+
+                    if let Some(indent) = indent {
+                        width += self.width_for_char(' ') * indent as f32;
+                    }
+
+                    return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
+                }
+                prev_c = c;
+            }
+
+            None
+        })
+    }
+
+    pub fn wrap_shaped_line<'a>(
+        &'a mut self,
+        str: &'a str,
+        line: &'a Line,
+        wrap_width: Pixels,
+    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
+        let mut first_non_whitespace_ix = None;
+        let mut last_candidate_ix = None;
+        let mut last_candidate_x = px(0.);
+        let mut last_wrap_ix = ShapedBoundary {
+            run_ix: 0,
+            glyph_ix: 0,
+        };
+        let mut last_wrap_x = px(0.);
+        let mut prev_c = '\0';
+        let mut glyphs = line
+            .runs()
+            .iter()
+            .enumerate()
+            .flat_map(move |(run_ix, run)| {
+                run.glyphs()
+                    .iter()
+                    .enumerate()
+                    .map(move |(glyph_ix, glyph)| {
+                        let character = str[glyph.index..].chars().next().unwrap();
+                        (
+                            ShapedBoundary { run_ix, glyph_ix },
+                            character,
+                            glyph.position.x,
+                        )
+                    })
+            })
+            .peekable();
+
+        iter::from_fn(move || {
+            while let Some((ix, c, x)) = glyphs.next() {
+                if c == '\n' {
+                    continue;
+                }
+
+                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+                    last_candidate_ix = Some(ix);
+                    last_candidate_x = x;
+                }
+
+                if c != ' ' && first_non_whitespace_ix.is_none() {
+                    first_non_whitespace_ix = Some(ix);
+                }
+
+                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
+                let width = next_x - last_wrap_x;
+                if width > wrap_width && ix > last_wrap_ix {
+                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
+                        last_wrap_ix = last_candidate_ix;
+                        last_wrap_x = last_candidate_x;
+                    } else {
+                        last_wrap_ix = ix;
+                        last_wrap_x = x;
+                    }
+
+                    return Some(last_wrap_ix);
+                }
+                prev_c = c;
+            }
+
+            None
+        })
+    }
+
+    fn is_boundary(&self, prev: char, next: char) -> bool {
+        (prev == ' ') && (next != ' ')
+    }
+
+    #[inline(always)]
+    fn width_for_char(&mut self, c: char) -> Pixels {
+        if (c as u32) < 128 {
+            if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
+                cached_width
+            } else {
+                let width = self.compute_width_for_char(c);
+                self.cached_ascii_char_widths[c as usize] = Some(width);
+                width
+            }
+        } else {
+            if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
+                *cached_width
+            } else {
+                let width = self.compute_width_for_char(c);
+                self.cached_other_char_widths.insert(c, width);
+                width
+            }
+        }
+    }
+
+    fn compute_width_for_char(&self, c: char) -> Pixels {
+        self.platform_text_system
+            .layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)])
+            .width
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct Boundary {
+    pub ix: usize,
+    pub next_indent: u32,
+}
+
+impl Boundary {
+    fn new(ix: usize, next_indent: u32) -> Self {
+        Self { ix, next_indent }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{font, App, RunStyle};
+
+    #[test]
+    fn test_wrap_line() {
+        App::test().run(|cx| {
+            let text_system = cx.text_system().clone();
+            let mut wrapper = LineWrapper::new(
+                text_system.font_id(&font("Courier")).unwrap(),
+                px(16.),
+                text_system.platform_text_system.clone(),
+            );
+            assert_eq!(
+                wrapper
+                    .wrap_line("aa bbb cccc ddddd eeee", px(72.))
+                    .collect::<Vec<_>>(),
+                &[
+                    Boundary::new(7, 0),
+                    Boundary::new(12, 0),
+                    Boundary::new(18, 0)
+                ],
+            );
+            assert_eq!(
+                wrapper
+                    .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
+                    .collect::<Vec<_>>(),
+                &[
+                    Boundary::new(4, 0),
+                    Boundary::new(11, 0),
+                    Boundary::new(18, 0)
+                ],
+            );
+            assert_eq!(
+                wrapper
+                    .wrap_line("     aaaaaaa", px(72.))
+                    .collect::<Vec<_>>(),
+                &[
+                    Boundary::new(7, 5),
+                    Boundary::new(9, 5),
+                    Boundary::new(11, 5),
+                ]
+            );
+            assert_eq!(
+                wrapper
+                    .wrap_line("                            ", px(72.))
+                    .collect::<Vec<_>>(),
+                &[
+                    Boundary::new(7, 0),
+                    Boundary::new(14, 0),
+                    Boundary::new(21, 0)
+                ]
+            );
+            assert_eq!(
+                wrapper
+                    .wrap_line("          aaaaaaaaaaaaaa", px(72.))
+                    .collect::<Vec<_>>(),
+                &[
+                    Boundary::new(7, 0),
+                    Boundary::new(14, 3),
+                    Boundary::new(18, 3),
+                    Boundary::new(22, 3),
+                ]
+            );
+        });
+    }
+
+    // todo! repeat this test
+    #[test]
+    fn test_wrap_shaped_line() {
+        App::test().run(|cx| {
+            let text_system = cx.text_system().clone();
+
+            let normal = RunStyle {
+                font: font("Helvetica"),
+                color: Default::default(),
+                underline: Default::default(),
+            };
+            let bold = RunStyle {
+                font: font("Helvetica").bold(),
+                color: Default::default(),
+                underline: Default::default(),
+            };
+
+            let text = "aa bbb cccc ddddd eeee";
+            let line = text_system
+                .layout_line(
+                    text,
+                    px(16.),
+                    &[
+                        (4, normal.clone()),
+                        (5, bold.clone()),
+                        (6, normal.clone()),
+                        (1, bold.clone()),
+                        (7, normal.clone()),
+                    ],
+                )
+                .unwrap();
+
+            let mut wrapper = LineWrapper::new(
+                text_system.font_id(&normal.font).unwrap(),
+                px(16.),
+                text_system.platform_text_system.clone(),
+            );
+            assert_eq!(
+                wrapper
+                    .wrap_shaped_line(text, &line, px(72.))
+                    .collect::<Vec<_>>(),
+                &[
+                    ShapedBoundary {
+                        run_ix: 1,
+                        glyph_ix: 3
+                    },
+                    ShapedBoundary {
+                        run_ix: 2,
+                        glyph_ix: 3
+                    },
+                    ShapedBoundary {
+                        run_ix: 4,
+                        glyph_ix: 2
+                    }
+                ],
+            );
+        });
+    }
+}

crates/gpui3/src/text_system/text_layout_cache.rs 🔗

@@ -0,0 +1,478 @@
+use crate::{
+    black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
+    Run, RunStyle, UnderlineStyle, WindowContext,
+};
+use anyhow::Result;
+use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
+use smallvec::SmallVec;
+use std::{
+    borrow::Borrow,
+    collections::HashMap,
+    hash::{Hash, Hasher},
+    sync::Arc,
+};
+
+pub(crate) struct TextLayoutCache {
+    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    platform_text_system: Arc<dyn PlatformTextSystem>,
+}
+
+impl TextLayoutCache {
+    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
+        Self {
+            prev_frame: Mutex::new(HashMap::new()),
+            curr_frame: RwLock::new(HashMap::new()),
+            platform_text_system: fonts,
+        }
+    }
+
+    pub fn finish_frame(&self) {
+        let mut prev_frame = self.prev_frame.lock();
+        let mut curr_frame = self.curr_frame.write();
+        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
+        curr_frame.clear();
+    }
+
+    pub fn layout_line<'a>(
+        &'a self,
+        text: &'a str,
+        font_size: Pixels,
+        runs: &[(usize, FontId)],
+    ) -> Arc<LineLayout> {
+        dbg!("layout line");
+        let key = &CacheKeyRef {
+            text,
+            font_size,
+            runs,
+        } as &dyn CacheKey;
+        let curr_frame = self.curr_frame.upgradable_read();
+        if let Some(layout) = curr_frame.get(key) {
+            return layout.clone();
+        }
+
+        let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
+        if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
+            curr_frame.insert(key, layout.clone());
+            layout
+        } else {
+            let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
+            let key = CacheKeyValue {
+                text: text.into(),
+                font_size,
+                runs: SmallVec::from(runs),
+            };
+            curr_frame.insert(key, layout.clone());
+            layout
+        }
+    }
+}
+
+trait CacheKey {
+    fn key(&self) -> CacheKeyRef;
+}
+
+impl<'a> PartialEq for (dyn CacheKey + 'a) {
+    fn eq(&self, other: &dyn CacheKey) -> bool {
+        self.key() == other.key()
+    }
+}
+
+impl<'a> Eq for (dyn CacheKey + 'a) {}
+
+impl<'a> Hash for (dyn CacheKey + 'a) {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.key().hash(state)
+    }
+}
+
+#[derive(Eq)]
+struct CacheKeyValue {
+    text: String,
+    font_size: Pixels,
+    runs: SmallVec<[(usize, FontId); 1]>,
+}
+
+impl CacheKey for CacheKeyValue {
+    fn key(&self) -> CacheKeyRef {
+        CacheKeyRef {
+            text: self.text.as_str(),
+            font_size: self.font_size,
+            runs: self.runs.as_slice(),
+        }
+    }
+}
+
+impl PartialEq for CacheKeyValue {
+    fn eq(&self, other: &Self) -> bool {
+        self.key().eq(&other.key())
+    }
+}
+
+impl Hash for CacheKeyValue {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.key().hash(state);
+    }
+}
+
+impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
+    fn borrow(&self) -> &(dyn CacheKey + 'a) {
+        self as &dyn CacheKey
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+struct CacheKeyRef<'a> {
+    text: &'a str,
+    font_size: Pixels,
+    runs: &'a [(usize, FontId)],
+}
+
+impl<'a> CacheKey for CacheKeyRef<'a> {
+    fn key(&self) -> CacheKeyRef {
+        *self
+    }
+}
+
+impl<'a> Hash for CacheKeyRef<'a> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.text.hash(state);
+        self.font_size.hash(state);
+        for (len, font_id) in self.runs {
+            len.hash(state);
+            font_id.hash(state);
+        }
+    }
+}
+
+#[derive(Default, Debug, Clone)]
+pub struct Line {
+    layout: Arc<LineLayout>,
+    style_runs: SmallVec<[StyleRun; 32]>,
+}
+
+#[derive(Debug, Clone)]
+struct StyleRun {
+    len: u32,
+    color: Hsla,
+    underline: UnderlineStyle,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ShapedBoundary {
+    pub run_ix: usize,
+    pub glyph_ix: usize,
+}
+
+impl Line {
+    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
+        let mut style_runs = SmallVec::new();
+        for (len, style) in runs {
+            style_runs.push(StyleRun {
+                len: *len as u32,
+                color: style.color,
+                underline: style.underline.clone().unwrap_or_default(),
+            });
+        }
+        Self { layout, style_runs }
+    }
+
+    pub fn runs(&self) -> &[Run] {
+        &self.layout.runs
+    }
+
+    pub fn width(&self) -> Pixels {
+        self.layout.width
+    }
+
+    pub fn font_size(&self) -> Pixels {
+        self.layout.font_size
+    }
+
+    pub fn x_for_index(&self, index: usize) -> Pixels {
+        for run in &self.layout.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return glyph.position.x;
+                }
+            }
+        }
+        self.layout.width
+    }
+
+    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
+        for run in &self.layout.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return Some(run.font_id);
+                }
+            }
+        }
+
+        None
+    }
+
+    pub fn len(&self) -> usize {
+        self.layout.len
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.layout.len == 0
+    }
+
+    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
+        if x >= self.layout.width {
+            None
+        } else {
+            for run in self.layout.runs.iter().rev() {
+                for glyph in run.glyphs.iter().rev() {
+                    if glyph.position.x <= x {
+                        return Some(glyph.index);
+                    }
+                }
+            }
+            Some(0)
+        }
+    }
+
+    // todo!
+    pub fn paint(
+        &self,
+        origin: Point<Pixels>,
+        visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
+        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
+
+        let mut style_runs = self.style_runs.iter();
+        let mut run_end = 0;
+        let mut color = black();
+        let mut underline = None;
+
+        for run in &self.layout.runs {
+            cx.text_system().with_font(run.font_id, |system, font| {
+                let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width;
+
+                for glyph in &run.glyphs {
+                    let glyph_origin = origin + baseline_offset + glyph.position;
+                    if glyph_origin.x > visible_bounds.upper_right().x {
+                        break;
+                    }
+
+                    let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
+                    if glyph.index >= run_end {
+                        if let Some(style_run) = style_runs.next() {
+                            if let Some((_, underline_style)) = &mut underline {
+                                if style_run.underline != *underline_style {
+                                    finished_underline = underline.take();
+                                }
+                            }
+                            if style_run.underline.thickness > px(0.) {
+                                underline.get_or_insert((
+                                    point(
+                                        glyph_origin.x,
+                                        origin.y
+                                            + baseline_offset.y
+                                            + (self.layout.descent * 0.618),
+                                    ),
+                                    UnderlineStyle {
+                                        color: style_run.underline.color,
+                                        thickness: style_run.underline.thickness,
+                                        squiggly: style_run.underline.squiggly,
+                                    },
+                                ));
+                            }
+
+                            run_end += style_run.len as usize;
+                            color = style_run.color;
+                        } else {
+                            run_end = self.layout.len;
+                            finished_underline = underline.take();
+                        }
+                    }
+
+                    if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
+                        continue;
+                    }
+
+                    if let Some((_underline_origin, _underline_style)) = finished_underline {
+                        // cx.scene().insert(Underline {
+                        //     origin: underline_origin,
+                        //     width: glyph_origin.x - underline_origin.x,
+                        //     thickness: underline_style.thickness.into(),
+                        //     color: underline_style.color.unwrap(),
+                        //     squiggly: underline_style.squiggly,
+                        // });
+                    }
+
+                    // if glyph.is_emoji {
+                    //     cx.scene().push_image_glyph(scene::ImageGlyph {
+                    //         font_id: run.font_id,
+                    //         font_size: self.layout.font_size,
+                    //         id: glyph.id,
+                    //         origin: glyph_origin,
+                    //     });
+                    // } else {
+                    //     cx.scene().push_glyph(scene::Glyph {
+                    //         font_id: run.font_id,
+                    //         font_size: self.layout.font_size,
+                    //         id: glyph.id,
+                    //         origin: glyph_origin,
+                    //         color,
+                    //     });
+                    // }
+                }
+
+                anyhow::Ok(())
+            })??;
+        }
+
+        if let Some((_underline_start, _underline_style)) = underline.take() {
+            let _line_end_x = origin.x + self.layout.width;
+            // cx.scene().push_underline(Underline {
+            //     origin: underline_start,
+            //     width: line_end_x - underline_start.x,
+            //     color: underline_style.color,
+            //     thickness: underline_style.thickness.into(),
+            //     squiggly: underline_style.squiggly,
+            // });
+        }
+
+        Ok(())
+    }
+
+    pub fn paint_wrapped(
+        &self,
+        origin: Point<Pixels>,
+        _visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        boundaries: &[ShapedBoundary],
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
+        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
+
+        let mut boundaries = boundaries.into_iter().peekable();
+        let mut color_runs = self.style_runs.iter();
+        let mut style_run_end = 0;
+        let mut _color = black(); // todo!
+        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
+
+        let mut glyph_origin = origin;
+        let mut prev_position = px(0.);
+        for (run_ix, run) in self.layout.runs.iter().enumerate() {
+            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
+                glyph_origin.x += glyph.position.x - prev_position;
+
+                if boundaries
+                    .peek()
+                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
+                {
+                    boundaries.next();
+                    if let Some((_underline_origin, _underline_style)) = underline.take() {
+                        // cx.scene().push_underline(Underline {
+                        //     origin: underline_origin,
+                        //     width: glyph_origin.x - underline_origin.x,
+                        //     thickness: underline_style.thickness.into(),
+                        //     color: underline_style.color.unwrap(),
+                        //     squiggly: underline_style.squiggly,
+                        // });
+                    }
+
+                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
+                }
+                prev_position = glyph.position.x;
+
+                let mut finished_underline = None;
+                if glyph.index >= style_run_end {
+                    if let Some(style_run) = color_runs.next() {
+                        style_run_end += style_run.len as usize;
+                        _color = style_run.color;
+                        if let Some((_, underline_style)) = &mut underline {
+                            if style_run.underline != *underline_style {
+                                finished_underline = underline.take();
+                            }
+                        }
+                        if style_run.underline.thickness > px(0.) {
+                            underline.get_or_insert((
+                                glyph_origin
+                                    + point(
+                                        px(0.),
+                                        baseline_offset.y + (self.layout.descent * 0.618),
+                                    ),
+                                UnderlineStyle {
+                                    color: Some(
+                                        style_run.underline.color.unwrap_or(style_run.color),
+                                    ),
+                                    thickness: style_run.underline.thickness,
+                                    squiggly: style_run.underline.squiggly,
+                                },
+                            ));
+                        }
+                    } else {
+                        style_run_end = self.layout.len;
+                        _color = black();
+                        finished_underline = underline.take();
+                    }
+                }
+
+                if let Some((_underline_origin, _underline_style)) = finished_underline {
+                    // cx.scene().push_underline(Underline {
+                    //     origin: underline_origin,
+                    //     width: glyph_origin.x - underline_origin.x,
+                    //     thickness: underline_style.thickness.into(),
+                    //     color: underline_style.color.unwrap(),
+                    //     squiggly: underline_style.squiggly,
+                    // });
+                }
+
+                cx.text_system().with_font(run.font_id, |system, font| {
+                    let _glyph_bounds = Bounds {
+                        origin: glyph_origin,
+                        size: system.bounding_box(font, self.layout.font_size)?.size,
+                    };
+                    // if glyph_bounds.intersects(visible_bounds) {
+                    //     if glyph.is_emoji {
+                    //         cx.scene().push_image_glyph(scene::ImageGlyph {
+                    //             font_id: run.font_id,
+                    //             font_size: self.layout.font_size,
+                    //             id: glyph.id,
+                    //             origin: glyph_bounds.origin() + baseline_offset,
+                    //         });
+                    //     } else {
+                    //         cx.scene().push_glyph(scene::Glyph {
+                    //             font_id: run.font_id,
+                    //             font_size: self.layout.font_size,
+                    //             id: glyph.id,
+                    //             origin: glyph_bounds.origin() + baseline_offset,
+                    //             color,
+                    //         });
+                    //     }
+                    // }
+                    anyhow::Ok(())
+                })??;
+            }
+        }
+
+        if let Some((_underline_origin, _underline_style)) = underline.take() {
+            // let line_end_x = glyph_origin.x + self.layout.width - prev_position;
+            // cx.scene().push_underline(Underline {
+            //     origin: underline_origin,
+            //     width: line_end_x - underline_origin.x,
+            //     thickness: underline_style.thickness.into(),
+            //     color: underline_style.color,
+            //     squiggly: underline_style.squiggly,
+            // });
+        }
+
+        Ok(())
+    }
+}
+
+impl Run {
+    pub fn glyphs(&self) -> &[Glyph] {
+        &self.glyphs
+    }
+}

crates/gpui3/src/util.rs 🔗

@@ -0,0 +1,49 @@
+use smol::future::FutureExt;
+use std::{future::Future, time::Duration};
+pub use util::*;
+
+pub fn post_inc(value: &mut usize) -> usize {
+    let prev = *value;
+    *value += 1;
+    prev
+}
+
+pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
+where
+    F: Future<Output = T>,
+{
+    let timer = async {
+        smol::Timer::after(timeout).await;
+        Err(())
+    };
+    let future = async move { Ok(f.await) };
+    timer.race(future).await
+}
+
+#[cfg(any(test, feature = "test"))]
+pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
+
+#[cfg(any(test, feature = "test"))]
+impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        use backtrace::{BacktraceFmt, BytesOrWideString};
+
+        let cwd = std::env::current_dir().unwrap();
+        let cwd = cwd.parent().unwrap();
+        let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
+            std::fmt::Display::fmt(&path, fmt)
+        };
+        let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
+        for frame in self.0.frames() {
+            let mut formatted_frame = fmt.frame();
+            if frame
+                .symbols()
+                .iter()
+                .any(|s| s.filename().map_or(false, |f| f.starts_with(&cwd)))
+            {
+                formatted_frame.backtrace_frame(frame)?;
+            }
+        }
+        fmt.finish()
+    }
+}

crates/gpui3/src/view.rs 🔗

@@ -0,0 +1,143 @@
+use parking_lot::Mutex;
+
+use crate::{
+    AnyElement, Element, Handle, IntoAnyElement, Layout, LayoutId, Result, ViewContext,
+    WindowContext,
+};
+use std::{any::Any, marker::PhantomData, sync::Arc};
+
+pub struct View<S: Send + Sync, P> {
+    state: Handle<S>,
+    render: Arc<dyn Fn(&mut S, &mut ViewContext<S>) -> AnyElement<S> + Send + Sync + 'static>,
+    parent_state_type: PhantomData<P>,
+}
+
+impl<S: 'static + Send + Sync, P: 'static + Send> View<S, P> {
+    pub fn into_any(self) -> AnyView<P> {
+        AnyView {
+            view: Arc::new(Mutex::new(self)),
+            parent_state_type: PhantomData,
+        }
+    }
+}
+
+impl<S: Send + Sync, P> Clone for View<S, P> {
+    fn clone(&self) -> Self {
+        Self {
+            state: self.state.clone(),
+            render: self.render.clone(),
+            parent_state_type: PhantomData,
+        }
+    }
+}
+
+pub type RootView<S> = View<S, ()>;
+
+pub fn view<S, P, E>(
+    state: Handle<S>,
+    render: impl Fn(&mut S, &mut ViewContext<S>) -> E + Send + Sync + 'static,
+) -> View<S, P>
+where
+    S: 'static + Send + Sync,
+    P: 'static,
+    E: Element<State = S>,
+{
+    View {
+        state,
+        render: Arc::new(move |state, cx| render(state, cx).into_any()),
+        parent_state_type: PhantomData,
+    }
+}
+
+impl<S: Send + Sync + 'static, P: Send + 'static> Element for View<S, P> {
+    type State = P;
+    type FrameState = AnyElement<S>;
+
+    fn layout(
+        &mut self,
+        _: &mut Self::State,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<(LayoutId, Self::FrameState)> {
+        self.state.update(cx, |state, cx| {
+            let mut element = (self.render)(state, cx);
+            let layout_id = element.layout(state, cx)?;
+            Ok((layout_id, element))
+        })
+    }
+
+    fn paint(
+        &mut self,
+        _: Layout,
+        _: &mut Self::State,
+        element: &mut Self::FrameState,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()> {
+        self.state
+            .update(cx, |state, cx| element.paint(state, None, cx))
+    }
+}
+
+trait ViewObject: Send + 'static {
+    fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
+    fn paint(
+        &mut self,
+        layout: Layout,
+        element: &mut dyn Any,
+        cx: &mut WindowContext,
+    ) -> Result<()>;
+}
+
+impl<S: Send + Sync + 'static, P: Send + 'static> ViewObject for View<S, P> {
+    fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)> {
+        self.state.update(cx, |state, cx| {
+            let mut element = (self.render)(state, cx);
+            let layout_id = element.layout(state, cx)?;
+            let element = Box::new(element) as Box<dyn Any>;
+            Ok((layout_id, element))
+        })
+    }
+
+    fn paint(&mut self, _: Layout, element: &mut dyn Any, cx: &mut WindowContext) -> Result<()> {
+        self.state.update(cx, |state, cx| {
+            let element = element.downcast_mut::<AnyElement<S>>().unwrap();
+            element.paint(state, None, cx)
+        })
+    }
+}
+
+pub struct AnyView<S> {
+    view: Arc<Mutex<dyn ViewObject>>,
+    parent_state_type: PhantomData<S>,
+}
+
+impl<S: 'static> Element for AnyView<S> {
+    type State = ();
+    type FrameState = Box<dyn Any>;
+
+    fn layout(
+        &mut self,
+        _: &mut Self::State,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<(LayoutId, Self::FrameState)> {
+        self.view.lock().layout(cx)
+    }
+
+    fn paint(
+        &mut self,
+        layout: Layout,
+        _: &mut (),
+        element: &mut Box<dyn Any>,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()> {
+        self.view.lock().paint(layout, element.as_mut(), cx)
+    }
+}
+
+impl<S> Clone for AnyView<S> {
+    fn clone(&self) -> Self {
+        Self {
+            view: self.view.clone(),
+            parent_state_type: PhantomData,
+        }
+    }
+}

crates/gpui3/src/window.rs 🔗

@@ -0,0 +1,403 @@
+use crate::{
+    px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
+    LayoutId, MainThread, MainThreadOnly, Pixels, PlatformWindow, Point, Reference, Scene, Size,
+    StackContext, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
+};
+use anyhow::Result;
+use futures::Future;
+use std::{any::TypeId, marker::PhantomData, mem, sync::Arc};
+use util::ResultExt;
+
+pub struct AnyWindow {}
+
+pub struct Window {
+    handle: AnyWindowHandle,
+    platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+    rem_size: Pixels,
+    content_size: Size<Pixels>,
+    layout_engine: TaffyLayoutEngine,
+    pub(crate) root_view: Option<AnyView<()>>,
+    mouse_position: Point<Pixels>,
+    pub(crate) scene: Scene,
+    pub(crate) dirty: bool,
+}
+
+impl Window {
+    pub fn new(
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+        cx: &mut MainThread<AppContext>,
+    ) -> Self {
+        let platform_window = cx.platform().open_window(handle, options);
+        let mouse_position = platform_window.mouse_position();
+        let content_size = platform_window.content_size();
+        let scale_factor = platform_window.scale_factor();
+        platform_window.on_resize(Box::new({
+            let handle = handle;
+            let cx = cx.to_async();
+            move |content_size, scale_factor| {
+                cx.update_window(handle, |cx| {
+                    cx.window.scene = Scene::new(scale_factor);
+                    cx.window.content_size = content_size;
+                    cx.window.dirty = true;
+                })
+                .log_err();
+            }
+        }));
+
+        let platform_window =
+            MainThreadOnly::new(Arc::new(platform_window), cx.platform().dispatcher());
+
+        Window {
+            handle,
+            platform_window,
+            rem_size: px(16.),
+            content_size,
+            layout_engine: TaffyLayoutEngine::new(),
+            root_view: None,
+            mouse_position,
+            scene: Scene::new(scale_factor),
+            dirty: true,
+        }
+    }
+}
+
+pub struct WindowContext<'a, 'w> {
+    app: Reference<'a, AppContext>,
+    window: Reference<'w, Window>,
+}
+
+impl<'a, 'w> WindowContext<'a, 'w> {
+    pub(crate) fn mutable(app: &'a mut AppContext, window: &'w mut Window) -> Self {
+        Self {
+            app: Reference::Mutable(app),
+            window: Reference::Mutable(window),
+        }
+    }
+
+    pub fn notify(&mut self) {
+        self.window.dirty = true;
+    }
+
+    pub fn request_layout(
+        &mut self,
+        style: Style,
+        children: impl IntoIterator<Item = LayoutId>,
+    ) -> Result<LayoutId> {
+        self.app.layout_id_buffer.clear();
+        self.app.layout_id_buffer.extend(children.into_iter());
+        let rem_size = self.rem_size();
+
+        self.window
+            .layout_engine
+            .request_layout(style, rem_size, &self.app.layout_id_buffer)
+    }
+
+    pub fn request_measured_layout<
+        F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync + 'static,
+    >(
+        &mut self,
+        style: Style,
+        rem_size: Pixels,
+        measure: F,
+    ) -> Result<LayoutId> {
+        self.window
+            .layout_engine
+            .request_measured_layout(style, rem_size, measure)
+    }
+
+    pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
+        Ok(self
+            .window
+            .layout_engine
+            .layout(layout_id)
+            .map(Into::into)?)
+    }
+
+    pub fn rem_size(&self) -> Pixels {
+        self.window.rem_size
+    }
+
+    pub fn mouse_position(&self) -> Point<Pixels> {
+        self.window.mouse_position
+    }
+
+    pub fn scene(&mut self) -> &mut Scene {
+        &mut self.window.scene
+    }
+
+    pub fn run_on_main<R>(
+        &self,
+        f: impl FnOnce(&mut MainThread<WindowContext>) -> R + Send + 'static,
+    ) -> impl Future<Output = Result<R>>
+    where
+        R: Send + 'static,
+    {
+        let id = self.window.handle.id;
+        self.app.run_on_main(move |cx| {
+            cx.update_window(id, |cx| {
+                f(unsafe {
+                    mem::transmute::<&mut WindowContext, &mut MainThread<WindowContext>>(cx)
+                })
+            })
+        })
+    }
+
+    pub(crate) fn draw(&mut self) -> Result<()> {
+        let unit_entity = self.unit_entity.clone();
+        self.update_entity(&unit_entity, |_, cx| {
+            let mut root_view = cx.window.root_view.take().unwrap();
+            let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
+            let available_space = cx.window.content_size.map(Into::into);
+
+            dbg!("computing layout");
+            cx.window
+                .layout_engine
+                .compute_layout(root_layout_id, available_space)?;
+            dbg!("asking for layout");
+            let layout = cx.window.layout_engine.layout(root_layout_id)?;
+
+            dbg!("painting root view");
+
+            root_view.paint(layout, &mut (), &mut frame_state, cx)?;
+            cx.window.root_view = Some(root_view);
+            let scene = cx.window.scene.take();
+
+            let _ = cx.run_on_main(|cx| {
+                cx.window
+                    .platform_window
+                    .borrow_on_main_thread()
+                    .draw(scene);
+            });
+
+            cx.window.dirty = false;
+            Ok(())
+        })
+    }
+}
+
+impl MainThread<WindowContext<'_, '_>> {
+    // todo!("implement other methods that use platform window")
+    fn platform_window(&self) -> &dyn PlatformWindow {
+        self.window.platform_window.borrow_on_main_thread().as_ref()
+    }
+}
+
+impl Context for WindowContext<'_, '_> {
+    type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
+    type Result<T> = T;
+
+    fn entity<T: Send + Sync + 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
+    ) -> Handle<T> {
+        let slot = self.app.entities.reserve();
+        let entity = build_entity(&mut ViewContext::mutable(
+            &mut *self.app,
+            &mut self.window,
+            slot.id,
+        ));
+        self.entities.redeem(slot, entity)
+    }
+
+    fn update_entity<T: Send + Sync + 'static, R>(
+        &mut self,
+        handle: &Handle<T>,
+        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
+    ) -> R {
+        let mut entity = self.entities.lease(handle);
+        let result = update(
+            &mut *entity,
+            &mut ViewContext::mutable(&mut *self.app, &mut *self.window, handle.id),
+        );
+        self.entities.end_lease(entity);
+        result
+    }
+}
+
+impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> {
+    type Target = AppContext;
+
+    fn deref(&self) -> &Self::Target {
+        &self.app
+    }
+}
+
+impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.app
+    }
+}
+
+impl<S> StackContext for ViewContext<'_, '_, S> {
+    fn app(&mut self) -> &mut AppContext {
+        &mut *self.window_cx.app
+    }
+
+    fn with_text_style<F, R>(&mut self, style: crate::TextStyleRefinement, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.window_cx.app.push_text_style(style);
+        let result = f(self);
+        self.window_cx.app.pop_text_style();
+        result
+    }
+
+    fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.window_cx.app.push_state(state);
+        let result = f(self);
+        self.window_cx.app.pop_state::<T>();
+        result
+    }
+}
+
+pub struct ViewContext<'a, 'w, S> {
+    window_cx: WindowContext<'a, 'w>,
+    entity_type: PhantomData<S>,
+    entity_id: EntityId,
+}
+
+impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
+    fn mutable(app: &'a mut AppContext, window: &'w mut Window, entity_id: EntityId) -> Self {
+        Self {
+            window_cx: WindowContext::mutable(app, window),
+            entity_id,
+            entity_type: PhantomData,
+        }
+    }
+
+    pub fn handle(&self) -> WeakHandle<S> {
+        self.entities.weak_handle(self.entity_id)
+    }
+
+    pub fn observe<E: Send + Sync + 'static>(
+        &mut self,
+        handle: &Handle<E>,
+        on_notify: impl Fn(&mut S, Handle<E>, &mut ViewContext<'_, '_, S>) + Send + Sync + 'static,
+    ) {
+        let this = self.handle();
+        let handle = handle.downgrade();
+        let window_handle = self.window.handle;
+        self.app
+            .observers
+            .entry(handle.id)
+            .or_default()
+            .push(Arc::new(move |cx| {
+                cx.update_window(window_handle.id, |cx| {
+                    if let Some(handle) = handle.upgrade(cx) {
+                        this.update(cx, |this, cx| on_notify(this, handle, cx))
+                            .is_ok()
+                    } else {
+                        false
+                    }
+                })
+                .unwrap_or(false)
+            }));
+    }
+
+    pub fn notify(&mut self) {
+        self.window_cx.notify();
+        self.window_cx
+            .app
+            .pending_effects
+            .push_back(Effect::Notify(self.entity_id));
+    }
+
+    pub(crate) fn erase_state<R>(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R {
+        let entity_id = self.unit_entity.id;
+        let mut cx = ViewContext::mutable(
+            &mut *self.window_cx.app,
+            &mut *self.window_cx.window,
+            entity_id,
+        );
+        f(&mut cx)
+    }
+}
+
+impl<'a, 'w, S> Context for ViewContext<'a, 'w, S> {
+    type EntityContext<'b, 'c, U: 'static + Send + Sync> = ViewContext<'b, 'c, U>;
+    type Result<U> = U;
+
+    fn entity<T2: Send + Sync + 'static>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T2>) -> T2,
+    ) -> Handle<T2> {
+        self.window_cx.entity(build_entity)
+    }
+
+    fn update_entity<U: Send + Sync + 'static, R>(
+        &mut self,
+        handle: &Handle<U>,
+        update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
+    ) -> R {
+        self.window_cx.update_entity(handle, update)
+    }
+}
+
+impl<'a, 'w, S: 'static> std::ops::Deref for ViewContext<'a, 'w, S> {
+    type Target = WindowContext<'a, 'w>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.window_cx
+    }
+}
+
+impl<'a, 'w, S: 'static> std::ops::DerefMut for ViewContext<'a, 'w, S> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.window_cx
+    }
+}
+
+// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
+slotmap::new_key_type! { pub struct WindowId; }
+
+#[derive(PartialEq, Eq)]
+pub struct WindowHandle<S> {
+    id: WindowId,
+    state_type: PhantomData<S>,
+}
+
+impl<S> Copy for WindowHandle<S> {}
+
+impl<S> Clone for WindowHandle<S> {
+    fn clone(&self) -> Self {
+        WindowHandle {
+            id: self.id,
+            state_type: PhantomData,
+        }
+    }
+}
+
+impl<S> WindowHandle<S> {
+    pub fn new(id: WindowId) -> Self {
+        WindowHandle {
+            id,
+            state_type: PhantomData,
+        }
+    }
+}
+
+impl<S: 'static> Into<AnyWindowHandle> for WindowHandle<S> {
+    fn into(self) -> AnyWindowHandle {
+        AnyWindowHandle {
+            id: self.id,
+            state_type: TypeId::of::<S>(),
+        }
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub struct AnyWindowHandle {
+    pub(crate) id: WindowId,
+    state_type: TypeId,
+}
+
+#[derive(Clone)]
+pub struct Layout {
+    pub order: u32,
+    pub bounds: Bounds<Pixels>,
+}

crates/gpui3_macros/Cargo.toml 🔗

@@ -0,0 +1,14 @@
+[package]
+name = "gpui3_macros"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/gpui3_macros.rs"
+proc-macro = true
+
+[dependencies]
+syn = "1.0.72"
+quote = "1.0.9"
+proc-macro2 = "1.0.66"

crates/gpui3_macros/src/derive_element.rs 🔗

@@ -0,0 +1,50 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, DeriveInput, GenericParam};
+
+pub fn derive_element(input: TokenStream) -> TokenStream {
+    let ast = parse_macro_input!(input as DeriveInput);
+    let type_name = ast.ident;
+
+    let mut state_type = quote! { () };
+
+    for param in &ast.generics.params {
+        if let GenericParam::Type(type_param) = param {
+            let type_ident = &type_param.ident;
+            state_type = quote! {#type_ident};
+        }
+    }
+
+    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
+
+    let gen = quote! {
+        impl #impl_generics gpui3::Element for #type_name #ty_generics
+        #where_clause
+        {
+            type State = #state_type;
+            type FrameState = gpui3::AnyElement<#state_type>;
+
+            fn layout(
+                &mut self,
+                state: &mut #state_type,
+                cx: &mut gpui3::ViewContext<V>,
+            ) -> anyhow::Result<(gpui3::LayoutId, Self::FrameState)> {
+                let mut rendered_element = self.render(state, cx).into_element().into_any();
+                let layout_id = rendered_element.layout(state, cx)?;
+                Ok((layout_id, rendered_element))
+            }
+
+            fn paint(
+                &mut self,
+                layout: &gpui3::Layout,
+                state: &mut #state_type,
+                rendered_element: &mut Self::FrameState,
+                cx: &mut gpui3::ViewContext<V>,
+            ) {
+                rendered_element.paint(layout.origin, state, cx);
+            }
+        }
+    };
+
+    gen.into()
+}

crates/gpui3_macros/src/gpui3_macros.rs 🔗

@@ -0,0 +1,14 @@
+use proc_macro::TokenStream;
+
+mod derive_element;
+mod style_helpers;
+
+#[proc_macro]
+pub fn style_helpers(args: TokenStream) -> TokenStream {
+    style_helpers::style_helpers(args)
+}
+
+#[proc_macro_derive(Element, attributes(element_crate))]
+pub fn derive_element(input: TokenStream) -> TokenStream {
+    derive_element::derive_element(input)
+}

crates/gpui3_macros/src/style_helpers.rs 🔗

@@ -0,0 +1,326 @@
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use syn::{
+    parse::{Parse, ParseStream, Result},
+    parse_macro_input,
+};
+
+struct StyleableMacroInput;
+
+impl Parse for StyleableMacroInput {
+    fn parse(_input: ParseStream) -> Result<Self> {
+        Ok(StyleableMacroInput)
+    }
+}
+
+pub fn style_helpers(input: TokenStream) -> TokenStream {
+    let _ = parse_macro_input!(input as StyleableMacroInput);
+    let methods = generate_methods();
+    let output = quote! {
+        #(#methods)*
+    };
+    output.into()
+}
+
+fn generate_methods() -> Vec<TokenStream2> {
+    let mut methods = Vec::new();
+
+    for (prefix, auto_allowed, fields) in box_prefixes() {
+        for (suffix, length_tokens) in box_suffixes() {
+            if auto_allowed || suffix != "auto" {
+                let method = generate_method(prefix, suffix, &fields, length_tokens);
+                methods.push(method);
+            }
+        }
+    }
+
+    for (prefix, fields) in corner_prefixes() {
+        for (suffix, radius_tokens) in corner_suffixes() {
+            let method = generate_method(prefix, suffix, &fields, radius_tokens);
+            methods.push(method);
+        }
+    }
+
+    for (prefix, fields) in border_prefixes() {
+        for (suffix, width_tokens) in border_suffixes() {
+            let method = generate_method(prefix, suffix, &fields, width_tokens);
+            methods.push(method);
+        }
+    }
+
+    methods
+}
+
+fn generate_method(
+    prefix: &'static str,
+    suffix: &'static str,
+    fields: &Vec<TokenStream2>,
+    length_tokens: TokenStream2,
+) -> TokenStream2 {
+    let method_name = if suffix.is_empty() {
+        format_ident!("{}", prefix)
+    } else {
+        format_ident!("{}_{}", prefix, suffix)
+    };
+
+    let field_assignments = fields
+        .iter()
+        .map(|field_tokens| {
+            quote! {
+                style.#field_tokens = Some(gpui3::#length_tokens.into());
+            }
+        })
+        .collect::<Vec<_>>();
+
+    let method = quote! {
+        fn #method_name(mut self) -> Self where Self: std::marker::Sized {
+            let style = self.declared_style();
+            #(#field_assignments)*
+            self
+        }
+    };
+
+    method
+}
+
+fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
+    vec![
+        ("w", true, vec![quote! { size.width }]),
+        ("h", true, vec![quote! { size.height }]),
+        (
+            "size",
+            true,
+            vec![quote! {size.width}, quote! {size.height}],
+        ),
+        ("min_w", false, vec![quote! { min_size.width }]),
+        ("min_h", false, vec![quote! { min_size.height }]),
+        ("max_w", false, vec![quote! { max_size.width }]),
+        ("max_h", false, vec![quote! { max_size.height }]),
+        (
+            "m",
+            true,
+            vec![
+                quote! { margin.top },
+                quote! { margin.bottom },
+                quote! { margin.left },
+                quote! { margin.right },
+            ],
+        ),
+        ("mt", true, vec![quote! { margin.top }]),
+        ("mb", true, vec![quote! { margin.bottom }]),
+        (
+            "my",
+            true,
+            vec![quote! { margin.top }, quote! { margin.bottom }],
+        ),
+        (
+            "mx",
+            true,
+            vec![quote! { margin.left }, quote! { margin.right }],
+        ),
+        ("ml", true, vec![quote! { margin.left }]),
+        ("mr", true, vec![quote! { margin.right }]),
+        (
+            "p",
+            false,
+            vec![
+                quote! { padding.top },
+                quote! { padding.bottom },
+                quote! { padding.left },
+                quote! { padding.right },
+            ],
+        ),
+        ("pt", false, vec![quote! { padding.top }]),
+        ("pb", false, vec![quote! { padding.bottom }]),
+        (
+            "px",
+            false,
+            vec![quote! { padding.left }, quote! { padding.right }],
+        ),
+        (
+            "py",
+            false,
+            vec![quote! { padding.top }, quote! { padding.bottom }],
+        ),
+        ("pl", false, vec![quote! { padding.left }]),
+        ("pr", false, vec![quote! { padding.right }]),
+        ("top", true, vec![quote! { inset.top }]),
+        ("bottom", true, vec![quote! { inset.bottom }]),
+        ("left", true, vec![quote! { inset.left }]),
+        ("right", true, vec![quote! { inset.right }]),
+        (
+            "gap",
+            false,
+            vec![quote! { gap.width }, quote! { gap.height }],
+        ),
+        ("gap_x", false, vec![quote! { gap.width }]),
+        ("gap_y", false, vec![quote! { gap.height }]),
+    ]
+}
+
+fn box_suffixes() -> Vec<(&'static str, TokenStream2)> {
+    vec![
+        ("0", quote! { px(0.) }),
+        ("0p5", quote! { rems(0.125) }),
+        ("1", quote! { rems(0.25) }),
+        ("1p5", quote! { rems(0.375) }),
+        ("2", quote! { rems(0.5) }),
+        ("2p5", quote! { rems(0.625) }),
+        ("3", quote! { rems(0.75) }),
+        ("3p5", quote! { rems(0.875) }),
+        ("4", quote! { rems(1.) }),
+        ("5", quote! { rems(1.25) }),
+        ("6", quote! { rems(1.5) }),
+        ("7", quote! { rems(1.75) }),
+        ("8", quote! { rems(2.0) }),
+        ("9", quote! { rems(2.25) }),
+        ("10", quote! { rems(2.5) }),
+        ("11", quote! { rems(2.75) }),
+        ("12", quote! { rems(3.) }),
+        ("16", quote! { rems(4.) }),
+        ("20", quote! { rems(5.) }),
+        ("24", quote! { rems(6.) }),
+        ("32", quote! { rems(8.) }),
+        ("40", quote! { rems(10.) }),
+        ("48", quote! { rems(12.) }),
+        ("56", quote! { rems(14.) }),
+        ("64", quote! { rems(16.) }),
+        ("72", quote! { rems(18.) }),
+        ("80", quote! { rems(20.) }),
+        ("96", quote! { rems(24.) }),
+        ("auto", quote! { auto() }),
+        ("px", quote! { px(1.) }),
+        ("full", quote! { relative(1.) }),
+        ("1_2", quote! { relative(0.5) }),
+        ("1_3", quote! { relative(1./3.) }),
+        ("2_3", quote! { relative(2./3.) }),
+        ("1_4", quote! { relative(0.25) }),
+        ("2_4", quote! { relative(0.5) }),
+        ("3_4", quote! { relative(0.75) }),
+        ("1_5", quote! { relative(0.2) }),
+        ("2_5", quote! { relative(0.4) }),
+        ("3_5", quote! { relative(0.6) }),
+        ("4_5", quote! { relative(0.8) }),
+        ("1_6", quote! { relative(1./6.) }),
+        ("5_6", quote! { relative(5./6.) }),
+        ("1_12", quote! { relative(1./12.) }),
+        // ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
+        // ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
+        // ("screen", quote! { DefiniteLength::Vh(100.0) }),
+    ]
+}
+
+fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
+    vec![
+        (
+            "rounded",
+            vec![
+                quote! { corner_radii.top_left },
+                quote! { corner_radii.top_right },
+                quote! { corner_radii.bottom_right },
+                quote! { corner_radii.bottom_left },
+            ],
+        ),
+        (
+            "rounded_t",
+            vec![
+                quote! { corner_radii.top_left },
+                quote! { corner_radii.top_right },
+            ],
+        ),
+        (
+            "rounded_b",
+            vec![
+                quote! { corner_radii.bottom_left },
+                quote! { corner_radii.bottom_right },
+            ],
+        ),
+        (
+            "rounded_r",
+            vec![
+                quote! { corner_radii.top_right },
+                quote! { corner_radii.bottom_right },
+            ],
+        ),
+        (
+            "rounded_l",
+            vec![
+                quote! { corner_radii.top_left },
+                quote! { corner_radii.bottom_left },
+            ],
+        ),
+        ("rounded_tl", vec![quote! { corner_radii.top_left }]),
+        ("rounded_tr", vec![quote! { corner_radii.top_right }]),
+        ("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
+        ("rounded_br", vec![quote! { corner_radii.bottom_right }]),
+    ]
+}
+
+fn corner_suffixes() -> Vec<(&'static str, TokenStream2)> {
+    vec![
+        ("none", quote! { px(0.) }),
+        ("sm", quote! { rems(0.125) }),
+        ("md", quote! { rems(0.25) }),
+        ("lg", quote! { rems(0.5) }),
+        ("xl", quote! { rems(0.75) }),
+        ("2xl", quote! { rems(1.) }),
+        ("3xl", quote! { rems(1.5) }),
+        ("full", quote! {  px(9999.) }),
+    ]
+}
+
+fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
+    vec![
+        (
+            "border",
+            vec![
+                quote! { border_widths.top },
+                quote! { border_widths.right },
+                quote! { border_widths.bottom },
+                quote! { border_widths.left },
+            ],
+        ),
+        ("border_t", vec![quote! { border_widths.top }]),
+        ("border_b", vec![quote! { border_widths.bottom }]),
+        ("border_r", vec![quote! { border_widths.right }]),
+        ("border_l", vec![quote! { border_widths.left }]),
+        (
+            "border_x",
+            vec![
+                quote! { border_widths.left },
+                quote! { border_widths.right },
+            ],
+        ),
+        (
+            "border_y",
+            vec![
+                quote! { border_widths.top },
+                quote! { border_widths.bottom },
+            ],
+        ),
+    ]
+}
+
+fn border_suffixes() -> Vec<(&'static str, TokenStream2)> {
+    vec![
+        ("", quote! { px(1.) }),
+        ("0", quote! { px(0.) }),
+        ("1", quote! { px(1.) }),
+        ("2", quote! { px(2.) }),
+        ("3", quote! { px(3.) }),
+        ("4", quote! { px(4.) }),
+        ("5", quote! { px(5.) }),
+        ("6", quote! { px(6.) }),
+        ("7", quote! { px(7.) }),
+        ("8", quote! { px(8.) }),
+        ("9", quote! { px(9.) }),
+        ("10", quote! { px(10.) }),
+        ("11", quote! { px(11.) }),
+        ("12", quote! { px(12.) }),
+        ("16", quote! { px(16.) }),
+        ("20", quote! { px(20.) }),
+        ("24", quote! { px(24.) }),
+        ("32", quote! { px(32.) }),
+    ]
+}

crates/refineable/derive_refineable/src/derive_refineable.rs 🔗

@@ -35,7 +35,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
     let field_visibilities: Vec<_> = fields.iter().map(|f| &f.vis).collect();
     let wrapped_types: Vec<_> = fields.iter().map(|f| get_wrapper_type(f, &f.ty)).collect();
 
-    // Create trait bound that each wrapped type must implement Clone & Default
+    // Create trait bound that each wrapped type must implement Clone // & Default
     let type_param_bounds: Vec<_> = wrapped_types
         .iter()
         .map(|ty| {
@@ -51,13 +51,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
                         lifetimes: None,
                         path: parse_quote!(Clone),
                     }));
-                    punctuated.push_punct(syn::token::Add::default());
-                    punctuated.push_value(TypeParamBound::Trait(TraitBound {
-                        paren_token: None,
-                        modifier: syn::TraitBoundModifier::None,
-                        lifetimes: None,
-                        path: parse_quote!(Default),
-                    }));
+
+                    // punctuated.push_punct(syn::token::Add::default());
+                    // punctuated.push_value(TypeParamBound::Trait(TraitBound {
+                    //     paren_token: None,
+                    //     modifier: syn::TraitBoundModifier::None,
+                    //     lifetimes: None,
+                    //     path: parse_quote!(Default),
+                    // }));
                     punctuated
                 },
             })
@@ -161,7 +162,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
     };
 
     let gen = quote! {
-        #[derive(Default, Clone)]
+        #[derive(Clone)]
         pub struct #refinement_ident #impl_generics {
             #( #field_visibilities #field_names: #wrapped_types ),*
         }
@@ -186,6 +187,16 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
             }
         }
 
+        impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics
+            #where_clause
+        {
+            fn default() -> Self {
+                #refinement_ident {
+                    #( #field_names: Default::default() ),*
+                }
+            }
+        }
+
         impl #impl_generics #refinement_ident #ty_generics
             #where_clause
         {

crates/refineable/src/refineable.rs 🔗

@@ -17,6 +17,12 @@ pub trait Refineable: Clone {
     {
         Self::default().refined(refinement)
     }
+    fn from_cascade(cascade: &RefinementCascade<Self>) -> Self
+    where
+        Self: Default + Sized,
+    {
+        Self::default().refined(&cascade.merged())
+    }
 }
 
 pub struct RefinementCascade<S: Refineable>(Vec<Option<S::Refinement>>);

crates/storybook/Cargo.toml 🔗

@@ -8,15 +8,20 @@ publish = false
 name = "storybook"
 path = "src/storybook.rs"
 
+[features]
+test-support = []
+
 [dependencies]
 anyhow.workspace = true
 clap = { version = "4.4", features = ["derive", "string"] }
 chrono = "0.4"
+derive_more.workspace = true
 fs = { path = "../fs" }
 futures.workspace = true
 gpui2 = { path = "../gpui2" }
 itertools = "0.11.0"
 log.workspace = true
+refineable = { path = "../refineable" }
 rust-embed.workspace = true
 serde.workspace = true
 settings = { path = "../settings" }

crates/storybook/build.rs 🔗

@@ -0,0 +1,5 @@
+fn main() {
+    // Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
+    // TODO: We shouldn't depend on WebRTC in editor
+    println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
+}

crates/storybook2/Cargo.lock 🔗

@@ -0,0 +1,2919 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "adler32"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+
+[[package]]
+name = "arrayref"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
+
+[[package]]
+name = "async-channel"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "futures-core",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
+dependencies = [
+ "async-lock",
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite",
+ "log",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "socket2",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
+dependencies = [
+ "event-listener",
+]
+
+[[package]]
+name = "async-net"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4051e67316bc7eff608fe723df5d32ed639946adcd69e07df41fd42a7b411f1f"
+dependencies = [
+ "async-io",
+ "autocfg",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "autocfg",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix",
+ "signal-hook",
+ "windows-sys",
+]
+
+[[package]]
+name = "async-task"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
+
+[[package]]
+name = "atomic"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide 0.7.1",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "bindgen"
+version = "0.65.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn 2.0.25",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
+dependencies = [
+ "async-channel",
+ "async-lock",
+ "async-task",
+ "atomic-waker",
+ "fastrand",
+ "futures-lite",
+ "log",
+]
+
+[[package]]
+name = "bstr"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+
+[[package]]
+name = "castaway"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading 0.7.4",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6"
+dependencies = [
+ "bitflags",
+ "block",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "collections"
+version = "0.1.0"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "const-cstr"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+ "uuid 0.5.1",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
+[[package]]
+name = "core-graphics"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "core-text"
+version = "19.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25"
+dependencies = [
+ "core-foundation",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "ctor"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "curl"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22"
+dependencies = [
+ "curl-sys",
+ "libc",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "socket2",
+ "winapi",
+]
+
+[[package]]
+name = "curl-sys"
+version = "0.4.63+curl-8.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aeb0fef7046022a1e2ad67a004978f0e3cacb9e3123dc62ce768f92197b771dc"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+ "winapi",
+]
+
+[[package]]
+name = "data-url"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a30bfce702bcfa94e906ef82421f2c0e61c076ad76030c16ee5d2e9a32fe193"
+dependencies = [
+ "matches",
+]
+
+[[package]]
+name = "deflate"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
+dependencies = [
+ "adler32",
+ "byteorder",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading 0.8.0",
+]
+
+[[package]]
+name = "dwrote"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "winapi",
+ "wio",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "erased-serde"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f94c0e13118e7d7533271f754a168ae8400e6a1cc043f2bfd53cc7290f1a1de3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "etagere"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcf22f748754352918e082e0039335ee92454a5d62bcaf69b5e8daf5907d9644"
+dependencies = [
+ "euclid",
+ "svg_fmt",
+]
+
+[[package]]
+name = "euclid"
+version = "0.22.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide 0.7.1",
+]
+
+[[package]]
+name = "float-cmp"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e"
+
+[[package]]
+name = "float-ord"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "font-kit"
+version = "0.11.0"
+source = "git+https://github.com/zed-industries/font-kit?rev=b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18#b2f77d56f450338aa4f7dd2f0197d8c9acb0cf18"
+dependencies = [
+ "bitflags",
+ "byteorder",
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "dirs-next",
+ "dwrote",
+ "float-ord",
+ "freetype",
+ "lazy_static",
+ "libc",
+ "log",
+ "pathfinder_geometry",
+ "pathfinder_simd",
+ "walkdir",
+ "winapi",
+ "yeslogic-fontconfig-sys",
+]
+
+[[package]]
+name = "fontdb"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e58903f4f8d5b58c7d300908e4ebe5289c1bfdf5587964330f12023b8ff17fd1"
+dependencies = [
+ "log",
+ "memmap2",
+ "ttf-parser 0.12.3",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "freetype"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6"
+dependencies = [
+ "freetype-sys",
+ "libc",
+]
+
+[[package]]
+name = "freetype-sys"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a"
+dependencies = [
+ "cmake",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
+
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.25",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
+
+[[package]]
+name = "futures-util"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gif"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "gimli"
+version = "0.27.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "globset"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "fnv",
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "gpui"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-task",
+ "bindgen",
+ "block",
+ "cc",
+ "cocoa",
+ "collections",
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "ctor",
+ "etagere",
+ "font-kit",
+ "foreign-types",
+ "futures",
+ "gpui_macros",
+ "image",
+ "itertools",
+ "lazy_static",
+ "log",
+ "media",
+ "metal",
+ "num_cpus",
+ "objc",
+ "ordered-float",
+ "parking",
+ "parking_lot 0.11.2",
+ "pathfinder_color",
+ "pathfinder_geometry",
+ "postage",
+ "rand",
+ "resvg",
+ "schemars",
+ "seahash",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "smallvec",
+ "smol",
+ "sqlez",
+ "sum_tree",
+ "time",
+ "tiny-skia",
+ "usvg",
+ "util",
+ "uuid 1.4.0",
+ "waker-fn",
+]
+
+[[package]]
+name = "gpui_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+
+[[package]]
+name = "http"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "image"
+version = "0.23.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "gif",
+ "jpeg-decoder",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+ "png",
+ "scoped_threadpool",
+ "tiff",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "indoc"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "isahc"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
+dependencies = [
+ "async-channel",
+ "castaway",
+ "crossbeam-utils",
+ "curl",
+ "curl-sys",
+ "encoding_rs",
+ "event-listener",
+ "futures-lite",
+ "http",
+ "log",
+ "mime",
+ "once_cell",
+ "polling",
+ "slab",
+ "sluice",
+ "tracing",
+ "tracing-futures",
+ "url",
+ "waker-fn",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "kurbo"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449"
+dependencies = [
+ "arrayvec 0.7.4",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "libloading"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
+dependencies = [
+ "cfg-if",
+ "windows-sys",
+]
+
+[[package]]
+name = "libsqlite3-sys"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "lock_api"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
+dependencies = [
+ "serde",
+ "value-bag",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+
+[[package]]
+name = "media"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bindgen",
+ "block",
+ "bytes",
+ "core-foundation",
+ "foreign-types",
+ "metal",
+ "objc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "memmap2"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "metal"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c"
+dependencies = [
+ "bitflags",
+ "block",
+ "cocoa-foundation",
+ "foreign-types",
+ "log",
+ "objc",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
+dependencies = [
+ "adler32",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+ "objc_exception",
+]
+
+[[package]]
+name = "objc_exception"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "object"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.90"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "ordered-float"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "parking"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core 0.9.8",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.3.5",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "pathfinder_color"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69bdc0d277d559e35e1b374de56df9262a6b71e091ca04a8831a239f8c7f0c62"
+dependencies = [
+ "pathfinder_simd",
+]
+
+[[package]]
+name = "pathfinder_geometry"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
+dependencies = [
+ "log",
+ "pathfinder_simd",
+]
+
+[[package]]
+name = "pathfinder_simd"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
+dependencies = [
+ "rustc_version",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pest"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pico-args"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
+
+[[package]]
+name = "pin-project"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.25",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "storybook"
+version = "0.1.0"
+dependencies = [
+ "gpui",
+]
+
+[[package]]
+name = "png"
+version = "0.16.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "deflate",
+ "miniz_oxide 0.3.7",
+]
+
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys",
+]
+
+[[package]]
+name = "pollster"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
+
+[[package]]
+name = "postage"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1"
+dependencies = [
+ "atomic",
+ "crossbeam-queue",
+ "futures",
+ "log",
+ "parking_lot 0.12.1",
+ "pin-project",
+ "pollster",
+ "static_assertions",
+ "thiserror",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.25",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rayon"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
+[[package]]
+name = "rctree"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be9e29cb19c8fe84169fcb07f8f11e66bc9e6e0280efd4715c54818296f8a4a8"
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall 0.2.16",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
+
+[[package]]
+name = "resvg"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09697862c5c3f940cbaffef91969c62188b5c8ed385b0aef43a5ff01ddc8000f"
+dependencies = [
+ "jpeg-decoder",
+ "log",
+ "pico-args",
+ "png",
+ "rgb",
+ "svgfilters",
+ "tiny-skia",
+ "usvg",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "roxmltree"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
+dependencies = [
+ "xmlparser",
+]
+
+[[package]]
+name = "rust-embed"
+version = "6.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "6.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.25",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "7.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74"
+dependencies = [
+ "globset",
+ "sha2",
+ "walkdir",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustybuzz"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab463a295d00f3692e0974a0bfd83c7a9bcd119e27e07c2beecdb1b44a09d10"
+dependencies = [
+ "bitflags",
+ "bytemuck",
+ "smallvec",
+ "ttf-parser 0.9.0",
+ "unicode-bidi-mirroring",
+ "unicode-ccc",
+ "unicode-general-category",
+ "unicode-script",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
+
+[[package]]
+name = "safe_arch"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "schemars"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
+dependencies = [
+ "dyn-clone",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "scoped_threadpool"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.171"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.171"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.25",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "serde_fmt"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5062a995d481b2308b6064e9af76011f2921c35f97b0468811ed9f6cd91dfed"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "simplecss"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
+
+[[package]]
+name = "slab"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "sluice"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
+dependencies = [
+ "async-channel",
+ "futures-core",
+ "futures-io",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
+
+[[package]]
+name = "smol"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-net",
+ "async-process",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "socket2"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "sqlez"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "futures",
+ "indoc",
+ "lazy_static",
+ "libsqlite3-sys",
+ "parking_lot 0.11.2",
+ "smol",
+ "thread_local",
+ "uuid 1.4.0",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "sum_tree"
+version = "0.1.0"
+dependencies = [
+ "arrayvec 0.7.4",
+ "log",
+]
+
+[[package]]
+name = "sval"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b031320a434d3e9477ccf9b5756d57d4272937b8d22cb88af80b7633a1b78b1"
+
+[[package]]
+name = "sval_buffer"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bf7e9412af26b342f3f2cc5cc4122b0105e9d16eb76046cd14ed10106cf6028"
+dependencies = [
+ "sval",
+ "sval_ref",
+]
+
+[[package]]
+name = "sval_dynamic"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0ef628e8a77a46ed3338db8d1b08af77495123cc229453084e47cd716d403cf"
+dependencies = [
+ "sval",
+]
+
+[[package]]
+name = "sval_fmt"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dc09e9364c2045ab5fa38f7b04d077b3359d30c4c2b3ec4bae67a358bd64326"
+dependencies = [
+ "itoa",
+ "ryu",
+ "sval",
+]
+
+[[package]]
+name = "sval_json"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ada6f627e38cbb8860283649509d87bc4a5771141daa41c78fd31f2b9485888d"
+dependencies = [
+ "itoa",
+ "ryu",
+ "sval",
+]
+
+[[package]]
+name = "sval_ref"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703ca1942a984bd0d9b5a4c0a65ab8b4b794038d080af4eb303c71bc6bf22d7c"
+dependencies = [
+ "sval",
+]
+
+[[package]]
+name = "sval_serde"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830926cd0581f7c3e5d51efae4d35c6b6fc4db583842652891ba2f1bed8db046"
+dependencies = [
+ "serde",
+ "sval",
+ "sval_buffer",
+ "sval_fmt",
+]
+
+[[package]]
+name = "svg_fmt"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
+
+[[package]]
+name = "svgfilters"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb0dce2fee79ac40c21dafba48565ff7a5fa275e23ffe9ce047a40c9574ba34e"
+dependencies = [
+ "float-cmp",
+ "rgb",
+]
+
+[[package]]
+name = "svgtypes"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c536faaff1a10837cfe373142583f6e27d81e96beba339147e77b67c9f260ff"
+dependencies = [
+ "float-cmp",
+ "siphasher",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "take-until"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb"
+
+[[package]]
+name = "thiserror"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.25",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "tiff"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
+dependencies = [
+ "jpeg-decoder",
+ "miniz_oxide 0.4.4",
+ "weezl",
+]
+
+[[package]]
+name = "time"
+version = "0.3.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
+dependencies = [
+ "itoa",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
+
+[[package]]
+name = "time-macros"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "tiny-skia"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf81f2900d2e235220e6f31ec9f63ade6a7f59090c556d74fe949bb3b15e9fe"
+dependencies = [
+ "arrayref",
+ "arrayvec 0.5.2",
+ "bytemuck",
+ "cfg-if",
+ "png",
+ "safe_arch",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.25",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tracing-futures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
+dependencies = [
+ "pin-project",
+ "tracing",
+]
+
+[[package]]
+name = "ttf-parser"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ddb402ac6c2af6f7a2844243887631c4e94b51585b229fcfddb43958cd55ca"
+
+[[package]]
+name = "ttf-parser"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6"
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
+[[package]]
+name = "unicode-bidi-mirroring"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694"
+
+[[package]]
+name = "unicode-ccc"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1"
+
+[[package]]
+name = "unicode-general-category"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f9af028e052a610d99e066b33304625dea9613170a2563314490a4e6ec5cf7f"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-script"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc"
+
+[[package]]
+name = "unicode-vo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
+
+[[package]]
+name = "url"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "usvg"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef8352f317d8f9a918ba5154797fb2a93e2730244041cf7d5be35148266adfa5"
+dependencies = [
+ "base64",
+ "data-url",
+ "flate2",
+ "fontdb",
+ "kurbo",
+ "log",
+ "memmap2",
+ "pico-args",
+ "rctree",
+ "roxmltree",
+ "rustybuzz",
+ "simplecss",
+ "siphasher",
+ "svgtypes",
+ "ttf-parser 0.12.3",
+ "unicode-bidi",
+ "unicode-script",
+ "unicode-vo",
+ "xmlwriter",
+]
+
+[[package]]
+name = "util"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "backtrace",
+ "dirs",
+ "futures",
+ "isahc",
+ "lazy_static",
+ "log",
+ "rand",
+ "rust-embed",
+ "serde",
+ "serde_json",
+ "smol",
+ "take-until",
+ "url",
+]
+
+[[package]]
+name = "uuid"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22"
+
+[[package]]
+name = "uuid"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "value-bag"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3"
+dependencies = [
+ "value-bag-serde1",
+ "value-bag-sval2",
+]
+
+[[package]]
+name = "value-bag-serde1"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0b9f3feef403a50d4d67e9741a6d8fc688bcbb4e4f31bd4aab72cc690284394"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_fmt",
+]
+
+[[package]]
+name = "value-bag-sval2"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b24f4146b6f3361e91cbf527d1fb35e9376c3c0cef72ca5ec5af6d640fad7d"
+dependencies = [
+ "sval",
+ "sval_buffer",
+ "sval_dynamic",
+ "sval_fmt",
+ "sval_json",
+ "sval_ref",
+ "sval_serde",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "waker-fn"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+
+[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "weezl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
+
+[[package]]
+name = "which"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
+dependencies = [
+ "either",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "wio"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "xmlparser"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
+
+[[package]]
+name = "xmlwriter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+
+[[package]]
+name = "yeslogic-fontconfig-sys"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386"
+dependencies = [
+ "const-cstr",
+ "dlib",
+ "once_cell",
+ "pkg-config",
+]

crates/storybook2/Cargo.toml 🔗

@@ -0,0 +1,25 @@
+[package]
+name = "storybook2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[[bin]]
+name = "storybook"
+path = "src/storybook2.rs"
+
+[dependencies]
+anyhow.workspace = true
+# TODO: Remove after diagnosing stack overflow.
+backtrace-on-stack-overflow = "0.3.0"
+gpui3 = { path = "../gpui3" }
+log.workspace = true
+rust-embed.workspace = true
+serde.workspace = true
+settings = { path = "../settings" }
+simplelog = "0.9"
+theme = { path = "../theme" }
+util = { path = "../util" }
+
+[dev-dependencies]
+gpui3 = { path = "../gpui3", features = ["test"] }

crates/storybook2/build.rs 🔗

@@ -0,0 +1,5 @@
+fn main() {
+    // Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
+    // TODO: We shouldn't depend on WebRTC in editor
+    println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
+}

crates/storybook2/docs/thoughts.md 🔗

@@ -0,0 +1,72 @@
+Much of element styling is now handled by an external engine.
+
+
+How do I make an element hover.
+
+There's a hover style.
+
+Hoverable needs to wrap another element. That element can be styled.
+
+```rs
+struct Hoverable<E: Element> {
+
+}
+
+impl<V> Element<V> for Hoverable {
+
+}
+
+```
+
+
+
+```rs
+#[derive(Styled, Interactive)]
+pub struct Div {
+    declared_style: StyleRefinement,
+    interactions: Interactions
+}
+
+pub trait Styled {
+    fn declared_style(&mut self) -> &mut StyleRefinement;
+    fn compute_style(&mut self) -> Style {
+        Style::default().refine(self.declared_style())
+    }
+
+    // All the tailwind classes, modifying self.declared_style()
+}
+
+impl Style {
+    pub fn paint_background<V>(layout: Layout, cx: &mut PaintContext<V>);
+    pub fn paint_foreground<V>(layout: Layout, cx: &mut PaintContext<V>);
+}
+
+pub trait Interactive<V> {
+    fn interactions(&mut self) -> &mut Interactions<V>;
+
+    fn on_click(self, )
+}
+
+struct Interactions<V> {
+    click: SmallVec<[<Rc<dyn Fn(&mut V, &dyn Any, )>; 1]>,
+}
+
+
+```
+
+
+```rs
+
+
+trait Stylable {
+    type Style;
+
+    fn with_style(self, style: Self::Style) -> Self;
+}
+
+
+
+
+
+
+```

crates/storybook2/src/collab_panel.rs 🔗

@@ -0,0 +1,176 @@
+use crate::theme::{theme, Theme};
+use gpui3::{
+    div, img, svg, view, AppContext, ArcCow, Context, Element, IntoAnyElement, ParentElement,
+    ScrollState, StyleHelpers, View, ViewContext, WindowContext,
+};
+
+pub struct CollabPanel {
+    scroll_state: ScrollState,
+}
+
+pub fn collab_panel<S: 'static>(cx: &mut WindowContext) -> View<CollabPanel, S> {
+    view(cx.entity(|cx| CollabPanel::new(cx)), CollabPanel::render)
+}
+
+impl CollabPanel {
+    fn new(_: &mut AppContext) -> Self {
+        CollabPanel {
+            scroll_state: ScrollState::default(),
+        }
+    }
+}
+
+impl CollabPanel {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
+        let theme = theme(cx);
+
+        // Panel
+        div()
+            .w_64()
+            .h_full()
+            .flex()
+            .flex_col()
+            .font("Zed Sans Extended")
+            .text_color(theme.middle.base.default.foreground)
+            .border_color(theme.middle.base.default.border)
+            .border()
+            .fill(theme.middle.base.default.background)
+            .child(
+                div()
+                    .w_full()
+                    .flex()
+                    .flex_col()
+                    .overflow_y_scroll(self.scroll_state.clone())
+                    // List Container
+                    .child(
+                        div()
+                            .fill(theme.lowest.base.default.background)
+                            .pb_1()
+                            .border_color(theme.lowest.base.default.border)
+                            .border_b()
+                            //:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
+                            // .group()
+                            // List Section Header
+                            .child(self.list_section_header("#CRDB", true, theme))
+                            // List Item Large
+                            .child(self.list_item(
+                                "http://github.com/maxbrunsfeld.png?s=50",
+                                "maxbrunsfeld",
+                                theme,
+                            )),
+                    )
+                    .child(
+                        div()
+                            .py_2()
+                            .flex()
+                            .flex_col()
+                            .child(self.list_section_header("CHANNELS", true, theme)),
+                    )
+                    .child(
+                        div()
+                            .py_2()
+                            .flex()
+                            .flex_col()
+                            .child(self.list_section_header("CONTACTS", true, theme))
+                            .children(
+                                std::iter::repeat_with(|| {
+                                    vec![
+                                        self.list_item(
+                                            "http://github.com/as-cii.png?s=50",
+                                            "as-cii",
+                                            theme,
+                                        ),
+                                        self.list_item(
+                                            "http://github.com/nathansobo.png?s=50",
+                                            "nathansobo",
+                                            theme,
+                                        ),
+                                        self.list_item(
+                                            "http://github.com/maxbrunsfeld.png?s=50",
+                                            "maxbrunsfeld",
+                                            theme,
+                                        ),
+                                    ]
+                                })
+                                .take(10)
+                                .flatten(),
+                            ),
+                    ),
+            )
+            .child(
+                div()
+                    .h_7()
+                    .px_2()
+                    .border_t()
+                    .border_color(theme.middle.variant.default.border)
+                    .flex()
+                    .items_center()
+                    .child(
+                        div()
+                            .text_sm()
+                            .text_color(theme.middle.variant.default.foreground)
+                            .child("Find..."),
+                    ),
+            )
+    }
+
+    fn list_section_header(
+        &self,
+        label: impl IntoAnyElement<Self>,
+        expanded: bool,
+        theme: &Theme,
+    ) -> impl Element<State = Self> {
+        div()
+            .h_7()
+            .px_2()
+            .flex()
+            .justify_between()
+            .items_center()
+            .child(div().flex().gap_1().text_sm().child(label))
+            .child(
+                div().flex().h_full().gap_1().items_center().child(
+                    svg()
+                        .path(if expanded {
+                            "icons/radix/caret-down.svg"
+                        } else {
+                            "icons/radix/caret-up.svg"
+                        })
+                        .w_3p5()
+                        .h_3p5()
+                        .fill(theme.middle.variant.default.foreground),
+                ),
+            )
+    }
+
+    fn list_item(
+        &self,
+        avatar_uri: impl Into<ArcCow<'static, str>>,
+        label: impl IntoAnyElement<Self>,
+        theme: &Theme,
+    ) -> impl Element<State = Self> {
+        div()
+            .h_7()
+            .px_2()
+            .flex()
+            .items_center()
+            // .hover()
+            // .fill(theme.lowest.variant.hovered.background)
+            // .active()
+            // .fill(theme.lowest.variant.pressed.background)
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .text_sm()
+                    .child(
+                        img()
+                            .uri(avatar_uri)
+                            .size_3p5()
+                            .rounded_full()
+                            .fill(theme.middle.positive.default.foreground),
+                    )
+                    .child(label),
+            )
+    }
+}

crates/storybook2/src/components.rs 🔗

@@ -0,0 +1,97 @@
+use gpui2::{
+    elements::div, interactive::Interactive, platform::MouseButton, style::StyleHelpers, ArcCow,
+    Element, EventContext, IntoElement, ParentElement, ViewContext,
+};
+use std::{marker::PhantomData, rc::Rc};
+
+struct ButtonHandlers<V, D> {
+    click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
+}
+
+impl<V, D> Default for ButtonHandlers<V, D> {
+    fn default() -> Self {
+        Self { click: None }
+    }
+}
+
+#[derive(Element)]
+pub struct Button<V: 'static, D: 'static> {
+    handlers: ButtonHandlers<V, D>,
+    label: Option<ArcCow<'static, str>>,
+    icon: Option<ArcCow<'static, str>>,
+    data: Rc<D>,
+    view_type: PhantomData<V>,
+}
+
+// Impl block for buttons without data.
+// See below for an impl block for any button.
+impl<V: 'static> Button<V, ()> {
+    fn new() -> Self {
+        Self {
+            handlers: ButtonHandlers::default(),
+            label: None,
+            icon: None,
+            data: Rc::new(()),
+            view_type: PhantomData,
+        }
+    }
+
+    pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
+        Button {
+            handlers: ButtonHandlers::default(),
+            label: self.label,
+            icon: self.icon,
+            data: Rc::new(data),
+            view_type: PhantomData,
+        }
+    }
+}
+
+// Impl block for button regardless of its data type.
+impl<V: 'static, D: 'static> Button<V, D> {
+    pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
+        self.label = Some(label.into());
+        self
+    }
+
+    pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
+        self.icon = Some(icon.into());
+        self
+    }
+
+    pub fn on_click(
+        mut self,
+        handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
+    ) -> Self {
+        self.handlers.click = Some(Rc::new(handler));
+        self
+    }
+}
+
+pub fn button<V>() -> Button<V, ()> {
+    Button::new()
+}
+
+impl<V: 'static, D: 'static> Button<V, D> {
+    fn render(
+        &mut self,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> impl IntoElement<V> + Interactive<V> {
+        // let colors = &cx.theme::<Theme>().colors;
+
+        let button = div()
+            // .fill(colors.error(0.5))
+            .h_4()
+            .children(self.label.clone());
+
+        if let Some(handler) = self.handlers.click.clone() {
+            let data = self.data.clone();
+            button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
+                handler(view, data.as_ref(), cx)
+            })
+        } else {
+            button
+        }
+    }
+}

crates/storybook2/src/storybook2.rs 🔗

@@ -0,0 +1,75 @@
+#![allow(dead_code, unused_variables)]
+
+use gpui3::{Bounds, WindowBounds, WindowOptions};
+use log::LevelFilter;
+use simplelog::SimpleLogger;
+
+mod collab_panel;
+mod theme;
+mod themes;
+mod workspace;
+
+// gpui2::actions! {
+//     storybook,
+//     [ToggleInspector]
+// }
+
+fn main() {
+    // unsafe { backtrace_on_stack_overflow::enable() };
+
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    gpui3::App::production().run(|cx| {
+        let window = cx.open_window(
+            WindowOptions {
+                bounds: WindowBounds::Fixed(Bounds {
+                    size: gpui3::Size {
+                        width: 800_f32.into(),
+                        height: 600_f32.into(),
+                    },
+                    ..Default::default()
+                }),
+                ..Default::default()
+            },
+            |cx| workspace(cx),
+        );
+
+        cx.activate(true);
+    });
+}
+
+use rust_embed::RustEmbed;
+use workspace::workspace;
+
+#[derive(RustEmbed)]
+#[folder = "../../assets"]
+#[include = "themes/**/*"]
+#[include = "fonts/**/*"]
+#[include = "icons/**/*"]
+#[exclude = "*.DS_Store"]
+pub struct Assets;
+
+// impl AssetSource for Assets {
+//     fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
+//         Self::get(path)
+//             .map(|f| f.data)
+//             .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+//     }
+
+//     fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
+//         Self::iter().filter(|p| p.starts_with(path)).collect()
+//     }
+// }
+
+// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
+//     let font_paths = Assets.list("fonts");
+//     let mut embedded_fonts = Vec::new();
+//     for font_path in &font_paths {
+//         if font_path.ends_with(".ttf") {
+//             let font_path = &*font_path;
+//             let font_bytes = Assets.load(font_path).unwrap().to_vec();
+//             embedded_fonts.push(Arc::from(font_bytes));
+//         }
+//     }
+//     platform.fonts().add_fonts(&embedded_fonts).unwrap();
+// }

crates/storybook2/src/theme.rs 🔗

@@ -0,0 +1,193 @@
+use gpui3::{Element, Hsla, Layout, LayoutId, Result, StackContext, ViewContext, WindowContext};
+use serde::{de::Visitor, Deserialize, Deserializer};
+use std::{collections::HashMap, fmt};
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Theme {
+    pub name: String,
+    pub is_light: bool,
+    pub lowest: Layer,
+    pub middle: Layer,
+    pub highest: Layer,
+    pub popover_shadow: Shadow,
+    pub modal_shadow: Shadow,
+    #[serde(deserialize_with = "deserialize_player_colors")]
+    pub players: Vec<PlayerColors>,
+    #[serde(deserialize_with = "deserialize_syntax_colors")]
+    pub syntax: HashMap<String, Hsla>,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Layer {
+    pub base: StyleSet,
+    pub variant: StyleSet,
+    pub on: StyleSet,
+    pub accent: StyleSet,
+    pub positive: StyleSet,
+    pub warning: StyleSet,
+    pub negative: StyleSet,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct StyleSet {
+    #[serde(rename = "default")]
+    pub default: ContainerColors,
+    pub hovered: ContainerColors,
+    pub pressed: ContainerColors,
+    pub active: ContainerColors,
+    pub disabled: ContainerColors,
+    pub inverted: ContainerColors,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct ContainerColors {
+    pub background: Hsla,
+    pub foreground: Hsla,
+    pub border: Hsla,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct PlayerColors {
+    pub selection: Hsla,
+    pub cursor: Hsla,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Shadow {
+    pub blur: u8,
+    pub color: Hsla,
+    pub offset: Vec<u8>,
+}
+
+fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    struct PlayerArrayVisitor;
+
+    impl<'de> Visitor<'de> for PlayerArrayVisitor {
+        type Value = Vec<PlayerColors>;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+            formatter.write_str("an object with integer keys")
+        }
+
+        fn visit_map<A: serde::de::MapAccess<'de>>(
+            self,
+            mut map: A,
+        ) -> Result<Self::Value, A::Error> {
+            let mut players = Vec::with_capacity(8);
+            while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
+                if key < 8 {
+                    players.push(value);
+                } else {
+                    return Err(serde::de::Error::invalid_value(
+                        serde::de::Unexpected::Unsigned(key as u64),
+                        &"a key in range 0..7",
+                    ));
+                }
+            }
+            Ok(players)
+        }
+    }
+
+    deserializer.deserialize_map(PlayerArrayVisitor)
+}
+
+fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    #[derive(Deserialize)]
+    struct ColorWrapper {
+        color: Hsla,
+    }
+
+    struct SyntaxVisitor;
+
+    impl<'de> Visitor<'de> for SyntaxVisitor {
+        type Value = HashMap<String, Hsla>;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+            formatter.write_str("a map with keys and objects with a single color field as values")
+        }
+
+        fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
+        where
+            M: serde::de::MapAccess<'de>,
+        {
+            let mut result = HashMap::new();
+            while let Some(key) = map.next_key()? {
+                let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
+                result.insert(key, wrapper.color);
+            }
+            Ok(result)
+        }
+    }
+    deserializer.deserialize_map(SyntaxVisitor)
+}
+
+pub fn themed<E, F>(theme: Theme, cx: &mut ViewContext<E::State>, build_child: F) -> Themed<E>
+where
+    E: Element,
+    F: FnOnce(&mut ViewContext<E::State>) -> E,
+{
+    let child = cx.with_state(theme.clone(), |cx| build_child(cx));
+    Themed { theme, child }
+}
+
+pub struct Themed<E> {
+    pub(crate) theme: Theme,
+    pub(crate) child: E,
+}
+
+impl<E: Element> Element for Themed<E> {
+    type State = E::State;
+    type FrameState = E::FrameState;
+
+    fn layout(
+        &mut self,
+        state: &mut E::State,
+        cx: &mut ViewContext<E::State>,
+    ) -> anyhow::Result<(LayoutId, Self::FrameState)>
+    where
+        Self: Sized,
+    {
+        cx.with_state(self.theme.clone(), |cx| self.child.layout(state, cx))
+    }
+
+    fn paint(
+        &mut self,
+        layout: Layout,
+        state: &mut Self::State,
+        frame_state: &mut Self::FrameState,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()>
+    where
+        Self: Sized,
+    {
+        cx.with_state(self.theme.clone(), |cx| {
+            self.child.paint(layout, state, frame_state, cx)
+        })
+    }
+}
+
+// fn preferred_theme<V: 'static>(cx: &AppContext) -> Theme {
+//     settings::get::<ThemeSettings>(cx)
+//         .theme
+//         .deserialized_base_theme
+//         .lock()
+//         .get_or_insert_with(|| {
+//             let theme: Theme =
+//                 serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
+//                     .unwrap();
+//             Box::new(theme)
+//         })
+//         .downcast_ref::<Theme>()
+//         .unwrap()
+//         .clone()
+// }
+
+pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
+    cx.state()
+}

crates/storybook2/src/themes/rose_pine_dawn.rs 🔗

@@ -0,0 +1,845 @@
+use crate::theme::Theme;
+use gpui3::serde_json::{self, json};
+
+pub fn rose_pine_dawn() -> Theme {
+    serde_json::from_value(json! {
+        {
+          "name": "Rosé Pine",
+          "is_light": false,
+          "ramps": {},
+          "lowest": {
+            "base": {
+              "default": {
+                "background": "#292739",
+                "border": "#423f55",
+                "foreground": "#e0def4"
+              },
+              "hovered": {
+                "background": "#423f55",
+                "border": "#423f55",
+                "foreground": "#e0def4"
+              },
+              "pressed": {
+                "background": "#4e4b63",
+                "border": "#423f55",
+                "foreground": "#e0def4"
+              },
+              "active": {
+                "background": "#47445b",
+                "border": "#36334a",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#292739",
+                "border": "#353347",
+                "foreground": "#2f2b43"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#4b4860"
+              }
+            },
+            "variant": {
+              "default": {
+                "background": "#292739",
+                "border": "#423f55",
+                "foreground": "#75718e"
+              },
+              "hovered": {
+                "background": "#423f55",
+                "border": "#423f55",
+                "foreground": "#75718e"
+              },
+              "pressed": {
+                "background": "#4e4b63",
+                "border": "#423f55",
+                "foreground": "#75718e"
+              },
+              "active": {
+                "background": "#47445b",
+                "border": "#36334a",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#292739",
+                "border": "#353347",
+                "foreground": "#2f2b43"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#4b4860"
+              }
+            },
+            "on": {
+              "default": {
+                "background": "#1d1b2a",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "hovered": {
+                "background": "#232132",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "pressed": {
+                "background": "#2f2d40",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "active": {
+                "background": "#403e53",
+                "border": "#504d65",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#1d1b2a",
+                "border": "#1e1c2c",
+                "foreground": "#3b384f"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#3b394e"
+              }
+            },
+            "accent": {
+              "default": {
+                "background": "#2f3739",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "hovered": {
+                "background": "#435255",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "pressed": {
+                "background": "#4e6164",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "active": {
+                "background": "#5d757a",
+                "border": "#6e8f94",
+                "foreground": "#fbfdfd"
+              },
+              "disabled": {
+                "background": "#2f3739",
+                "border": "#3a4446",
+                "foreground": "#85aeb5"
+              },
+              "inverted": {
+                "background": "#fbfdfd",
+                "border": "#171717",
+                "foreground": "#587074"
+              }
+            },
+            "positive": {
+              "default": {
+                "background": "#182e23",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "hovered": {
+                "background": "#254839",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "pressed": {
+                "background": "#2c5645",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "active": {
+                "background": "#356b57",
+                "border": "#40836c",
+                "foreground": "#f9fdfb"
+              },
+              "disabled": {
+                "background": "#182e23",
+                "border": "#1e3b2e",
+                "foreground": "#4ea287"
+              },
+              "inverted": {
+                "background": "#f9fdfb",
+                "border": "#000e00",
+                "foreground": "#326552"
+              }
+            },
+            "warning": {
+              "default": {
+                "background": "#50341a",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "hovered": {
+                "background": "#6d4d2b",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "pressed": {
+                "background": "#7e5a34",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "active": {
+                "background": "#946e41",
+                "border": "#b0854f",
+                "foreground": "#fffcf9"
+              },
+              "disabled": {
+                "background": "#50341a",
+                "border": "#5e4023",
+                "foreground": "#d2a263"
+              },
+              "inverted": {
+                "background": "#fffcf9",
+                "border": "#2c1600",
+                "foreground": "#8e683c"
+              }
+            },
+            "negative": {
+              "default": {
+                "background": "#431820",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "hovered": {
+                "background": "#612834",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "pressed": {
+                "background": "#71303f",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "active": {
+                "background": "#883c4f",
+                "border": "#a44961",
+                "foreground": "#fff9fa"
+              },
+              "disabled": {
+                "background": "#431820",
+                "border": "#52202a",
+                "foreground": "#c75c79"
+              },
+              "inverted": {
+                "background": "#fff9fa",
+                "border": "#230000",
+                "foreground": "#82384a"
+              }
+            }
+          },
+          "middle": {
+            "base": {
+              "default": {
+                "background": "#1d1b2a",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "hovered": {
+                "background": "#232132",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "pressed": {
+                "background": "#2f2d40",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "active": {
+                "background": "#403e53",
+                "border": "#504d65",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#1d1b2a",
+                "border": "#1e1c2c",
+                "foreground": "#3b384f"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#3b394e"
+              }
+            },
+            "variant": {
+              "default": {
+                "background": "#1d1b2a",
+                "border": "#232132",
+                "foreground": "#75718e"
+              },
+              "hovered": {
+                "background": "#232132",
+                "border": "#232132",
+                "foreground": "#75718e"
+              },
+              "pressed": {
+                "background": "#2f2d40",
+                "border": "#232132",
+                "foreground": "#75718e"
+              },
+              "active": {
+                "background": "#403e53",
+                "border": "#504d65",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#1d1b2a",
+                "border": "#1e1c2c",
+                "foreground": "#3b384f"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#3b394e"
+              }
+            },
+            "on": {
+              "default": {
+                "background": "#191724",
+                "border": "#1c1a29",
+                "foreground": "#e0def4"
+              },
+              "hovered": {
+                "background": "#1c1a29",
+                "border": "#1c1a29",
+                "foreground": "#e0def4"
+              },
+              "pressed": {
+                "background": "#1d1b2b",
+                "border": "#1c1a29",
+                "foreground": "#e0def4"
+              },
+              "active": {
+                "background": "#222031",
+                "border": "#353347",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#191724",
+                "border": "#1a1826",
+                "foreground": "#4e4b63"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#1f1d2e"
+              }
+            },
+            "accent": {
+              "default": {
+                "background": "#2f3739",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "hovered": {
+                "background": "#435255",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "pressed": {
+                "background": "#4e6164",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "active": {
+                "background": "#5d757a",
+                "border": "#6e8f94",
+                "foreground": "#fbfdfd"
+              },
+              "disabled": {
+                "background": "#2f3739",
+                "border": "#3a4446",
+                "foreground": "#85aeb5"
+              },
+              "inverted": {
+                "background": "#fbfdfd",
+                "border": "#171717",
+                "foreground": "#587074"
+              }
+            },
+            "positive": {
+              "default": {
+                "background": "#182e23",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "hovered": {
+                "background": "#254839",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "pressed": {
+                "background": "#2c5645",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "active": {
+                "background": "#356b57",
+                "border": "#40836c",
+                "foreground": "#f9fdfb"
+              },
+              "disabled": {
+                "background": "#182e23",
+                "border": "#1e3b2e",
+                "foreground": "#4ea287"
+              },
+              "inverted": {
+                "background": "#f9fdfb",
+                "border": "#000e00",
+                "foreground": "#326552"
+              }
+            },
+            "warning": {
+              "default": {
+                "background": "#50341a",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "hovered": {
+                "background": "#6d4d2b",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "pressed": {
+                "background": "#7e5a34",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "active": {
+                "background": "#946e41",
+                "border": "#b0854f",
+                "foreground": "#fffcf9"
+              },
+              "disabled": {
+                "background": "#50341a",
+                "border": "#5e4023",
+                "foreground": "#d2a263"
+              },
+              "inverted": {
+                "background": "#fffcf9",
+                "border": "#2c1600",
+                "foreground": "#8e683c"
+              }
+            },
+            "negative": {
+              "default": {
+                "background": "#431820",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "hovered": {
+                "background": "#612834",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "pressed": {
+                "background": "#71303f",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "active": {
+                "background": "#883c4f",
+                "border": "#a44961",
+                "foreground": "#fff9fa"
+              },
+              "disabled": {
+                "background": "#431820",
+                "border": "#52202a",
+                "foreground": "#c75c79"
+              },
+              "inverted": {
+                "background": "#fff9fa",
+                "border": "#230000",
+                "foreground": "#82384a"
+              }
+            }
+          },
+          "highest": {
+            "base": {
+              "default": {
+                "background": "#191724",
+                "border": "#1c1a29",
+                "foreground": "#e0def4"
+              },
+              "hovered": {
+                "background": "#1c1a29",
+                "border": "#1c1a29",
+                "foreground": "#e0def4"
+              },
+              "pressed": {
+                "background": "#1d1b2b",
+                "border": "#1c1a29",
+                "foreground": "#e0def4"
+              },
+              "active": {
+                "background": "#222031",
+                "border": "#353347",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#191724",
+                "border": "#1a1826",
+                "foreground": "#4e4b63"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#1f1d2e"
+              }
+            },
+            "variant": {
+              "default": {
+                "background": "#191724",
+                "border": "#1c1a29",
+                "foreground": "#75718e"
+              },
+              "hovered": {
+                "background": "#1c1a29",
+                "border": "#1c1a29",
+                "foreground": "#75718e"
+              },
+              "pressed": {
+                "background": "#1d1b2b",
+                "border": "#1c1a29",
+                "foreground": "#75718e"
+              },
+              "active": {
+                "background": "#222031",
+                "border": "#353347",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#191724",
+                "border": "#1a1826",
+                "foreground": "#4e4b63"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#1f1d2e"
+              }
+            },
+            "on": {
+              "default": {
+                "background": "#1d1b2a",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "hovered": {
+                "background": "#232132",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "pressed": {
+                "background": "#2f2d40",
+                "border": "#232132",
+                "foreground": "#e0def4"
+              },
+              "active": {
+                "background": "#403e53",
+                "border": "#504d65",
+                "foreground": "#e0def4"
+              },
+              "disabled": {
+                "background": "#1d1b2a",
+                "border": "#1e1c2c",
+                "foreground": "#3b384f"
+              },
+              "inverted": {
+                "background": "#e0def4",
+                "border": "#191724",
+                "foreground": "#3b394e"
+              }
+            },
+            "accent": {
+              "default": {
+                "background": "#2f3739",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "hovered": {
+                "background": "#435255",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "pressed": {
+                "background": "#4e6164",
+                "border": "#435255",
+                "foreground": "#9cced7"
+              },
+              "active": {
+                "background": "#5d757a",
+                "border": "#6e8f94",
+                "foreground": "#fbfdfd"
+              },
+              "disabled": {
+                "background": "#2f3739",
+                "border": "#3a4446",
+                "foreground": "#85aeb5"
+              },
+              "inverted": {
+                "background": "#fbfdfd",
+                "border": "#171717",
+                "foreground": "#587074"
+              }
+            },
+            "positive": {
+              "default": {
+                "background": "#182e23",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "hovered": {
+                "background": "#254839",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "pressed": {
+                "background": "#2c5645",
+                "border": "#254839",
+                "foreground": "#5dc2a3"
+              },
+              "active": {
+                "background": "#356b57",
+                "border": "#40836c",
+                "foreground": "#f9fdfb"
+              },
+              "disabled": {
+                "background": "#182e23",
+                "border": "#1e3b2e",
+                "foreground": "#4ea287"
+              },
+              "inverted": {
+                "background": "#f9fdfb",
+                "border": "#000e00",
+                "foreground": "#326552"
+              }
+            },
+            "warning": {
+              "default": {
+                "background": "#50341a",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "hovered": {
+                "background": "#6d4d2b",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "pressed": {
+                "background": "#7e5a34",
+                "border": "#6d4d2b",
+                "foreground": "#f5c177"
+              },
+              "active": {
+                "background": "#946e41",
+                "border": "#b0854f",
+                "foreground": "#fffcf9"
+              },
+              "disabled": {
+                "background": "#50341a",
+                "border": "#5e4023",
+                "foreground": "#d2a263"
+              },
+              "inverted": {
+                "background": "#fffcf9",
+                "border": "#2c1600",
+                "foreground": "#8e683c"
+              }
+            },
+            "negative": {
+              "default": {
+                "background": "#431820",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "hovered": {
+                "background": "#612834",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "pressed": {
+                "background": "#71303f",
+                "border": "#612834",
+                "foreground": "#ea6f92"
+              },
+              "active": {
+                "background": "#883c4f",
+                "border": "#a44961",
+                "foreground": "#fff9fa"
+              },
+              "disabled": {
+                "background": "#431820",
+                "border": "#52202a",
+                "foreground": "#c75c79"
+              },
+              "inverted": {
+                "background": "#fff9fa",
+                "border": "#230000",
+                "foreground": "#82384a"
+              }
+            }
+          },
+          "popover_shadow": {
+            "blur": 4,
+            "color": "#00000033",
+            "offset": [
+              1,
+              2
+            ]
+          },
+          "modal_shadow": {
+            "blur": 16,
+            "color": "#00000033",
+            "offset": [
+              0,
+              2
+            ]
+          },
+          "players": {
+            "0": {
+              "selection": "#9cced73d",
+              "cursor": "#9cced7"
+            },
+            "1": {
+              "selection": "#5dc2a33d",
+              "cursor": "#5dc2a3"
+            },
+            "2": {
+              "selection": "#9d76913d",
+              "cursor": "#9d7691"
+            },
+            "3": {
+              "selection": "#c4a7e63d",
+              "cursor": "#c4a7e6"
+            },
+            "4": {
+              "selection": "#c4a7e63d",
+              "cursor": "#c4a7e6"
+            },
+            "5": {
+              "selection": "#32748f3d",
+              "cursor": "#32748f"
+            },
+            "6": {
+              "selection": "#ea6f923d",
+              "cursor": "#ea6f92"
+            },
+            "7": {
+              "selection": "#f5c1773d",
+              "cursor": "#f5c177"
+            }
+          },
+          "syntax": {
+            "comment": {
+              "color": "#6e6a86"
+            },
+            "operator": {
+              "color": "#31748f"
+            },
+            "punctuation": {
+              "color": "#908caa"
+            },
+            "variable": {
+              "color": "#e0def4"
+            },
+            "string": {
+              "color": "#f6c177"
+            },
+            "type": {
+              "color": "#9ccfd8"
+            },
+            "type.builtin": {
+              "color": "#9ccfd8"
+            },
+            "boolean": {
+              "color": "#ebbcba"
+            },
+            "function": {
+              "color": "#ebbcba"
+            },
+            "keyword": {
+              "color": "#31748f"
+            },
+            "tag": {
+              "color": "#9ccfd8"
+            },
+            "function.method": {
+              "color": "#ebbcba"
+            },
+            "title": {
+              "color": "#f6c177"
+            },
+            "link_text": {
+              "color": "#9ccfd8",
+              "italic": false
+            },
+            "link_uri": {
+              "color": "#ebbcba"
+            }
+          },
+          "color_family": {
+            "neutral": {
+              "low": 11.568627450980392,
+              "high": 91.37254901960785,
+              "range": 79.80392156862746,
+              "scaling_value": 1.2530712530712529
+            },
+            "red": {
+              "low": 6.862745098039216,
+              "high": 100,
+              "range": 93.13725490196079,
+              "scaling_value": 1.0736842105263158
+            },
+            "orange": {
+              "low": 5.490196078431373,
+              "high": 100,
+              "range": 94.50980392156863,
+              "scaling_value": 1.058091286307054
+            },
+            "yellow": {
+              "low": 8.627450980392156,
+              "high": 100,
+              "range": 91.37254901960785,
+              "scaling_value": 1.094420600858369
+            },
+            "green": {
+              "low": 2.7450980392156863,
+              "high": 100,
+              "range": 97.25490196078431,
+              "scaling_value": 1.028225806451613
+            },
+            "cyan": {
+              "low": 0,
+              "high": 100,
+              "range": 100,
+              "scaling_value": 1
+            },
+            "blue": {
+              "low": 9.019607843137255,
+              "high": 100,
+              "range": 90.98039215686275,
+              "scaling_value": 1.0991379310344827
+            },
+            "violet": {
+              "low": 5.490196078431373,
+              "high": 100,
+              "range": 94.50980392156863,
+              "scaling_value": 1.058091286307054
+            },
+            "magenta": {
+              "low": 0,
+              "high": 100,
+              "range": 100,
+              "scaling_value": 1
+            }
+          }
+        }
+    })
+    .unwrap()
+}

crates/storybook2/src/workspace.rs 🔗

@@ -0,0 +1,473 @@
+use crate::{
+    collab_panel::{collab_panel, CollabPanel},
+    theme::theme,
+    themes::rose_pine_dawn,
+};
+use gpui3::{
+    div, img, svg, view, Context, Element, ParentElement, RootView, StyleHelpers, View,
+    ViewContext, WindowContext,
+};
+
+pub struct Workspace {
+    left_panel: View<CollabPanel, Self>,
+    right_panel: View<CollabPanel, Self>,
+}
+
+pub fn workspace(cx: &mut WindowContext) -> RootView<Workspace> {
+    view(cx.entity(|cx| Workspace::new(cx)), Workspace::render)
+}
+
+impl Workspace {
+    fn new(cx: &mut ViewContext<Self>) -> Self {
+        Self {
+            left_panel: collab_panel(cx),
+            right_panel: collab_panel(cx),
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
+        let theme = rose_pine_dawn();
+        div()
+            .font("Helvetica")
+            .text_base()
+            .size_full()
+            .fill(theme.middle.positive.default.background)
+            .child("Hello world")
+
+        // TODO: Implement style.
+        //.size_full().fill(gpui3::hsla(0.83, 1., 0.5, 1.))
+
+        // TODO: Debug font not font.
+        //.child("Is this thing on?")
+
+        // themed(rose_pine_dawn(), cx, |cx| {
+        //     div()
+        //         .size_full()
+        //         .flex()
+        //         .flex_col()
+        //         .font("Zed Sans Extended")
+        //         .gap_0()
+        //         .justify_start()
+        //         .items_start()
+        //         .text_color(theme.lowest.base.default.foreground)
+        //         // .fill(theme.middle.base.default.background)
+        //         .fill(gpui3::hsla(0.83, 1., 0.5, 1.))
+        //         .child(titlebar(cx))
+        //         .child(
+        //             div()
+        //                 .flex_1()
+        //                 .w_full()
+        //                 .flex()
+        //                 .flex_row()
+        //                 .overflow_hidden()
+        //                 .child(self.left_panel.clone())
+        //                 .child(div().h_full().flex_1())
+        //                 .child(self.right_panel.clone()),
+        //         )
+        //         .child(statusbar::statusbar(cx))
+        // })
+    }
+}
+
+struct Titlebar;
+
+pub fn titlebar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
+    let ref mut this = Titlebar;
+    let theme = theme(cx);
+    div()
+        .flex()
+        .items_center()
+        .justify_between()
+        .w_full()
+        .h_8()
+        .fill(theme.lowest.base.default.background)
+        .child(this.left_group(cx))
+        .child(this.right_group(cx))
+}
+
+impl Titlebar {
+    fn render<V: 'static + Send + Sync>(
+        &mut self,
+        cx: &mut ViewContext<V>,
+    ) -> impl Element<State = V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .h_8()
+            .fill(theme.lowest.base.default.background)
+            .child(self.left_group(cx))
+            .child(self.right_group(cx))
+    }
+
+    fn left_group<S: 'static + Send + Sync>(
+        &mut self,
+        cx: &mut ViewContext<S>,
+    ) -> impl Element<State = S> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_4()
+            .px_2()
+            // === Traffic Lights === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_2()
+                    .child(
+                        div()
+                            .w_3()
+                            .h_3()
+                            .rounded_full()
+                            .fill(theme.lowest.positive.default.foreground),
+                    )
+                    .child(
+                        div()
+                            .w_3()
+                            .h_3()
+                            .rounded_full()
+                            .fill(theme.lowest.warning.default.foreground),
+                    )
+                    .child(
+                        div()
+                            .w_3()
+                            .h_3()
+                            .rounded_full()
+                            .fill(theme.lowest.negative.default.foreground),
+                    ),
+            )
+            // === Project Info === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .child(
+                        div()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .px_2()
+                            .rounded_md()
+                            // .hover()
+                            // .fill(theme.lowest.base.hovered.background)
+                            // .active()
+                            // .fill(theme.lowest.base.pressed.background)
+                            .child(div().text_sm().child("project")),
+                    )
+                    .child(
+                        div()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .px_2()
+                            .rounded_md()
+                            .text_color(theme.lowest.variant.default.foreground)
+                            // .hover()
+                            // .fill(theme.lowest.base.hovered.background)
+                            // .active()
+                            // .fill(theme.lowest.base.pressed.background)
+                            .child(div().text_sm().child("branch")),
+                    ),
+            )
+    }
+
+    fn right_group<S: 'static + Send + Sync>(
+        &mut self,
+        cx: &mut ViewContext<S>,
+    ) -> impl Element<State = S> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_3()
+            .px_2()
+            // === Actions === //
+            .child(
+                div().child(
+                    div().flex().items_center().gap_1().child(
+                        div().size_4().flex().items_center().justify_center().child(
+                            svg()
+                                .path("icons/exit.svg")
+                                .size_4()
+                                .fill(theme.lowest.base.default.foreground),
+                        ),
+                    ),
+                ),
+            )
+            .child(div().w_px().h_3().fill(theme.lowest.base.default.border))
+            // === Comms === //
+            .child(
+                div().child(
+                    div()
+                        .flex()
+                        .items_center()
+                        .gap_px()
+                        .child(
+                            div()
+                                .px_2()
+                                .py_1()
+                                .rounded_md()
+                                .h_full()
+                                .flex()
+                                .items_center()
+                                .justify_center()
+                                // .hover()
+                                // .fill(theme.lowest.base.hovered.background)
+                                // .active()
+                                // .fill(theme.lowest.base.pressed.background)
+                                .child(
+                                    svg()
+                                        .path("icons/microphone.svg")
+                                        .size_3p5()
+                                        .fill(theme.lowest.base.default.foreground),
+                                ),
+                        )
+                        .child(
+                            div()
+                                .px_2()
+                                .py_1()
+                                .rounded_md()
+                                .h_full()
+                                .flex()
+                                .items_center()
+                                .justify_center()
+                                // .hover()
+                                // .fill(theme.lowest.base.hovered.background)
+                                // .active()
+                                // .fill(theme.lowest.base.pressed.background)
+                                .child(
+                                    svg()
+                                        .path("icons/radix/speaker-loud.svg")
+                                        .size_3p5()
+                                        .fill(theme.lowest.base.default.foreground),
+                                ),
+                        )
+                        .child(
+                            div()
+                                .px_2()
+                                .py_1()
+                                .rounded_md()
+                                .h_full()
+                                .flex()
+                                .items_center()
+                                .justify_center()
+                                // .hover()
+                                // .fill(theme.lowest.base.hovered.background)
+                                // .active()
+                                // .fill(theme.lowest.base.pressed.background)
+                                .child(
+                                    svg()
+                                        .path("icons/radix/desktop.svg")
+                                        .size_3p5()
+                                        .fill(theme.lowest.base.default.foreground),
+                                ),
+                        ),
+                ),
+            )
+            .child(div().w_px().h_3().fill(theme.lowest.base.default.border))
+            // User Group
+            .child(
+                div().child(
+                    div()
+                        .px_1()
+                        .py_1()
+                        .flex()
+                        .items_center()
+                        .justify_center()
+                        .rounded_md()
+                        .gap_0p5()
+                        // .hover()
+                        // .fill(theme.lowest.base.hovered.background)
+                        // .active()
+                        // .fill(theme.lowest.base.pressed.background)
+                        .child(
+                            img()
+                                .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                .size_4()
+                                .rounded_md()
+                                .fill(theme.middle.on.default.foreground),
+                        )
+                        .child(
+                            svg()
+                                .path("icons/caret_down_8.svg")
+                                .w_2()
+                                .h_2()
+                                .fill(theme.lowest.variant.default.foreground),
+                        ),
+                ),
+            )
+    }
+}
+
+// ================================================================================ //
+
+mod statusbar {
+
+    use super::*;
+
+    pub fn statusbar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .h_8()
+            .fill(theme.lowest.base.default.background)
+        // .child(left_group(cx))
+        // .child(right_group(cx))
+    }
+
+    fn left_group<V: 'static + Send + Sync>(cx: &mut ViewContext<V>) -> impl Element<State = V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_4()
+            .px_2()
+            // === Tools === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/project.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.base.default.foreground),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/conversations.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.base.default.foreground),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/file_icons/notebook.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.accent.default.foreground),
+                            ),
+                    ),
+            )
+            // === Diagnostics === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_2()
+                    .child(
+                        div()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .gap_0p5()
+                            .px_1()
+                            .text_color(theme.lowest.variant.default.foreground)
+                            // .hover()
+                            // .fill(theme.lowest.base.hovered.background)
+                            // .active()
+                            // .fill(theme.lowest.base.pressed.background)
+                            .child(
+                                svg()
+                                    .path("icons/error.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.negative.default.foreground),
+                            )
+                            .child(div().text_sm().child("2")),
+                    )
+                    .child(
+                        div()
+                            .text_sm()
+                            .text_color(theme.lowest.variant.default.foreground)
+                            .child("Something is wrong"),
+                    ),
+            )
+    }
+
+    fn right_group<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_4()
+            .px_2()
+            // === Tools === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/check_circle.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.base.default.foreground),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/copilot.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.accent.default.foreground),
+                            ),
+                    ),
+            )
+    }
+}

crates/util/src/arc_cow.rs 🔗

@@ -1,4 +1,7 @@
-use std::sync::Arc;
+use std::{
+    fmt::{self, Debug},
+    sync::Arc,
+};
 
 #[derive(PartialEq, Eq)]
 pub enum ArcCow<'a, T: ?Sized> {
@@ -72,3 +75,12 @@ impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
         }
     }
 }
+
+impl<'a, T: ?Sized + Debug> Debug for ArcCow<'a, T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            ArcCow::Borrowed(borrowed) => Debug::fmt(borrowed, f),
+            ArcCow::Owned(owned) => Debug::fmt(&**owned, f),
+        }
+    }
+}

crates/zed/Cargo.toml 🔗

@@ -139,6 +139,7 @@ tree-sitter-nu.workspace = true
 url = "2.2"
 urlencoding = "2.1.2"
 uuid = { version = "1.1.2", features = ["v4"] }
+owning_ref = "0.4.1"
 
 [dev-dependencies]
 call = { path = "../call", features = ["test-support"] }