Compare commits
4 commits
main
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
![]() |
001692bfc9 | ||
![]() |
63dcef2a76 | ||
![]() |
b504cc6dc4 | ||
![]() |
94d4174557 |
27 changed files with 8500 additions and 40 deletions
468
Cargo.lock
generated
468
Cargo.lock
generated
|
@ -273,6 +273,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||
|
||||
[[package]]
|
||||
name = "ash"
|
||||
version = "0.37.3+1.3.251"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
|
||||
dependencies = [
|
||||
"libloading 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assets"
|
||||
version = "0.1.0"
|
||||
|
@ -1177,6 +1186,12 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "channel"
|
||||
version = "0.1.0"
|
||||
|
@ -1436,6 +1451,16 @@ dependencies = [
|
|||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.43.0"
|
||||
|
@ -1589,6 +1614,37 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "com"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6"
|
||||
dependencies = [
|
||||
"com_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "com_macros"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5"
|
||||
dependencies = [
|
||||
"com_macros_support",
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "com_macros_support"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.6"
|
||||
|
@ -2110,6 +2166,17 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
|
||||
|
||||
[[package]]
|
||||
name = "d3d12"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"libloading 0.8.0",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
|
@ -3158,6 +3225,17 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
|
||||
dependencies = [
|
||||
"khronos_api",
|
||||
"log",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
|
@ -3189,6 +3267,27 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"slotmap",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glutin_wgl_sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"
|
||||
dependencies = [
|
||||
"gl_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "go_to_line"
|
||||
version = "0.1.0"
|
||||
|
@ -3206,6 +3305,58 @@ dependencies = [
|
|||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu-alloc"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"gpu-alloc-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu-alloc-types"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu-allocator"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884"
|
||||
dependencies = [
|
||||
"log",
|
||||
"presser",
|
||||
"thiserror",
|
||||
"winapi 0.3.9",
|
||||
"windows 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu-descriptor"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"gpu-descriptor-types",
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu-descriptor-types"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpui"
|
||||
version = "0.1.0"
|
||||
|
@ -3237,7 +3388,7 @@ dependencies = [
|
|||
"linkme",
|
||||
"log",
|
||||
"media",
|
||||
"metal",
|
||||
"metal 0.21.0",
|
||||
"num_cpus",
|
||||
"objc",
|
||||
"ordered-float 2.10.0",
|
||||
|
@ -3268,6 +3419,7 @@ dependencies = [
|
|||
"util",
|
||||
"uuid 1.4.1",
|
||||
"waker-fn",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3341,6 +3493,21 @@ dependencies = [
|
|||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hassle-rs"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"com",
|
||||
"libc",
|
||||
"libloading 0.8.0",
|
||||
"thiserror",
|
||||
"widestring",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.3.9"
|
||||
|
@ -3404,6 +3571,12 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hexf-parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.3"
|
||||
|
@ -3855,9 +4028,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.64"
|
||||
version = "0.3.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
|
||||
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -3893,6 +4066,23 @@ dependencies = [
|
|||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "khronos-egl"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libloading 0.8.0",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "khronos_api"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
|
@ -4354,7 +4544,7 @@ dependencies = [
|
|||
"bytes 1.5.0",
|
||||
"core-foundation",
|
||||
"foreign-types 0.3.2",
|
||||
"metal",
|
||||
"metal 0.21.0",
|
||||
"objc",
|
||||
]
|
||||
|
||||
|
@ -4422,6 +4612,21 @@ dependencies = [
|
|||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"block",
|
||||
"core-graphics-types",
|
||||
"foreign-types 0.5.0",
|
||||
"log",
|
||||
"objc",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
|
@ -4589,6 +4794,26 @@ version = "0.8.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8878eb410fc90853da3908aebfe61d73d26d4437ef850b70050461f939509899"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bitflags 2.4.1",
|
||||
"codespan-reporting",
|
||||
"hexf-parse",
|
||||
"indexmap 2.0.0",
|
||||
"log",
|
||||
"num-traits",
|
||||
"rustc-hash",
|
||||
"spirv",
|
||||
"termcolor",
|
||||
"thiserror",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoid"
|
||||
version = "0.4.0"
|
||||
|
@ -4990,6 +5215,15 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvim-rs"
|
||||
version = "0.6.0-pre"
|
||||
|
@ -5636,6 +5870,12 @@ version = "0.2.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "presser"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
|
||||
|
||||
[[package]]
|
||||
name = "prettier"
|
||||
version = "0.1.0"
|
||||
|
@ -5740,6 +5980,12 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f0f7f43585c34e4fdd7497d746bc32e14458cf11c69341cc0587b1d825dde42"
|
||||
|
||||
[[package]]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
|
@ -6103,6 +6349,12 @@ dependencies = [
|
|||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.5.2"
|
||||
|
@ -6307,6 +6559,12 @@ dependencies = [
|
|||
"bytecheck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "renderdoc-sys"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.20"
|
||||
|
@ -7347,13 +7605,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "simplelog"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720"
|
||||
version = "0.12.0"
|
||||
source = "git+https://github.com/Drakulix/simplelog.rs#4ef071dfd008d7729658cd5313e6d877bde272ca"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"log",
|
||||
"termcolor",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7479,6 +7736,15 @@ dependencies = [
|
|||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spirv"
|
||||
version = "0.3.0+sdk-1.3.268.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.2"
|
||||
|
@ -8062,9 +8328,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
@ -8242,18 +8508,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.48"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.48"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -8310,6 +8576,8 @@ checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
|
|||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
|
@ -9175,6 +9443,12 @@ version = "0.1.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
version = "0.1.1"
|
||||
|
@ -9467,9 +9741,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.87"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
|
||||
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -9477,9 +9751,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.87"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
|
||||
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
|
@ -9492,9 +9766,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.37"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
|
||||
checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
|
@ -9504,9 +9778,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.87"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
||||
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -9514,9 +9788,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.87"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -9527,9 +9801,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.87"
|
||||
version = "0.2.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
|
@ -9751,9 +10025,9 @@ source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e
|
|||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.64"
|
||||
version = "0.3.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
|
||||
checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
@ -9819,6 +10093,112 @@ dependencies = [
|
|||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.4",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg_aliases",
|
||||
"js-sys",
|
||||
"log",
|
||||
"naga",
|
||||
"parking_lot 0.12.1",
|
||||
"profiling",
|
||||
"raw-window-handle 0.6.0",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"wgpu-core",
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-core"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b15e451d4060ada0d99a64df44e4d590213496da7c4f245572d51071e8e30ed"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.4",
|
||||
"bit-vec",
|
||||
"bitflags 2.4.1",
|
||||
"cfg_aliases",
|
||||
"codespan-reporting",
|
||||
"indexmap 2.0.0",
|
||||
"log",
|
||||
"naga",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"profiling",
|
||||
"raw-window-handle 0.6.0",
|
||||
"rustc-hash",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"web-sys",
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-hal"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bb47856236bfafc0bc591a925eb036ac19cd987624a447ff353e7a7e7e6f72"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"arrayvec 0.7.4",
|
||||
"ash",
|
||||
"bit-set",
|
||||
"bitflags 2.4.1",
|
||||
"block",
|
||||
"cfg_aliases",
|
||||
"core-graphics-types",
|
||||
"d3d12",
|
||||
"glow",
|
||||
"glutin_wgl_sys",
|
||||
"gpu-alloc",
|
||||
"gpu-allocator",
|
||||
"gpu-descriptor",
|
||||
"hassle-rs",
|
||||
"js-sys",
|
||||
"khronos-egl",
|
||||
"libc",
|
||||
"libloading 0.8.0",
|
||||
"log",
|
||||
"metal 0.27.0",
|
||||
"naga",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"profiling",
|
||||
"range-alloc",
|
||||
"raw-window-handle 0.6.0",
|
||||
"renderdoc-sys",
|
||||
"rustc-hash",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"wgpu-types",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-types"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "895fcbeb772bfb049eb80b2d6e47f6c9af235284e9703c96fc0218a42ffd5af2"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"js-sys",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "4.4.2"
|
||||
|
@ -9837,6 +10217,12 @@ version = "1.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
|
@ -9898,6 +10284,25 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
|
@ -10192,6 +10597,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
|
||||
|
||||
[[package]]
|
||||
name = "xmlparser"
|
||||
version = "0.13.5"
|
||||
|
@ -10312,6 +10723,7 @@ dependencies = [
|
|||
"theme",
|
||||
"theme_selector",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tiny_http",
|
||||
"toml",
|
||||
"tree-sitter",
|
||||
|
|
|
@ -129,7 +129,7 @@ sysinfo = "0.29.10"
|
|||
tempfile = "3.9.0"
|
||||
thiserror = "1.0.29"
|
||||
tiktoken-rs = "0.5.7"
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known", "alloc", "parsing"] }
|
||||
toml = "0.5"
|
||||
tree-sitter = { version = "0.20", features = ["wasm"] }
|
||||
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
|
||||
|
|
|
@ -16,6 +16,7 @@ test-support = [
|
|||
"util/test-support",
|
||||
]
|
||||
runtime_shaders = []
|
||||
wgpu = ["dep:wgpu"]
|
||||
|
||||
[lib]
|
||||
path = "src/gpui.rs"
|
||||
|
@ -67,13 +68,16 @@ util = { path = "../util" }
|
|||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
waker-fn = "1.1.0"
|
||||
|
||||
# TODO: Handle this better once we have actual cross-platform building
|
||||
wgpu = { version = "0.19.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
backtrace = "0.3"
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
dhat = "0.3"
|
||||
env_logger.workspace = true
|
||||
png = "0.16"
|
||||
simplelog = "0.9"
|
||||
simplelog = { git = "https://github.com/Drakulix/simplelog.rs" }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
mod app_menu;
|
||||
mod keystroke;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(all(target_os = "macos", not(feature = "wgpu")))]
|
||||
mod mac;
|
||||
#[cfg(all(target_os = "macos", feature = "wgpu"))]
|
||||
mod mac_wgpu;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
mod test;
|
||||
|
||||
|
@ -34,8 +36,10 @@ use uuid::Uuid;
|
|||
|
||||
pub use app_menu::*;
|
||||
pub use keystroke::*;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[cfg(all(target_os = "macos", not(feature = "wgpu")))]
|
||||
pub(crate) use mac::*;
|
||||
#[cfg(all(target_os = "macos", feature = "wgpu"))]
|
||||
pub(crate) use mac_wgpu::*;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) use test::*;
|
||||
use time::UtcOffset;
|
||||
|
|
123
crates/gpui/src/platform/mac_wgpu.rs
Normal file
123
crates/gpui/src/platform/mac_wgpu.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
//! 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 display;
|
||||
mod display_linker;
|
||||
mod events;
|
||||
mod metal_atlas;
|
||||
mod metal_renderer;
|
||||
mod open_type;
|
||||
mod platform;
|
||||
mod text_system;
|
||||
mod wgpu_atlas;
|
||||
mod wgpu_renderer;
|
||||
mod window;
|
||||
mod window_appearance;
|
||||
|
||||
use crate::{px, size, GlobalPixels, Pixels, Size};
|
||||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger},
|
||||
};
|
||||
use metal_renderer::*;
|
||||
use objc::runtime::{BOOL, NO, YES};
|
||||
use std::ops::Range;
|
||||
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use display_linker::*;
|
||||
pub(crate) use metal_atlas::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use text_system::*;
|
||||
pub(crate) 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSRect> for Size<Pixels> {
|
||||
fn from(rect: NSRect) -> Self {
|
||||
let NSSize { width, height } = rect.size;
|
||||
size(width.into(), height.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSRect> for Size<GlobalPixels> {
|
||||
fn from(rect: NSRect) -> Self {
|
||||
let NSSize { width, height } = rect.size;
|
||||
size(width.into(), height.into())
|
||||
}
|
||||
}
|
1
crates/gpui/src/platform/mac_wgpu/dispatch.h
Normal file
1
crates/gpui/src/platform/mac_wgpu/dispatch.h
Normal file
|
@ -0,0 +1 @@
|
|||
#include <dispatch/dispatch.h>
|
101
crates/gpui/src/platform/mac_wgpu/dispatcher.rs
Normal file
101
crates/gpui/src/platform/mac_wgpu/dispatcher.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{PlatformDispatcher, TaskLabel};
|
||||
use async_task::Runnable;
|
||||
use objc::{
|
||||
class, msg_send,
|
||||
runtime::{BOOL, YES},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use std::{ffi::c_void, ptr::NonNull, sync::Arc, time::Duration};
|
||||
|
||||
/// All items in the generated file are marked as pub, so we're gonna wrap it in a separate mod to prevent
|
||||
/// these pub items from leaking into public API.
|
||||
pub(crate) mod dispatch_sys {
|
||||
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
|
||||
}
|
||||
|
||||
use dispatch_sys::*;
|
||||
pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t {
|
||||
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
|
||||
}
|
||||
|
||||
pub(crate) struct MacDispatcher {
|
||||
parker: Arc<Mutex<Parker>>,
|
||||
}
|
||||
|
||||
impl Default for MacDispatcher {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MacDispatcher {
|
||||
pub fn new() -> Self {
|
||||
MacDispatcher {
|
||||
parker: Arc::new(Mutex::new(Parker::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||
unsafe {
|
||||
let queue =
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
|
||||
let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
|
||||
dispatch_after_f(
|
||||
when,
|
||||
queue,
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&self, _background_only: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park()
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
self.parker.lock().unparker()
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn trampoline(runnable: *mut c_void) {
|
||||
let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
|
||||
task.run();
|
||||
}
|
166
crates/gpui/src/platform/mac_wgpu/display.rs
Normal file
166
crates/gpui/src/platform/mac_wgpu/display.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
|
||||
use anyhow::Result;
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||
};
|
||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MacDisplay(pub(crate) CGDirectDisplayID);
|
||||
|
||||
unsafe impl Send for MacDisplay {}
|
||||
|
||||
impl MacDisplay {
|
||||
/// Get the screen with the given [`DisplayId`].
|
||||
pub fn find_by_id(id: DisplayId) -> Option<Self> {
|
||||
Self::all().find(|screen| screen.id() == 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.
|
||||
pub fn primary() -> Self {
|
||||
// Instead of iterating through all active systems displays via `all()` we use the first
|
||||
// NSScreen and gets its CGDirectDisplayID, because we can't be sure that `CGGetActiveDisplayList`
|
||||
// will always return a list of active displays (machine might be sleeping).
|
||||
//
|
||||
// The following is what Chromium does too:
|
||||
//
|
||||
// https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/ui/display/mac/screen_mac.mm#56
|
||||
unsafe {
|
||||
let screens = NSScreen::screens(nil);
|
||||
let screen = cocoa::foundation::NSArray::objectAtIndex(screens, 0);
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
|
||||
let screen_number = device_description.objectForKey_(screen_number_key);
|
||||
let screen_number: CGDirectDisplayID = msg_send![screen_number, unsignedIntegerValue];
|
||||
Self(screen_number)
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains an iterator over all currently active system displays.
|
||||
pub fn all() -> impl Iterator<Item = Self> {
|
||||
unsafe {
|
||||
// We're assuming there aren't more than 32 displays connected to the system.
|
||||
let mut displays = Vec::with_capacity(32);
|
||||
let mut display_count = 0;
|
||||
let result = CGGetActiveDisplayList(
|
||||
displays.capacity() as u32,
|
||||
displays.as_mut_ptr(),
|
||||
&mut display_count,
|
||||
);
|
||||
|
||||
if result == 0 {
|
||||
displays.set_len(display_count as usize);
|
||||
displays.into_iter().map(MacDisplay)
|
||||
} else {
|
||||
panic!("Failed to get active display list. Result: {result}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from Cocoa's coordinate space to GPUI's coordinate space.
|
||||
///
|
||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||
pub(crate) fn global_bounds_from_ns_rect(rect: NSRect) -> Bounds<GlobalPixels> {
|
||||
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
|
||||
|
||||
Bounds {
|
||||
origin: point(
|
||||
GlobalPixels(rect.origin.x as f32),
|
||||
GlobalPixels(
|
||||
primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
|
||||
),
|
||||
),
|
||||
size: size(
|
||||
GlobalPixels(rect.size.width as f32),
|
||||
GlobalPixels(rect.size.height as f32),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from GPUI's coordinate system to Cocoa's native coordinate space.
|
||||
///
|
||||
/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards (matching CoreGraphics)
|
||||
pub(crate) fn global_bounds_to_ns_rect(bounds: Bounds<GlobalPixels>) -> NSRect {
|
||||
let primary_screen_height = MacDisplay::primary().bounds().size.height;
|
||||
|
||||
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()),
|
||||
)
|
||||
}
|
||||
|
||||
impl PlatformDisplay for MacDisplay {
|
||||
fn id(&self) -> DisplayId {
|
||||
DisplayId(self.0)
|
||||
}
|
||||
|
||||
fn uuid(&self) -> Result<Uuid> {
|
||||
let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
|
||||
anyhow::ensure!(
|
||||
!cfuuid.is_null(),
|
||||
"AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
|
||||
);
|
||||
|
||||
let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
|
||||
Ok(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 bounds(&self) -> Bounds<GlobalPixels> {
|
||||
unsafe {
|
||||
// CGDisplayBounds is in "global display" coordinates, where 0 is
|
||||
// the top left of the primary display.
|
||||
let bounds = CGDisplayBounds(self.0);
|
||||
|
||||
Bounds {
|
||||
origin: point(
|
||||
GlobalPixels(bounds.origin.x as f32),
|
||||
GlobalPixels(bounds.origin.y as f32),
|
||||
),
|
||||
size: size(
|
||||
GlobalPixels(bounds.size.width as f32),
|
||||
GlobalPixels(bounds.size.height as f32),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
272
crates/gpui/src/platform/mac_wgpu/display_linker.rs
Normal file
272
crates/gpui/src/platform/mac_wgpu/display_linker.rs
Normal file
|
@ -0,0 +1,272 @@
|
|||
use std::{
|
||||
ffi::c_void,
|
||||
mem,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use crate::DisplayId;
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub(crate) struct MacDisplayLinker {
|
||||
links: HashMap<DisplayId, MacDisplayLink>,
|
||||
}
|
||||
|
||||
struct MacDisplayLink {
|
||||
system_link: sys::DisplayLink,
|
||||
_output_callback: Arc<OutputCallback>,
|
||||
}
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn new() -> Self {
|
||||
MacDisplayLinker {
|
||||
links: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutputCallback = Mutex<Box<dyn FnMut() + Send>>;
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn set_output_callback(
|
||||
&mut self,
|
||||
display_id: DisplayId,
|
||||
output_callback: Box<dyn FnMut() + Send>,
|
||||
) {
|
||||
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
||||
let callback = Arc::new(Mutex::new(output_callback));
|
||||
let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
|
||||
unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
|
||||
|
||||
self.links.insert(
|
||||
display_id,
|
||||
MacDisplayLink {
|
||||
_output_callback: callback,
|
||||
system_link,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
log::warn!("DisplayLink could not be obtained for {:?}", display_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.start();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.stop();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn trampoline(
|
||||
_display_link_out: *mut sys::CVDisplayLink,
|
||||
current_time: *const sys::CVTimeStamp,
|
||||
output_time: *const sys::CVTimeStamp,
|
||||
_flags_in: i64,
|
||||
_flags_out: *mut i64,
|
||||
user_data: *mut c_void,
|
||||
) -> i32 {
|
||||
if let Some((_current_time, _output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
|
||||
let output_callback: Weak<OutputCallback> =
|
||||
Weak::from_raw(user_data as *mut OutputCallback);
|
||||
if let Some(output_callback) = output_callback.upgrade() {
|
||||
(output_callback.lock())()
|
||||
}
|
||||
mem::forget(output_callback);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
mod sys {
|
||||
//! Derived from display-link crate under the following license:
|
||||
//! <https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT>
|
||||
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
|
||||
#![allow(dead_code, non_upper_case_globals)]
|
||||
|
||||
use foreign_types::{foreign_type, ForeignType};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
fmt::{Debug, Formatter, Result},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CVDisplayLink {}
|
||||
|
||||
foreign_type! {
|
||||
type CType = CVDisplayLink;
|
||||
fn drop = CVDisplayLinkRelease;
|
||||
fn clone = CVDisplayLinkRetain;
|
||||
pub struct DisplayLink;
|
||||
pub struct DisplayLinkRef;
|
||||
}
|
||||
|
||||
impl Debug for DisplayLink {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> Result {
|
||||
formatter
|
||||
.debug_tuple("DisplayLink")
|
||||
.field(&self.as_ptr())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct CVTimeStamp {
|
||||
pub version: u32,
|
||||
pub video_time_scale: i32,
|
||||
pub video_time: i64,
|
||||
pub host_time: u64,
|
||||
pub rate_scalar: f64,
|
||||
pub video_refresh_period: i64,
|
||||
pub smpte_time: CVSMPTETime,
|
||||
pub flags: u64,
|
||||
pub reserved: u64,
|
||||
}
|
||||
|
||||
pub type CVTimeStampFlags = u64;
|
||||
|
||||
pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
|
||||
pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
|
||||
pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
|
||||
pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
|
||||
pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
|
||||
pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
|
||||
pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
|
||||
pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
|
||||
kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
|
||||
pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
|
||||
kCVTimeStampTopField | kCVTimeStampBottomField;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub(crate) struct CVSMPTETime {
|
||||
pub subframes: i16,
|
||||
pub subframe_divisor: i16,
|
||||
pub counter: u32,
|
||||
pub time_type: u32,
|
||||
pub flags: u32,
|
||||
pub hours: i16,
|
||||
pub minutes: i16,
|
||||
pub seconds: i16,
|
||||
pub frames: i16,
|
||||
}
|
||||
|
||||
pub type CVSMPTETimeType = u32;
|
||||
|
||||
pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
|
||||
pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
|
||||
pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
|
||||
pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
|
||||
pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
|
||||
pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
|
||||
pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
|
||||
pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
|
||||
|
||||
pub type CVSMPTETimeFlags = u32;
|
||||
|
||||
pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
|
||||
pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
|
||||
|
||||
pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
|
||||
display_link_out: *mut CVDisplayLink,
|
||||
// A pointer to the current timestamp. This represents the timestamp when the callback is called.
|
||||
current_time: *const CVTimeStamp,
|
||||
// A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
|
||||
output_time: *const CVTimeStamp,
|
||||
// Unused
|
||||
flags_in: i64,
|
||||
// Unused
|
||||
flags_out: *mut i64,
|
||||
// A pointer to app-defined data.
|
||||
display_link_context: *mut c_void,
|
||||
) -> i32;
|
||||
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
#[allow(improper_ctypes)]
|
||||
extern "C" {
|
||||
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
||||
display_id: u32,
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetOutputCallback(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
display_id: u32,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
|
||||
pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
|
||||
}
|
||||
|
||||
impl DisplayLink {
|
||||
/// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
|
||||
pub unsafe fn new() -> Option<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
|
||||
pub unsafe fn on_display(display_id: u32) -> Option<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayLinkRef {
|
||||
/// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
|
||||
pub unsafe fn set_output_callback(
|
||||
&mut self,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) {
|
||||
assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
|
||||
pub unsafe fn set_current_display(&mut self, display_id: u32) {
|
||||
assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
|
||||
pub unsafe fn start(&mut self) {
|
||||
assert_eq!(CVDisplayLinkStart(self), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
|
||||
pub unsafe fn stop(&mut self) {
|
||||
assert_eq!(CVDisplayLinkStop(self), 0);
|
||||
}
|
||||
}
|
||||
}
|
358
crates/gpui/src/platform/mac_wgpu/events.rs
Normal file
358
crates/gpui/src/platform/mac_wgpu/events.rs
Normal file
|
@ -0,0 +1,358 @@
|
|||
use crate::{
|
||||
point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels,
|
||||
PlatformInput, 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);
|
||||
}
|
||||
|
||||
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 PlatformInput {
|
||||
pub(crate) 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 => {
|
||||
TouchPhase::Started
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => 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,
|
||||
touch_phase: 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::MouseMove(MouseMoveEvent {
|
||||
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::MouseMove(MouseMoveEvent {
|
||||
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(MouseExitEvent {
|
||||
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,
|
||||
ime_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
242
crates/gpui/src/platform/mac_wgpu/metal_atlas.rs
Normal file
242
crates/gpui/src/platform/mac_wgpu/metal_atlas.rs
Normal file
|
@ -0,0 +1,242 @@
|
|||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Point, Size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::FxHashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use metal::Device;
|
||||
use parking_lot::Mutex;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||
|
||||
impl MetalAtlas {
|
||||
pub(crate) fn new(device: Device) -> Self {
|
||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||
device: AssertSend(device),
|
||||
monochrome_textures: Default::default(),
|
||||
polychrome_textures: Default::default(),
|
||||
path_textures: Default::default(),
|
||||
tiles_by_key: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
|
||||
self.0.lock().texture(id).metal_texture.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn allocate(
|
||||
&self,
|
||||
size: Size<DevicePixels>,
|
||||
texture_kind: AtlasTextureKind,
|
||||
) -> AtlasTile {
|
||||
self.0.lock().allocate(size, texture_kind)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||
let mut lock = self.0.lock();
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.path_textures,
|
||||
};
|
||||
for texture in textures {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MetalAtlasState {
|
||||
device: AssertSend<Device>,
|
||||
monochrome_textures: Vec<MetalAtlasTexture>,
|
||||
polychrome_textures: Vec<MetalAtlasTexture>,
|
||||
path_textures: Vec<MetalAtlasTexture>,
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
impl PlatformAtlas for MetalAtlas {
|
||||
fn get_or_insert_with<'a>(
|
||||
&self,
|
||||
key: &AtlasKey,
|
||||
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
|
||||
) -> Result<AtlasTile> {
|
||||
let mut lock = self.0.lock();
|
||||
if let Some(tile) = lock.tiles_by_key.get(key) {
|
||||
Ok(tile.clone())
|
||||
} else {
|
||||
let (size, bytes) = build()?;
|
||||
let tile = lock.allocate(size, key.texture_kind());
|
||||
let texture = lock.texture(tile.texture_id);
|
||||
texture.upload(tile.bounds, &bytes);
|
||||
lock.tiles_by_key.insert(key.clone(), tile.clone());
|
||||
Ok(tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetalAtlasState {
|
||||
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.unwrap_or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
&mut self,
|
||||
min_size: Size<DevicePixels>,
|
||||
kind: AtlasTextureKind,
|
||||
) -> &mut MetalAtlasTexture {
|
||||
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
|
||||
width: DevicePixels(1024),
|
||||
height: DevicePixels(1024),
|
||||
};
|
||||
|
||||
let size = min_size.max(&DEFAULT_ATLAS_SIZE);
|
||||
let texture_descriptor = metal::TextureDescriptor::new();
|
||||
texture_descriptor.set_width(size.width.into());
|
||||
texture_descriptor.set_height(size.height.into());
|
||||
let pixel_format;
|
||||
let usage;
|
||||
match kind {
|
||||
AtlasTextureKind::Monochrome => {
|
||||
pixel_format = metal::MTLPixelFormat::A8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Polychrome => {
|
||||
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Path => {
|
||||
pixel_format = metal::MTLPixelFormat::R16Float;
|
||||
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
}
|
||||
texture_descriptor.set_pixel_format(pixel_format);
|
||||
texture_descriptor.set_usage(usage);
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
let textures = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
let atlas_texture = MetalAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: textures.len() as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
metal_texture: AssertSend(metal_texture),
|
||||
};
|
||||
textures.push(atlas_texture);
|
||||
textures.last_mut().unwrap()
|
||||
}
|
||||
|
||||
fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
|
||||
let textures = match id.kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
struct MetalAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
}
|
||||
|
||||
impl MetalAtlasTexture {
|
||||
fn clear(&mut self) {
|
||||
self.allocator.clear();
|
||||
}
|
||||
|
||||
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
|
||||
let allocation = self.allocator.allocate(size.into())?;
|
||||
let tile = AtlasTile {
|
||||
texture_id: self.id,
|
||||
tile_id: allocation.id.into(),
|
||||
bounds: Bounds {
|
||||
origin: allocation.rectangle.min.into(),
|
||||
size,
|
||||
},
|
||||
};
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
|
||||
let region = metal::MTLRegion::new_2d(
|
||||
bounds.origin.x.into(),
|
||||
bounds.origin.y.into(),
|
||||
bounds.size.width.into(),
|
||||
bounds.size.height.into(),
|
||||
);
|
||||
self.metal_texture.replace_region(
|
||||
region,
|
||||
0,
|
||||
bytes.as_ptr() as *const _,
|
||||
bounds.size.width.to_bytes(self.bytes_per_pixel()) as u64,
|
||||
);
|
||||
}
|
||||
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
use metal::MTLPixelFormat::*;
|
||||
match self.metal_texture.pixel_format() {
|
||||
A8Unorm | R8Unorm => 1,
|
||||
RGBA8Unorm | BGRA8Unorm => 4,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<DevicePixels>> for etagere::Size {
|
||||
fn from(size: Size<DevicePixels>) -> Self {
|
||||
etagere::Size::new(size.width.into(), size.height.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<etagere::Point> for Point<DevicePixels> {
|
||||
fn from(value: etagere::Point) -> Self {
|
||||
Point {
|
||||
x: DevicePixels::from(value.x),
|
||||
y: DevicePixels::from(value.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<etagere::Size> for Size<DevicePixels> {
|
||||
fn from(size: etagere::Size) -> Self {
|
||||
Size {
|
||||
width: DevicePixels::from(size.width),
|
||||
height: DevicePixels::from(size.height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<etagere::Rectangle> for Bounds<DevicePixels> {
|
||||
fn from(rectangle: etagere::Rectangle) -> Self {
|
||||
Bounds {
|
||||
origin: rectangle.min.into(),
|
||||
size: rectangle.size().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
struct AssertSend<T>(T);
|
||||
|
||||
unsafe impl<T> Send for AssertSend<T> {}
|
1068
crates/gpui/src/platform/mac_wgpu/metal_renderer.rs
Normal file
1068
crates/gpui/src/platform/mac_wgpu/metal_renderer.rs
Normal file
File diff suppressed because it is too large
Load diff
394
crates/gpui/src/platform/mac_wgpu/open_type.rs
Normal file
394
crates/gpui/src/platform/mac_wgpu/open_type.rs
Normal file
|
@ -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;
|
||||
}
|
1229
crates/gpui/src/platform/mac_wgpu/platform.rs
Normal file
1229
crates/gpui/src/platform/mac_wgpu/platform.rs
Normal file
File diff suppressed because it is too large
Load diff
655
crates/gpui/src/platform/mac_wgpu/shaders.metal
Normal file
655
crates/gpui/src/platform/mac_wgpu/shaders.metal
Normal file
|
@ -0,0 +1,655 @@
|
|||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla);
|
||||
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
constant Size_DevicePixels *viewport_size);
|
||||
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||
constant Size_DevicePixels *atlas_size);
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds);
|
||||
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||
Corners_ScaledPixels corner_radii);
|
||||
float gaussian(float x, float sigma);
|
||||
float2 erf(float2 x);
|
||||
float blur_along_x(float x, float y, float sigma, float corner,
|
||||
float2 half_size);
|
||||
float4 over(float4 below, float4 above);
|
||||
|
||||
struct QuadVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 background_color [[flat]];
|
||||
float4 border_color [[flat]];
|
||||
uint quad_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct QuadFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 background_color [[flat]];
|
||||
float4 border_color [[flat]];
|
||||
uint quad_id [[flat]];
|
||||
};
|
||||
|
||||
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 Size_DevicePixels *viewport_size
|
||||
[[buffer(QuadInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Quad quad = quads[quad_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, quad.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
|
||||
quad.content_mask.bounds);
|
||||
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,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 quad_fragment(QuadFragmentInput 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;
|
||||
// Blend the border on top of the background and then linearly interpolate
|
||||
// between the two as we slide inside the background.
|
||||
float4 blended_border = over(input.background_color, input.border_color);
|
||||
color = mix(blended_border, input.background_color,
|
||||
saturate(0.5 - inset_distance));
|
||||
}
|
||||
|
||||
return color * float4(1., 1., 1., saturate(0.5 - distance));
|
||||
}
|
||||
|
||||
struct ShadowVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint shadow_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct ShadowFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint shadow_id [[flat]];
|
||||
};
|
||||
|
||||
vertex ShadowVertexOutput shadow_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
|
||||
constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(ShadowInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Shadow shadow = shadows[shadow_id];
|
||||
|
||||
float margin = 3. * shadow.blur_radius;
|
||||
// Set the bounds of the shadow and adjust its size based on the shadow's
|
||||
// spread radius to achieve the spreading effect
|
||||
Bounds_ScaledPixels bounds = shadow.bounds;
|
||||
bounds.origin.x -= margin;
|
||||
bounds.origin.y -= margin;
|
||||
bounds.size.width += 2. * margin;
|
||||
bounds.size.height += 2. * margin;
|
||||
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, bounds, viewport_size);
|
||||
float4 clip_distance =
|
||||
distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
|
||||
float4 color = hsla_to_rgba(shadow.color);
|
||||
|
||||
return ShadowVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
shadow_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
|
||||
constant Shadow *shadows
|
||||
[[buffer(ShadowInputIndex_Shadows)]]) {
|
||||
Shadow shadow = shadows[input.shadow_id];
|
||||
|
||||
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
|
||||
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
|
||||
float2 half_size = size / 2.;
|
||||
float2 center = origin + half_size;
|
||||
float2 point = input.position.xy - center;
|
||||
float corner_radius;
|
||||
if (point.x < 0.) {
|
||||
if (point.y < 0.) {
|
||||
corner_radius = shadow.corner_radii.top_left;
|
||||
} else {
|
||||
corner_radius = shadow.corner_radii.bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (point.y < 0.) {
|
||||
corner_radius = shadow.corner_radii.top_right;
|
||||
} else {
|
||||
corner_radius = shadow.corner_radii.bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
// The signal is only non-zero in a limited range, so don't waste samples
|
||||
float low = point.y - half_size.y;
|
||||
float high = point.y + half_size.y;
|
||||
float start = clamp(-3. * shadow.blur_radius, low, high);
|
||||
float end = clamp(3. * shadow.blur_radius, low, high);
|
||||
|
||||
// Accumulate samples (we can get away with surprisingly few samples)
|
||||
float step = (end - start) / 4.;
|
||||
float y = start + step * 0.5;
|
||||
float alpha = 0.;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
|
||||
corner_radius, half_size) *
|
||||
gaussian(y, shadow.blur_radius) * step;
|
||||
y += step;
|
||||
}
|
||||
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
}
|
||||
|
||||
struct UnderlineVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint underline_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct UnderlineFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint underline_id [[flat]];
|
||||
};
|
||||
|
||||
vertex UnderlineVertexOutput underline_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
|
||||
constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(ShadowInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Underline underline = underlines[underline_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, underline.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
|
||||
underline.content_mask.bounds);
|
||||
float4 color = hsla_to_rgba(underline.color);
|
||||
return UnderlineVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
underline_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
|
||||
constant Underline *underlines
|
||||
[[buffer(UnderlineInputIndex_Underlines)]]) {
|
||||
Underline underline = underlines[input.underline_id];
|
||||
if (underline.wavy) {
|
||||
float half_thickness = underline.thickness * 0.5;
|
||||
float2 origin =
|
||||
float2(underline.bounds.origin.x, underline.bounds.origin.y);
|
||||
float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
|
||||
float2(0., 0.5);
|
||||
float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
|
||||
float amplitude = 1. / (2. * underline.thickness);
|
||||
float sine = sin(st.x * frequency) * amplitude;
|
||||
float dSine = cos(st.x * frequency) * amplitude * frequency;
|
||||
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
|
||||
float distance_in_pixels = distance * underline.bounds.size.height;
|
||||
float distance_from_top_border = distance_in_pixels - half_thickness;
|
||||
float distance_from_bottom_border = distance_in_pixels + half_thickness;
|
||||
float alpha = saturate(
|
||||
0.5 - max(-distance_from_bottom_border, distance_from_top_border));
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
} else {
|
||||
return input.color;
|
||||
}
|
||||
}
|
||||
|
||||
struct MonochromeSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct MonochromeSpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
};
|
||||
|
||||
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
MonochromeSprite sprite = sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return MonochromeSpriteVertexOutput{
|
||||
device_position,
|
||||
tile_position,
|
||||
color,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 monochrome_sprite_fragment(
|
||||
MonochromeSpriteFragmentInput input [[stage_in]],
|
||||
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float4 color = input.color;
|
||||
color.a *= sample.a;
|
||||
return color;
|
||||
}
|
||||
|
||||
struct PolychromeSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
uint sprite_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct PolychromeSpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
uint sprite_id [[flat]];
|
||||
};
|
||||
|
||||
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
PolychromeSprite sprite = sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
return PolychromeSpriteVertexOutput{
|
||||
device_position,
|
||||
tile_position,
|
||||
sprite_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 polychrome_sprite_fragment(
|
||||
PolychromeSpriteFragmentInput input [[stage_in]],
|
||||
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
PolychromeSprite sprite = sprites[input.sprite_id];
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float distance =
|
||||
quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
|
||||
|
||||
float4 color = sample;
|
||||
if (sprite.grayscale) {
|
||||
float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
||||
color.r = grayscale;
|
||||
color.g = grayscale;
|
||||
color.b = grayscale;
|
||||
}
|
||||
color.a *= saturate(0.5 - distance);
|
||||
return color;
|
||||
}
|
||||
|
||||
struct PathRasterizationVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
float clip_rect_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct PathRasterizationFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
};
|
||||
|
||||
vertex PathRasterizationVertexOutput path_rasterization_vertex(
|
||||
uint vertex_id [[vertex_id]],
|
||||
constant PathVertex_ScaledPixels *vertices
|
||||
[[buffer(PathRasterizationInputIndex_Vertices)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
|
||||
PathVertex_ScaledPixels v = vertices[vertex_id];
|
||||
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
|
||||
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
|
||||
return PathRasterizationVertexOutput{
|
||||
float4(vertex_position / viewport_size * float2(2., -2.) +
|
||||
float2(-1., 1.),
|
||||
0., 1.),
|
||||
float2(v.st_position.x, v.st_position.y),
|
||||
{v.xy_position.x - v.content_mask.bounds.origin.x,
|
||||
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
|
||||
v.xy_position.x,
|
||||
v.xy_position.y - v.content_mask.bounds.origin.y,
|
||||
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
|
||||
v.xy_position.y}};
|
||||
}
|
||||
|
||||
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
|
||||
[[stage_in]]) {
|
||||
float2 dx = dfdx(input.st_position);
|
||||
float2 dy = dfdy(input.st_position);
|
||||
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
|
||||
(2. * input.st_position.x) * dy.x - dy.y);
|
||||
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
|
||||
float distance = f / length(gradient);
|
||||
float alpha = saturate(0.5 - distance);
|
||||
return float4(alpha, 0., 0., 1.);
|
||||
}
|
||||
|
||||
struct PathSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
};
|
||||
|
||||
vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
PathSprite sprite = sprites[sprite_id];
|
||||
// Don't apply content mask because it was already accounted for when
|
||||
// rasterizing the path.
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return PathSpriteVertexOutput{device_position, tile_position, color};
|
||||
}
|
||||
|
||||
fragment float4 path_sprite_fragment(
|
||||
PathSpriteVertexOutput input [[stage_in]],
|
||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
||||
float4 color = input.color;
|
||||
color.a *= mask;
|
||||
return color;
|
||||
}
|
||||
|
||||
struct SurfaceVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 texture_position;
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct SurfaceFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 texture_position;
|
||||
};
|
||||
|
||||
vertex SurfaceVertexOutput surface_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
|
||||
constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SurfaceInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *texture_size
|
||||
[[buffer(SurfaceInputIndex_TextureSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
SurfaceBounds surface = surfaces[surface_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, surface.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
|
||||
surface.content_mask.bounds);
|
||||
// We are going to copy the whole texture, so the texture position corresponds
|
||||
// to the current vertex of the unit triangle.
|
||||
float2 texture_position = unit_vertex;
|
||||
return SurfaceVertexOutput{
|
||||
device_position,
|
||||
texture_position,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
|
||||
texture2d<float> y_texture
|
||||
[[texture(SurfaceInputIndex_YTexture)]],
|
||||
texture2d<float> cb_cr_texture
|
||||
[[texture(SurfaceInputIndex_CbCrTexture)]]) {
|
||||
constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
|
||||
const float4x4 ycbcrToRGBTransform =
|
||||
float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
|
||||
float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
|
||||
float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
|
||||
float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
|
||||
float4 ycbcr = float4(
|
||||
y_texture.sample(texture_sampler, input.texture_position).r,
|
||||
cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
|
||||
|
||||
return ycbcrToRGBTransform * ycbcr;
|
||||
}
|
||||
|
||||
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 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
constant Size_DevicePixels *input_viewport_size) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
float2 viewport_size = float2((float)input_viewport_size->width,
|
||||
(float)input_viewport_size->height);
|
||||
float2 device_position =
|
||||
position / viewport_size * float2(2., -2.) + float2(-1., 1.);
|
||||
return float4(device_position, 0., 1.);
|
||||
}
|
||||
|
||||
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||
constant Size_DevicePixels *atlas_size) {
|
||||
float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
|
||||
float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
|
||||
return (tile_origin + unit_vertex * tile_size) /
|
||||
float2((float)atlas_size->width, (float)atlas_size->height);
|
||||
}
|
||||
|
||||
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||
Corners_ScaledPixels 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;
|
||||
}
|
||||
|
||||
// A standard gaussian function, used for weighting samples
|
||||
float gaussian(float x, float sigma) {
|
||||
return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
|
||||
}
|
||||
|
||||
// This approximates the error function, needed for the gaussian integral
|
||||
float2 erf(float2 x) {
|
||||
float2 s = sign(x);
|
||||
float2 a = abs(x);
|
||||
x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
|
||||
x *= x;
|
||||
return s - s / (x * x);
|
||||
}
|
||||
|
||||
float blur_along_x(float x, float y, float sigma, float corner,
|
||||
float2 half_size) {
|
||||
float delta = min(half_size.y - corner - abs(y), 0.);
|
||||
float curved =
|
||||
half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
|
||||
float2 integral =
|
||||
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
|
||||
return integral.y - integral.x;
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
return float4(position.x - clip_bounds.origin.x,
|
||||
clip_bounds.origin.x + clip_bounds.size.width - position.x,
|
||||
position.y - clip_bounds.origin.y,
|
||||
clip_bounds.origin.y + clip_bounds.size.height - position.y);
|
||||
}
|
||||
|
||||
float4 over(float4 below, float4 above) {
|
||||
float4 result;
|
||||
float alpha = above.a + below.a * (1.0 - above.a);
|
||||
result.rgb =
|
||||
(above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
|
||||
result.a = alpha;
|
||||
return result;
|
||||
}
|
139
crates/gpui/src/platform/mac_wgpu/shaders/quad.wgsl
Normal file
139
crates/gpui/src/platform/mac_wgpu/shaders/quad.wgsl
Normal file
|
@ -0,0 +1,139 @@
|
|||
struct QuadVertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) @interpolate(flat) background_color: vec4<f32>,
|
||||
@location(1) @interpolate(flat) border_color: vec4<f32>,
|
||||
@location(2) @interpolate(flat) quad_id: u32,
|
||||
@location(3) clip_distance: vec4<f32>,
|
||||
};
|
||||
|
||||
/*
|
||||
struct QuadFragmentInput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) @interpolate(flat) background_color: vec4<f32>,
|
||||
@location(1) @interpolate(flat) border_color: vec4<f32>,
|
||||
@location(2) @interpolate(flat) quad_id: u32,
|
||||
@location(3) clip_distance: vec4<f32>,
|
||||
};
|
||||
*/
|
||||
|
||||
struct ViewId {
|
||||
low_bits: u32,
|
||||
high_bits: u32,
|
||||
};
|
||||
|
||||
alias LayerId = u32;
|
||||
|
||||
alias DrawOrder = u32;
|
||||
|
||||
alias ScaledPixels = f32;
|
||||
alias DevicePixels = i32;
|
||||
|
||||
struct Point_ScaledPixels {
|
||||
x: ScaledPixels,
|
||||
y: ScaledPixels,
|
||||
};
|
||||
|
||||
struct Size_ScaledPixels {
|
||||
width: ScaledPixels,
|
||||
height: ScaledPixels,
|
||||
};
|
||||
|
||||
struct Size_DevicePixels {
|
||||
width: DevicePixels,
|
||||
height: DevicePixels,
|
||||
};
|
||||
|
||||
struct Bounds_ScaledPixels {
|
||||
origin: Point_ScaledPixels,
|
||||
size: Size_ScaledPixels,
|
||||
};
|
||||
|
||||
struct ContentMask_ScaledPixels {
|
||||
bounds: Bounds_ScaledPixels,
|
||||
};
|
||||
|
||||
struct Corners_ScaledPixels {
|
||||
top_left: ScaledPixels,
|
||||
top_right: ScaledPixels,
|
||||
bottom_right: ScaledPixels,
|
||||
bottom_left: ScaledPixels,
|
||||
};
|
||||
|
||||
struct Edges_ScaledPixels {
|
||||
top: ScaledPixels,
|
||||
right: ScaledPixels,
|
||||
bottom: ScaledPixels,
|
||||
left: ScaledPixels,
|
||||
}
|
||||
|
||||
struct Hsla {
|
||||
h: f32,
|
||||
s: f32,
|
||||
l: f32,
|
||||
a: f32,
|
||||
};
|
||||
|
||||
struct Quad {
|
||||
view_id: ViewId,
|
||||
layer_id: LayerId,
|
||||
order: DrawOrder,
|
||||
bounds: Bounds_ScaledPixels,
|
||||
content_mask: ContentMask_ScaledPixels,
|
||||
background: Hsla,
|
||||
border_color: Hsla,
|
||||
corner_radii: Corners_ScaledPixels,
|
||||
border_widths: Edges_ScaledPixels,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<storage> unit_vertices: array<vec2<f32>>;
|
||||
|
||||
@group(0) @binding(1)
|
||||
var<storage> quads: array<Quad>;
|
||||
|
||||
@group(0) @binding(2)
|
||||
var<storage> viewport_size: Size_DevicePixels;
|
||||
|
||||
|
||||
@vertex
|
||||
fn vertex(
|
||||
@builtin(vertex_index) unit_vertex_id: u32,
|
||||
@builtin(instance_index) quad_id: u32,
|
||||
) -> QuadVertexOutput {
|
||||
let unit_vertex = unit_vertices[unit_vertex_id];
|
||||
let quad = quads[quad_id];
|
||||
let device_position = to_device_position(unit_vertex, quad.bounds);
|
||||
/*
|
||||
float4 clip_distance = distance_from_clip_rect(
|
||||
unit_vertex,
|
||||
quad.bounds,
|
||||
quad.content_mask.bounds
|
||||
);
|
||||
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,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}
|
||||
};
|
||||
*/
|
||||
|
||||
return QuadVertexOutput();
|
||||
}
|
||||
|
||||
fn to_device_position(
|
||||
unit_vertex: vec2<f32>,
|
||||
bounds: Bounds_ScaledPixels,
|
||||
) -> vec4<f32> {
|
||||
let position = unit_vertex
|
||||
* vec2<f32>(bounds.size.width, bounds.size.height)
|
||||
+ vec2<f32>(bounds.origin.x, bounds.origin.y);
|
||||
|
||||
let viewport_size = vec2<f32>(f32(viewport_size.width), f32(viewport_size.height));
|
||||
let device_position = position / viewport_size * vec2<f32>(2., -2.) + vec2<f32>(-1., 1.);
|
||||
|
||||
return vec4<f32>(device_position, 0., 1.);
|
||||
}
|
||||
|
392
crates/gpui/src/platform/mac_wgpu/status_item.rs
Normal file
392
crates/gpui/src/platform/mac_wgpu/status_item.rs
Normal file
|
@ -0,0 +1,392 @@
|
|||
use crate::{
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
platform::{
|
||||
self,
|
||||
mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer},
|
||||
Event, FontSystem, WindowBounds,
|
||||
},
|
||||
Scene,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
|
||||
base::{id, nil, YES},
|
||||
foundation::{NSPoint, NSRect, NSSize},
|
||||
};
|
||||
use ctor::ctor;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
rc::StrongPtr,
|
||||
runtime::{Class, Object, Protocol, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::c_void,
|
||||
ptr,
|
||||
rc::{Rc, Weak},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::screen::Screen;
|
||||
|
||||
static mut VIEW_CLASS: *const Class = ptr::null();
|
||||
const STATE_IVAR: &str = "state";
|
||||
|
||||
#[ctor]
|
||||
unsafe fn build_classes() {
|
||||
VIEW_CLASS = {
|
||||
let mut decl = ClassDecl::new("GPUIStatusItemView", class!(NSView)).unwrap();
|
||||
decl.add_ivar::<*mut c_void>(STATE_IVAR);
|
||||
|
||||
decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
|
||||
|
||||
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!(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!(makeBackingLayer),
|
||||
make_backing_layer as extern "C" fn(&Object, Sel) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(viewDidChangeEffectiveAppearance),
|
||||
view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
|
||||
);
|
||||
|
||||
decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
|
||||
decl.add_method(
|
||||
sel!(displayLayer:),
|
||||
display_layer as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.register()
|
||||
};
|
||||
}
|
||||
|
||||
pub struct StatusItem(Rc<RefCell<StatusItemState>>);
|
||||
|
||||
struct StatusItemState {
|
||||
native_item: StrongPtr,
|
||||
native_view: StrongPtr,
|
||||
renderer: Renderer,
|
||||
scene: Option<Scene>,
|
||||
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
|
||||
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
impl StatusItem {
|
||||
pub fn add(fonts: Arc<dyn FontSystem>) -> Self {
|
||||
unsafe {
|
||||
let renderer = Renderer::new(false, fonts);
|
||||
let status_bar = NSStatusBar::systemStatusBar(nil);
|
||||
let native_item =
|
||||
StrongPtr::retain(status_bar.statusItemWithLength_(NSSquareStatusItemLength));
|
||||
|
||||
let button = native_item.button();
|
||||
let _: () = msg_send![button, setHidden: YES];
|
||||
|
||||
let native_view = msg_send![VIEW_CLASS, alloc];
|
||||
let state = Rc::new(RefCell::new(StatusItemState {
|
||||
native_item,
|
||||
native_view: StrongPtr::new(native_view),
|
||||
renderer,
|
||||
scene: None,
|
||||
event_callback: None,
|
||||
appearance_changed_callback: None,
|
||||
}));
|
||||
|
||||
let parent_view = button.superview().superview();
|
||||
NSView::initWithFrame_(
|
||||
native_view,
|
||||
NSRect::new(NSPoint::new(0., 0.), NSView::frame(parent_view).size),
|
||||
);
|
||||
(*native_view).set_ivar(
|
||||
STATE_IVAR,
|
||||
Weak::into_raw(Rc::downgrade(&state)) as *const c_void,
|
||||
);
|
||||
native_view.setWantsBestResolutionOpenGLSurface_(YES);
|
||||
native_view.setWantsLayer(YES);
|
||||
let _: () = msg_send![
|
||||
native_view,
|
||||
setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
|
||||
];
|
||||
|
||||
parent_view.addSubview_(native_view);
|
||||
|
||||
{
|
||||
let state = state.borrow();
|
||||
let layer = state.renderer.layer();
|
||||
let scale_factor = state.scale_factor();
|
||||
let size = state.content_size() * scale_factor;
|
||||
layer.set_contents_scale(scale_factor.into());
|
||||
layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
|
||||
}
|
||||
|
||||
Self(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Window for StatusItem {
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
self.0.borrow().bounds()
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Vector2F {
|
||||
self.0.borrow().content_size()
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
self.0.borrow().scale_factor()
|
||||
}
|
||||
|
||||
fn titlebar_height(&self) -> f32 {
|
||||
0.
|
||||
}
|
||||
|
||||
fn appearance(&self) -> platform::Appearance {
|
||||
unsafe {
|
||||
let appearance: id =
|
||||
msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
|
||||
platform::Appearance::from_native(appearance)
|
||||
}
|
||||
}
|
||||
|
||||
fn screen(&self) -> Rc<dyn platform::Screen> {
|
||||
unsafe {
|
||||
Rc::new(Screen {
|
||||
native_screen: self.0.borrow().native_window().screen(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_position(&self) -> Vector2F {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, _: Box<dyn platform::InputHandler>) {}
|
||||
|
||||
fn prompt(
|
||||
&self,
|
||||
_: crate::platform::PromptLevel,
|
||||
_: &str,
|
||||
_: &[&str],
|
||||
) -> postage::oneshot::Receiver<usize> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn activate(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_title(&mut self, _: &str) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, _: bool) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn show_character_palette(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn minimize(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn zoom(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn present_scene(&mut self, scene: Scene) {
|
||||
self.0.borrow_mut().scene = Some(scene);
|
||||
unsafe {
|
||||
let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_full_screen(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn on_event(&mut self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
|
||||
self.0.borrow_mut().event_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
|
||||
|
||||
fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
|
||||
|
||||
fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
|
||||
|
||||
fn on_moved(&mut self, _: Box<dyn FnMut()>) {}
|
||||
|
||||
fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
|
||||
|
||||
fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
|
||||
|
||||
fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
|
||||
self.0.borrow_mut().appearance_changed_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn is_topmost_for_position(&self, _: Vector2F) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusItemState {
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
unsafe {
|
||||
let window: id = self.native_window();
|
||||
let screen_frame = window.screen().visibleFrame();
|
||||
let window_frame = NSWindow::frame(window);
|
||||
let origin = vec2f(
|
||||
window_frame.origin.x as f32,
|
||||
(window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
|
||||
as f32,
|
||||
);
|
||||
let size = vec2f(
|
||||
window_frame.size.width as f32,
|
||||
window_frame.size.height as f32,
|
||||
);
|
||||
WindowBounds::Fixed(RectF::new(origin, size))
|
||||
}
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Vector2F {
|
||||
unsafe {
|
||||
let NSSize { width, height, .. } =
|
||||
NSView::frame(self.native_item.button().superview().superview()).size;
|
||||
vec2f(width as f32, height as f32)
|
||||
}
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
unsafe {
|
||||
let window: id = msg_send![self.native_item.button(), window];
|
||||
NSScreen::backingScaleFactor(window.screen()) as f32
|
||||
}
|
||||
}
|
||||
|
||||
pub fn native_window(&self) -> id {
|
||||
unsafe { msg_send![self.native_item.button(), window] }
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn dealloc_view(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
drop_state(this);
|
||||
|
||||
let _: () = msg_send![super(this, class!(NSView)), dealloc];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||
unsafe {
|
||||
if let Some(state) = get_state(this).upgrade() {
|
||||
let mut state_borrow = state.as_ref().borrow_mut();
|
||||
if let Some(event) =
|
||||
Event::from_native(native_event, Some(state_borrow.content_size().y()))
|
||||
{
|
||||
if let Some(mut callback) = state_borrow.event_callback.take() {
|
||||
drop(state_borrow);
|
||||
callback(event);
|
||||
state.borrow_mut().event_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
|
||||
if let Some(state) = unsafe { get_state(this).upgrade() } {
|
||||
let state = state.borrow();
|
||||
state.renderer.layer().as_ptr() as id
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
if let Some(state) = get_state(this).upgrade() {
|
||||
let mut state = state.borrow_mut();
|
||||
if let Some(scene) = state.scene.take() {
|
||||
state.renderer.render(&scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
if let Some(state) = get_state(this).upgrade() {
|
||||
let mut state_borrow = state.as_ref().borrow_mut();
|
||||
if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
|
||||
drop(state_borrow);
|
||||
callback();
|
||||
state.borrow_mut().appearance_changed_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
|
||||
let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
|
||||
let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
|
||||
let weak2 = weak1.clone();
|
||||
let _ = Weak::into_raw(weak1);
|
||||
weak2
|
||||
}
|
||||
|
||||
unsafe fn drop_state(object: &Object) {
|
||||
let raw: *const c_void = *object.get_ivar(STATE_IVAR);
|
||||
Weak::from_raw(raw as *const RefCell<StatusItemState>);
|
||||
}
|
745
crates/gpui/src/platform/mac_wgpu/text_system.rs
Normal file
745
crates/gpui/src/platform/mac_wgpu/text_system.rs
Normal file
|
@ -0,0 +1,745 @@
|
|||
use crate::{
|
||||
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun,
|
||||
FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
|
||||
RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use cocoa::appkit::{CGFloat, CGPoint};
|
||||
use collections::{BTreeSet, HashMap};
|
||||
use core_foundation::{
|
||||
array::CFIndex,
|
||||
attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
|
||||
base::{CFRange, TCFType},
|
||||
number::CFNumber,
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::{
|
||||
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
|
||||
color_space::CGColorSpace,
|
||||
context::CGContext,
|
||||
};
|
||||
use core_text::{
|
||||
font::CTFont,
|
||||
font_descriptor::{
|
||||
kCTFontSlantTrait, kCTFontSymbolicTrait, kCTFontWeightTrait, kCTFontWidthTrait,
|
||||
},
|
||||
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::{borrow::Cow, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
|
||||
|
||||
use super::open_type;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kCGImageAlphaOnly: u32 = 7;
|
||||
|
||||
pub(crate) 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(crate) 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: Vec<Cow<'static, [u8]>>) -> Result<()> {
|
||||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
fn all_font_names(&self) -> Vec<String> {
|
||||
let collection = core_text::font_collection::create_for_all_families();
|
||||
let Some(descriptors) = collection.get_descriptors() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut names = BTreeSet::new();
|
||||
for descriptor in descriptors.into_iter() {
|
||||
names.extend(lenient_font_attributes::family_name(&descriptor));
|
||||
}
|
||||
if let Ok(fonts_in_memory) = self.0.read().memory_source.all_families() {
|
||||
names.extend(fonts_in_memory);
|
||||
}
|
||||
names.into_iter().collect()
|
||||
}
|
||||
|
||||
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(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let font_id = candidates[ix];
|
||||
lock.font_selections.insert(font.clone(), font_id);
|
||||
Ok(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
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.0)?
|
||||
.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 glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
self.0.read().raster_bounds(params)
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
glyph_id: &RenderGlyphParams,
|
||||
raster_bounds: Bounds<DevicePixels>,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
self.0.read().rasterize_glyph(glyph_id, raster_bounds)
|
||||
}
|
||||
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> 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: Vec<Cow<'static, [u8]>>) -> Result<()> {
|
||||
let fonts = fonts
|
||||
.into_iter()
|
||||
.map(|bytes| match bytes {
|
||||
Cow::Borrowed(embedded_font) => {
|
||||
let data_provider = unsafe {
|
||||
core_graphics::data_provider::CGDataProvider::from_slice(embedded_font)
|
||||
};
|
||||
let font = core_graphics::font::CGFont::from_data_provider(data_provider)
|
||||
.map_err(|_| anyhow!("Could not load an embedded font."))?;
|
||||
let font = font_kit::loaders::core_text::Font::from_core_graphics_font(font);
|
||||
Ok(Handle::from_native(&font))
|
||||
}
|
||||
Cow::Owned(bytes) => Ok(Handle::from_memory(Arc::new(bytes), 0)),
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
self.memory_source.add_fonts(fonts.into_iter())?;
|
||||
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 Some(_) = font.glyph_for_char('m') else {
|
||||
continue;
|
||||
};
|
||||
// We've seen a number of panics in production caused by calling font.properties()
|
||||
// which unwraps a downcast to CFNumber. This is an attempt to avoid the panic,
|
||||
// and to try and identify the incalcitrant font.
|
||||
let traits = font.native_font().all_traits();
|
||||
if unsafe {
|
||||
!(traits
|
||||
.get(kCTFontSymbolicTrait)
|
||||
.downcast::<CFNumber>()
|
||||
.is_some()
|
||||
&& traits
|
||||
.get(kCTFontWidthTrait)
|
||||
.downcast::<CFNumber>()
|
||||
.is_some()
|
||||
&& traits
|
||||
.get(kCTFontWeightTrait)
|
||||
.downcast::<CFNumber>()
|
||||
.is_some()
|
||||
&& traits
|
||||
.get(kCTFontSlantTrait)
|
||||
.downcast::<CFNumber>()
|
||||
.is_some())
|
||||
} {
|
||||
log::error!(
|
||||
"Failed to read traits for font {:?}",
|
||||
font.postscript_name().unwrap()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
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.0)?.into())
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
self.fonts[font_id.0].glyph_for_char(ch).map(GlyphId)
|
||||
}
|
||||
|
||||
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 raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let font = &self.fonts[params.font_id.0];
|
||||
let scale = Transform2F::from_scale(params.scale_factor);
|
||||
Ok(font
|
||||
.raster_bounds(
|
||||
params.glyph_id.0,
|
||||
params.font_size.into(),
|
||||
scale,
|
||||
HintingOptions::None,
|
||||
font_kit::canvas::RasterizationOptions::GrayscaleAa,
|
||||
)?
|
||||
.into())
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
params: &RenderGlyphParams,
|
||||
glyph_bounds: Bounds<DevicePixels>,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
|
||||
Err(anyhow!("glyph bounds are empty"))
|
||||
} else {
|
||||
// Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
|
||||
let mut bitmap_size = glyph_bounds.size;
|
||||
if params.subpixel_variant.x > 0 {
|
||||
bitmap_size.width += DevicePixels(1);
|
||||
}
|
||||
if params.subpixel_variant.y > 0 {
|
||||
bitmap_size.height += DevicePixels(1);
|
||||
}
|
||||
let bitmap_size = bitmap_size;
|
||||
|
||||
let mut bytes;
|
||||
let cx;
|
||||
if params.is_emoji {
|
||||
bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
bitmap_size.width.0 as usize,
|
||||
bitmap_size.height.0 as usize,
|
||||
8,
|
||||
bitmap_size.width.0 as usize * 4,
|
||||
&CGColorSpace::create_device_rgb(),
|
||||
kCGImageAlphaPremultipliedLast,
|
||||
);
|
||||
} else {
|
||||
bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
bitmap_size.width.0 as usize,
|
||||
bitmap_size.height.0 as usize,
|
||||
8,
|
||||
bitmap_size.width.0 as usize,
|
||||
&CGColorSpace::create_device_gray(),
|
||||
kCGImageAlphaOnly,
|
||||
);
|
||||
}
|
||||
|
||||
// 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.0 as CGFloat,
|
||||
(glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
|
||||
);
|
||||
cx.scale(
|
||||
params.scale_factor as CGFloat,
|
||||
params.scale_factor as CGFloat,
|
||||
);
|
||||
|
||||
let subpixel_shift = params
|
||||
.subpixel_variant
|
||||
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
|
||||
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);
|
||||
self.fonts[params.font_id.0]
|
||||
.native_font()
|
||||
.clone_with_font_size(f32::from(params.font_size) as CGFloat)
|
||||
.draw_glyphs(
|
||||
&[params.glyph_id.0 as CGGlyph],
|
||||
&[CGPoint::new(
|
||||
(subpixel_shift.x / params.scale_factor) as CGFloat,
|
||||
(subpixel_shift.y / params.scale_factor) as CGFloat,
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
|
||||
if params.is_emoji {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((bitmap_size, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> 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 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[run.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 = SmallVec::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(ShapedGlyph {
|
||||
id: GlyphId(*glyph_id as u32),
|
||||
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(ShapedRun { font_id, glyphs })
|
||||
}
|
||||
|
||||
let typographic_bounds = line.get_typographic_bounds();
|
||||
LineLayout {
|
||||
runs,
|
||||
font_size,
|
||||
width: typographic_bounds.width.into(),
|
||||
ascent: typographic_bounds.ascent.into(),
|
||||
descent: typographic_bounds.descent.into(),
|
||||
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);
|
||||
}
|
||||
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(crate) struct __CFTypesetter(c_void);
|
||||
|
||||
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<DevicePixels> {
|
||||
fn from(rect: RectI) -> Self {
|
||||
Bounds {
|
||||
origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
|
||||
size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector2I> for Size<DevicePixels> {
|
||||
fn from(value: Vector2I) -> Self {
|
||||
size(value.x().into(), value.y().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RectI> for Bounds<i32> {
|
||||
fn from(rect: RectI) -> Self {
|
||||
Bounds {
|
||||
origin: point(rect.origin_x(), rect.origin_y()),
|
||||
size: size(rect.width(), rect.height()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some fonts may have no attributest despite `core_text` requiring them (and panicking).
|
||||
// This is the same version as `core_text` has without `expect` calls.
|
||||
mod lenient_font_attributes {
|
||||
use core_foundation::{
|
||||
base::{CFRetain, CFType, TCFType},
|
||||
string::{CFString, CFStringRef},
|
||||
};
|
||||
use core_text::font_descriptor::{
|
||||
kCTFontFamilyNameAttribute, CTFontDescriptor, CTFontDescriptorCopyAttribute,
|
||||
};
|
||||
|
||||
pub fn family_name(descriptor: &CTFontDescriptor) -> Option<String> {
|
||||
unsafe { get_string_attribute(descriptor, kCTFontFamilyNameAttribute) }
|
||||
}
|
||||
|
||||
fn get_string_attribute(
|
||||
descriptor: &CTFontDescriptor,
|
||||
attribute: CFStringRef,
|
||||
) -> Option<String> {
|
||||
unsafe {
|
||||
let value = CTFontDescriptorCopyAttribute(descriptor.as_concrete_TypeRef(), attribute);
|
||||
if value.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = CFType::wrap_under_create_rule(value);
|
||||
assert!(value.instance_of::<CFString>());
|
||||
let s = wrap_under_get_rule(value.as_CFTypeRef() as CFStringRef);
|
||||
Some(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn wrap_under_get_rule(reference: CFStringRef) -> CFString {
|
||||
assert!(!reference.is_null(), "Attempted to create a NULL object.");
|
||||
let reference = CFRetain(reference as *const ::std::os::raw::c_void) as CFStringRef;
|
||||
TCFType::wrap_under_create_rule(reference)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{font, px, FontRun, GlyphId, MacTextSystem, PlatformTextSystem};
|
||||
|
||||
#[test]
|
||||
fn test_wrap_line() {
|
||||
let fonts = MacTextSystem::new();
|
||||
let font_id = fonts.font_id(&font("Helvetica")).unwrap();
|
||||
|
||||
let line = "one two three four five\n";
|
||||
let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(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, px(16.), px(64.0));
|
||||
assert_eq!(
|
||||
wrap_boundaries,
|
||||
&["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_line_bom_char() {
|
||||
let fonts = MacTextSystem::new();
|
||||
let font_id = fonts.font_id(&font("Helvetica")).unwrap();
|
||||
let line = "\u{feff}";
|
||||
let mut style = FontRun {
|
||||
font_id,
|
||||
len: line.len(),
|
||||
};
|
||||
|
||||
let layout = fonts.layout_line(line, px(16.), &[style]);
|
||||
assert_eq!(layout.len, line.len());
|
||||
assert!(layout.runs.is_empty());
|
||||
|
||||
let line = "a\u{feff}b";
|
||||
style.len = line.len();
|
||||
let layout = fonts.layout_line(line, px(16.), &[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, GlyphId(68u32)); // a
|
||||
// There's no glyph for \u{feff}
|
||||
assert_eq!(layout.runs[0].glyphs[1].id, GlyphId(69u32)); // b
|
||||
}
|
||||
}
|
209
crates/gpui/src/platform/mac_wgpu/wgpu_atlas.rs
Normal file
209
crates/gpui/src/platform/mac_wgpu/wgpu_atlas.rs
Normal file
|
@ -0,0 +1,209 @@
|
|||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::FxHashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use parking_lot::Mutex;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub(crate) struct WgpuAtlas(Mutex<WgpuAtlasState>);
|
||||
|
||||
impl WgpuAtlas {
|
||||
pub(crate) fn new() -> Self {
|
||||
WgpuAtlas(Mutex::new(WgpuAtlasState {
|
||||
// device: AssertSend(device),
|
||||
monochrome_textures: Default::default(),
|
||||
polychrome_textures: Default::default(),
|
||||
path_textures: Default::default(),
|
||||
tiles_by_key: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
// pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
|
||||
// self.0.lock().texture(id).metal_texture.clone()
|
||||
// }
|
||||
|
||||
pub(crate) fn allocate(
|
||||
&self,
|
||||
size: Size<DevicePixels>,
|
||||
texture_kind: AtlasTextureKind,
|
||||
) -> AtlasTile {
|
||||
self.0.lock().allocate(size, texture_kind)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||
let mut lock = self.0.lock();
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.path_textures,
|
||||
};
|
||||
for texture in textures {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WgpuAtlasState {
|
||||
// device: AssertSend<Device>,
|
||||
monochrome_textures: Vec<WgpuAtlasTexture>,
|
||||
polychrome_textures: Vec<WgpuAtlasTexture>,
|
||||
path_textures: Vec<WgpuAtlasTexture>,
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
impl PlatformAtlas for WgpuAtlas {
|
||||
fn get_or_insert_with<'a>(
|
||||
&self,
|
||||
key: &AtlasKey,
|
||||
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
|
||||
) -> Result<AtlasTile> {
|
||||
let mut lock = self.0.lock();
|
||||
if let Some(tile) = lock.tiles_by_key.get(key) {
|
||||
Ok(tile.clone())
|
||||
} else {
|
||||
let (size, bytes) = build()?;
|
||||
let tile = lock.allocate(size, key.texture_kind());
|
||||
let texture = lock.texture(tile.texture_id);
|
||||
texture.upload(tile.bounds, &bytes);
|
||||
lock.tiles_by_key.insert(key.clone(), tile.clone());
|
||||
Ok(tile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WgpuAtlasState {
|
||||
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.unwrap_or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
&mut self,
|
||||
min_size: Size<DevicePixels>,
|
||||
kind: AtlasTextureKind,
|
||||
) -> &mut WgpuAtlasTexture {
|
||||
todo!();
|
||||
// const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
|
||||
// width: DevicePixels(1024),
|
||||
// height: DevicePixels(1024),
|
||||
// };
|
||||
|
||||
// let size = min_size.max(&DEFAULT_ATLAS_SIZE);
|
||||
// let texture_descriptor = metal::TextureDescriptor::new();
|
||||
// texture_descriptor.set_width(size.width.into());
|
||||
// texture_descriptor.set_height(size.height.into());
|
||||
// let pixel_format;
|
||||
// let usage;
|
||||
// match kind {
|
||||
// AtlasTextureKind::Monochrome => {
|
||||
// pixel_format = metal::MTLPixelFormat::A8Unorm;
|
||||
// usage = metal::MTLTextureUsage::ShaderRead;
|
||||
// }
|
||||
// AtlasTextureKind::Polychrome => {
|
||||
// pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
// usage = metal::MTLTextureUsage::ShaderRead;
|
||||
// }
|
||||
// AtlasTextureKind::Path => {
|
||||
// pixel_format = metal::MTLPixelFormat::R16Float;
|
||||
// usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
|
||||
// }
|
||||
// }
|
||||
// texture_descriptor.set_pixel_format(pixel_format);
|
||||
// texture_descriptor.set_usage(usage);
|
||||
// let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
// let textures = match kind {
|
||||
// AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
// AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
// AtlasTextureKind::Path => &mut self.path_textures,
|
||||
// };
|
||||
// let atlas_texture = WgpuAtlasTexture {
|
||||
// id: AtlasTextureId {
|
||||
// index: textures.len() as u32,
|
||||
// kind,
|
||||
// },
|
||||
// allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
// metal_texture: AssertSend(metal_texture),
|
||||
// };
|
||||
// textures.push(atlas_texture);
|
||||
// textures.last_mut().unwrap()
|
||||
}
|
||||
|
||||
fn texture(&self, id: AtlasTextureId) -> &WgpuAtlasTexture {
|
||||
let textures = match id.kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
struct WgpuAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
}
|
||||
|
||||
impl WgpuAtlasTexture {
|
||||
fn clear(&mut self) {
|
||||
self.allocator.clear();
|
||||
}
|
||||
|
||||
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
|
||||
let allocation = self.allocator.allocate(size.into())?;
|
||||
let tile = AtlasTile {
|
||||
texture_id: self.id,
|
||||
tile_id: allocation.id.into(),
|
||||
bounds: Bounds {
|
||||
origin: allocation.rectangle.min.into(),
|
||||
size,
|
||||
},
|
||||
};
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
|
||||
let region = metal::MTLRegion::new_2d(
|
||||
bounds.origin.x.into(),
|
||||
bounds.origin.y.into(),
|
||||
bounds.size.width.into(),
|
||||
bounds.size.height.into(),
|
||||
);
|
||||
self.metal_texture.replace_region(
|
||||
region,
|
||||
0,
|
||||
bytes.as_ptr() as *const _,
|
||||
bounds.size.width.to_bytes(self.bytes_per_pixel()) as u64,
|
||||
);
|
||||
}
|
||||
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
use metal::MTLPixelFormat::*;
|
||||
match self.metal_texture.pixel_format() {
|
||||
A8Unorm | R8Unorm => 1,
|
||||
RGBA8Unorm | BGRA8Unorm => 4,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
struct AssertSend<T>(T);
|
||||
|
||||
unsafe impl<T> Send for AssertSend<T> {}
|
65
crates/gpui/src/platform/mac_wgpu/wgpu_renderer.rs
Normal file
65
crates/gpui/src/platform/mac_wgpu/wgpu_renderer.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use wgpu::{
|
||||
Backends, Device, DeviceDescriptor, Features, Instance, InstanceDescriptor, InstanceFlags,
|
||||
Limits, Queue, Surface, SurfaceTarget,
|
||||
};
|
||||
|
||||
use crate::MacWindow;
|
||||
|
||||
use super::wgpu_atlas::WgpuAtlas;
|
||||
|
||||
pub struct WgpuRenderer {
|
||||
surface: Surface<'static>,
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
sprite_atlas: Arc<WgpuAtlas>,
|
||||
}
|
||||
|
||||
impl WgpuRenderer {
|
||||
pub fn new(window: MacWindow) -> WgpuRenderer {
|
||||
let instance = Instance::new(InstanceDescriptor {
|
||||
backends: Backends::METAL,
|
||||
flags: InstanceFlags::VALIDATION,
|
||||
dx12_shader_compiler: wgpu::Dx12Compiler::Dxc {
|
||||
dxil_path: None,
|
||||
dxc_path: None,
|
||||
},
|
||||
gles_minor_version: wgpu::Gles3MinorVersion::Automatic,
|
||||
});
|
||||
|
||||
let surface = instance
|
||||
.create_surface(SurfaceTarget::Window(Box::new(window)))
|
||||
.unwrap();
|
||||
|
||||
let adapter = smol::block_on(instance.request_adapter(&wgpu::RequestAdapterOptionsBase {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: Some(&surface),
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let (device, queue) = smol::block_on(adapter.request_device(
|
||||
&DeviceDescriptor {
|
||||
label: None,
|
||||
// TODO
|
||||
required_features: Features::default(),
|
||||
// TODO: This may bite us
|
||||
required_limits: Limits::default(),
|
||||
},
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
WgpuRenderer {
|
||||
surface,
|
||||
device,
|
||||
queue,
|
||||
sprite_atlas: Arc::new(WgpuAtlas::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sprite_atlas(&self) -> &Arc<WgpuAtlas> {
|
||||
&self.sprite_atlas
|
||||
}
|
||||
}
|
1838
crates/gpui/src/platform/mac_wgpu/window.rs
Normal file
1838
crates/gpui/src/platform/mac_wgpu/window.rs
Normal file
File diff suppressed because it is too large
Load diff
35
crates/gpui/src/platform/mac_wgpu/window_appearance.rs
Normal file
35
crates/gpui/src/platform/mac_wgpu/window_appearance.rs
Normal file
|
@ -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(crate) 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;
|
||||
}
|
|
@ -64,7 +64,7 @@ parking_lot.workspace = true
|
|||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
sha2 = "0.10"
|
||||
simplelog = "0.9"
|
||||
simplelog = { git = "https://github.com/Drakulix/simplelog.rs" }
|
||||
|
||||
[build-dependencies]
|
||||
serde.workspace = true
|
||||
|
|
|
@ -20,7 +20,7 @@ ctrlc = "3.4"
|
|||
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||
editor = { path = "../editor" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { path = "../gpui" }
|
||||
gpui = { path = "../gpui", features = ["wgpu"] }
|
||||
indoc.workspace = true
|
||||
itertools = "0.11.0"
|
||||
language = { path = "../language" }
|
||||
|
@ -30,7 +30,7 @@ picker = { path = "../picker" }
|
|||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
simplelog = "0.9"
|
||||
simplelog = { git = "https://github.com/Drakulix/simplelog.rs" }
|
||||
smallvec.workspace = true
|
||||
story = { path = "../story" }
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
|
|
|
@ -21,7 +21,7 @@ rust-embed.workspace = true
|
|||
schemars = { workspace = true, features = ["indexmap"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
simplelog = "0.9"
|
||||
simplelog = { git = "https://github.com/Drakulix/simplelog.rs" }
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
theme = { path = "../theme" }
|
||||
uuid.workspace = true
|
||||
|
|
|
@ -51,7 +51,7 @@ fs = { path = "../fs" }
|
|||
fsevent = { path = "../fsevent" }
|
||||
futures.workspace = true
|
||||
go_to_line = { path = "../go_to_line" }
|
||||
gpui = { path = "../gpui" }
|
||||
gpui = { path = "../gpui", features = ["wgpu"] }
|
||||
ignore = "0.4"
|
||||
image = "0.23"
|
||||
indexmap = "1.6.2"
|
||||
|
@ -92,7 +92,7 @@ serde_derive.workspace = true
|
|||
serde_json.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
shellexpand = "2.1.0"
|
||||
simplelog = "0.9"
|
||||
simplelog = { git = "https://github.com/Drakulix/simplelog.rs" }
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree = { path = "../sum_tree" }
|
||||
|
@ -102,6 +102,7 @@ text = { path = "../text" }
|
|||
theme = { path = "../theme" }
|
||||
theme_selector = { path = "../theme_selector" }
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tiny_http = "0.8"
|
||||
toml.workspace = true
|
||||
tree-sitter-bash.workspace = true
|
||||
|
|
|
@ -526,9 +526,16 @@ fn init_logger() {
|
|||
.open(&*paths::LOG)
|
||||
.expect("could not open logfile");
|
||||
|
||||
let format = Box::leak(
|
||||
time::format_description::parse("%Y-%m-%dT%T%:z")
|
||||
.unwrap()
|
||||
.into_boxed_slice(),
|
||||
);
|
||||
|
||||
let config = ConfigBuilder::new()
|
||||
.set_time_format_str("%Y-%m-%dT%T%:z")
|
||||
.set_time_to_local(true)
|
||||
.set_time_format_custom(format)
|
||||
.set_time_offset_to_local()
|
||||
.unwrap()
|
||||
.build();
|
||||
|
||||
simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue