Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
Julia
001692bfc9 Started on quad shader 2024-02-06 12:36:51 -05:00
Julia
63dcef2a76 Start getting a wgpu context up and running 2024-01-31 21:40:28 -05:00
Julia
b504cc6dc4 Add wgpu dependency 2024-01-31 13:04:51 -05:00
Julia
94d4174557 Add temporary mac_wgpu platform for hacking up testing wgpu on macOS 2024-01-31 12:25:15 -05:00
27 changed files with 8500 additions and 40 deletions

468
Cargo.lock generated
View file

@ -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",

View file

@ -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" }

View file

@ -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]

View file

@ -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;

View 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())
}
}

View file

@ -0,0 +1 @@
#include <dispatch/dispatch.h>

View 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();
}

View 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),
),
}
}
}
}

View 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);
}
}
}

View 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()
}
}

View 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> {}

File diff suppressed because it is too large Load diff

View 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;
}

File diff suppressed because it is too large Load diff

View 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;
}

View 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.);
}

View 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>);
}

View 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
}
}

View 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> {}

View 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
}
}

File diff suppressed because it is too large Load diff

View 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;
}

View file

@ -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

View file

@ -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"] }

View file

@ -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

View file

@ -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

View file

@ -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");