Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95f28953b7 | ||
|
|
1a027813f5 | ||
|
|
9072856d4a | ||
|
|
31c65e0410 | ||
|
|
7c95a4f417 | ||
|
|
f659e319ff | ||
|
|
fc1ba3cdc3 |
No files matched your search
Generated
+229
-204
@@ -234,18 +234,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.10.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d"
|
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-vec"
|
name = "bit-vec"
|
||||||
version = "0.9.1"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
@@ -274,6 +274,12 @@ dependencies = [
|
|||||||
"core2",
|
"core2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block2"
|
name = "block2"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -283,15 +289,6 @@ dependencies = [
|
|||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block2"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
|
||||||
dependencies = [
|
|
||||||
"objc2 0.6.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "built"
|
name = "built"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -394,9 +391,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg_aliases"
|
name = "cfg_aliases"
|
||||||
version = "0.2.2"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
@@ -409,9 +406,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codespan-reporting"
|
name = "codespan-reporting"
|
||||||
version = "0.13.1"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
@@ -453,6 +450,16 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -466,8 +473,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
|
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"core-graphics-types",
|
"core-graphics-types 0.1.3",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -479,7 +486,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-graphics-types"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"core-foundation 0.10.1",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -721,6 +739,15 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "font-types"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "font-types"
|
name = "font-types"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -744,7 +771,7 @@ dependencies = [
|
|||||||
"objc2-core-text",
|
"objc2-core-text",
|
||||||
"objc2-foundation 0.3.2",
|
"objc2-foundation 0.3.2",
|
||||||
"parlance",
|
"parlance",
|
||||||
"read-fonts",
|
"read-fonts 0.41.0",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows",
|
"windows",
|
||||||
@@ -779,30 +806,6 @@ version = "0.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-task"
|
|
||||||
version = "0.3.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-util"
|
|
||||||
version = "0.3.34"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"pin-project-lite",
|
|
||||||
"slab",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fxhash"
|
name = "fxhash"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -857,9 +860,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glow"
|
name = "glow"
|
||||||
version = "0.17.0"
|
version = "0.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5"
|
checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
@@ -890,6 +893,26 @@ dependencies = [
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-descriptor"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"gpu-descriptor-types",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gpu-descriptor-types"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.7.1"
|
version = "2.7.1"
|
||||||
@@ -910,7 +933,7 @@ checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"read-fonts",
|
"read-fonts 0.41.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -949,6 +972,12 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hexf-parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -1079,9 +1108,9 @@ checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.10"
|
version = "0.25.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
@@ -1097,8 +1126,8 @@ dependencies = [
|
|||||||
"rayon",
|
"rayon",
|
||||||
"rgb",
|
"rgb",
|
||||||
"tiff",
|
"tiff",
|
||||||
"zune-core",
|
"zune-core 0.5.0",
|
||||||
"zune-jpeg",
|
"zune-jpeg 0.5.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1160,7 +1189,6 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"image",
|
"image",
|
||||||
"log",
|
|
||||||
"parley",
|
"parley",
|
||||||
"swash",
|
"swash",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
@@ -1218,12 +1246,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.105"
|
version = "0.3.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
|
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"once_cell",
|
||||||
"futures-util",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1252,9 +1279,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.189"
|
version = "0.2.179"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
@@ -1347,6 +1374,15 @@ dependencies = [
|
|||||||
"imgref",
|
"imgref",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "malloc_buf"
|
||||||
|
version = "0.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "maybe-rayon"
|
name = "maybe-rayon"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1372,6 +1408,21 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "metal"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.10.0",
|
||||||
|
"block",
|
||||||
|
"core-graphics-types 0.2.0",
|
||||||
|
"foreign-types",
|
||||||
|
"log",
|
||||||
|
"objc",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -1384,9 +1435,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "moxcms"
|
name = "moxcms"
|
||||||
version = "0.8.1"
|
version = "0.7.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"pxfm",
|
"pxfm",
|
||||||
@@ -1394,9 +1445,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naga"
|
name = "naga"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130"
|
checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
@@ -1405,11 +1456,11 @@ dependencies = [
|
|||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"half",
|
"half",
|
||||||
"hashbrown 0.17.1",
|
"hashbrown 0.16.1",
|
||||||
|
"hexf-parse",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"libm",
|
"libm",
|
||||||
"log",
|
"log",
|
||||||
"naga-types",
|
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
@@ -1418,18 +1469,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "naga-types"
|
|
||||||
version = "30.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.17.1",
|
|
||||||
"indexmap",
|
|
||||||
"rustc-hash",
|
|
||||||
"thiserror 2.0.17",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ndk"
|
name = "ndk"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -1554,6 +1593,15 @@ dependencies = [
|
|||||||
"syn 2.0.113",
|
"syn 2.0.113",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||||
|
dependencies = [
|
||||||
|
"malloc_buf",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc-sys"
|
name = "objc-sys"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
@@ -1586,13 +1634,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
"objc2-core-image",
|
"objc2-core-image",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-quartz-core 0.2.2",
|
"objc2-quartz-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1614,7 +1662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1626,7 +1674,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
@@ -1638,7 +1686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
@@ -1673,10 +1721,10 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal 0.2.2",
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1685,7 +1733,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-contacts",
|
"objc2-contacts",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1714,7 +1762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
@@ -1748,7 +1796,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-app-kit 0.2.2",
|
"objc2-app-kit 0.2.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1761,23 +1809,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-metal"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"block2 0.6.2",
|
|
||||||
"objc2 0.6.4",
|
|
||||||
"objc2-foundation 0.3.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -1785,24 +1821,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal 0.2.2",
|
"objc2-metal",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "objc2-quartz-core"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"objc2 0.6.4",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-core-graphics",
|
|
||||||
"objc2-foundation 0.3.2",
|
|
||||||
"objc2-metal 0.3.2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1822,7 +1844,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-cloud-kit",
|
"objc2-cloud-kit",
|
||||||
"objc2-core-data",
|
"objc2-core-data",
|
||||||
@@ -1830,7 +1852,7 @@ dependencies = [
|
|||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-link-presentation",
|
"objc2-link-presentation",
|
||||||
"objc2-quartz-core 0.2.2",
|
"objc2-quartz-core",
|
||||||
"objc2-symbols",
|
"objc2-symbols",
|
||||||
"objc2-uniform-type-identifiers",
|
"objc2-uniform-type-identifiers",
|
||||||
"objc2-user-notifications",
|
"objc2-user-notifications",
|
||||||
@@ -1842,7 +1864,7 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
@@ -1854,7 +1876,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-core-location",
|
"objc2-core-location",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
@@ -1948,7 +1970,7 @@ dependencies = [
|
|||||||
"linebender_resource_handle",
|
"linebender_resource_handle",
|
||||||
"parlance",
|
"parlance",
|
||||||
"parley_data",
|
"parley_data",
|
||||||
"skrifa",
|
"skrifa 0.44.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2252,9 +2274,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ravif"
|
name = "ravif"
|
||||||
version = "0.13.0"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
|
checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"avif-serialize",
|
"avif-serialize",
|
||||||
"imgref",
|
"imgref",
|
||||||
@@ -2271,18 +2293,6 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "raw-window-metal"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135"
|
|
||||||
dependencies = [
|
|
||||||
"objc2 0.6.4",
|
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-foundation 0.3.2",
|
|
||||||
"objc2-quartz-core 0.3.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
@@ -2303,6 +2313,16 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "read-fonts"
|
||||||
|
version = "0.36.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5eaa2941a4c05443ee3a7b26ab076a553c343ad5995230cc2b1d3e993bdc6345"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"font-types 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "read-fonts"
|
name = "read-fonts"
|
||||||
version = "0.41.0"
|
version = "0.41.0"
|
||||||
@@ -2310,7 +2330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
|
checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"font-types",
|
"font-types 0.12.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2485,6 +2505,16 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "skrifa"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c9eb0b904a04d09bd68c65d946617b8ff733009999050f3b851c32fb3cfb60e"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"read-fonts 0.36.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "skrifa"
|
name = "skrifa"
|
||||||
version = "0.44.0"
|
version = "0.44.0"
|
||||||
@@ -2492,7 +2522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
|
checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"read-fonts",
|
"read-fonts 0.41.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2552,9 +2582,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spirv"
|
name = "spirv"
|
||||||
version = "0.4.0+sdk-1.4.341.0"
|
version = "0.3.0+sdk-1.3.268.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f"
|
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
]
|
]
|
||||||
@@ -2583,7 +2613,7 @@ version = "0.2.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e"
|
checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"skrifa",
|
"skrifa 0.39.0",
|
||||||
"yazi",
|
"yazi",
|
||||||
"zeno",
|
"zeno",
|
||||||
]
|
]
|
||||||
@@ -2672,16 +2702,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.11.3"
|
version = "0.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fax",
|
"fax",
|
||||||
"flate2",
|
"flate2",
|
||||||
"half",
|
"half",
|
||||||
"quick-error",
|
"quick-error",
|
||||||
"weezl",
|
"weezl",
|
||||||
"zune-jpeg",
|
"zune-jpeg 0.4.21",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2854,9 +2884,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.128"
|
version = "0.2.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
|
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -2867,19 +2897,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.78"
|
version = "0.4.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928"
|
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.128"
|
version = "0.2.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
|
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -2887,22 +2920,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.128"
|
version = "0.2.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
|
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 3.0.5",
|
"syn 2.0.113",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.128"
|
version = "0.2.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
|
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -3018,9 +3051,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.105"
|
version = "0.3.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
|
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3044,9 +3077,9 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu"
|
name = "wgpu"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "527ccdf43dd5b2e8676eed9984ce00e2bbb0a1b85b70c1969dcb6cd2eb55ab9e"
|
checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
@@ -3054,7 +3087,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"document-features",
|
"document-features",
|
||||||
"hashbrown 0.17.1",
|
"hashbrown 0.16.1",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"naga",
|
"naga",
|
||||||
@@ -3074,9 +3107,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core"
|
name = "wgpu-core"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14c018fce9b6270aa203c2fdd56f3cce996713534bd757e4ea58c8560b121f14"
|
checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
@@ -3085,11 +3118,10 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"document-features",
|
"document-features",
|
||||||
"hashbrown 0.17.1",
|
"hashbrown 0.16.1",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"naga",
|
"naga",
|
||||||
"naga-types",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
@@ -3102,70 +3134,66 @@ dependencies = [
|
|||||||
"wgpu-core-deps-emscripten",
|
"wgpu-core-deps-emscripten",
|
||||||
"wgpu-core-deps-windows-linux-android",
|
"wgpu-core-deps-windows-linux-android",
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
"wgpu-naga-bridge",
|
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-apple"
|
name = "wgpu-core-deps-apple"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "061f3d319a40d39d00b1ecc2c33b89fe21d4e6fe01859df3500a3a8ecccd6b68"
|
checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-emscripten"
|
name = "wgpu-core-deps-emscripten"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d98b86cf4abf524a902dd35f18ca6a3f08fc2ae9847c8f10b48e30491b1f0b86"
|
checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-core-deps-windows-linux-android"
|
name = "wgpu-core-deps-windows-linux-android"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7586165fd5f6d881cb9ce4bb71f40d6caab2c0f1837e3fc1d9788a197fb6004f"
|
checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wgpu-hal",
|
"wgpu-hal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-hal"
|
name = "wgpu-hal"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58"
|
checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android_system_properties",
|
"android_system_properties",
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"ash",
|
"ash",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.6.2",
|
"block",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
|
"core-graphics-types 0.2.0",
|
||||||
"glow",
|
"glow",
|
||||||
"glutin_wgl_sys",
|
"glutin_wgl_sys",
|
||||||
"gpu-allocator",
|
"gpu-allocator",
|
||||||
"hashbrown 0.17.1",
|
"gpu-descriptor",
|
||||||
|
"hashbrown 0.16.1",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"khronos-egl",
|
"khronos-egl",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading",
|
"libloading",
|
||||||
"log",
|
"log",
|
||||||
|
"metal",
|
||||||
"naga",
|
"naga",
|
||||||
"naga-types",
|
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
"objc2 0.6.4",
|
"objc",
|
||||||
"objc2-core-foundation",
|
|
||||||
"objc2-core-graphics",
|
|
||||||
"objc2-foundation 0.3.2",
|
|
||||||
"objc2-metal 0.3.2",
|
|
||||||
"objc2-quartz-core 0.3.2",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
@@ -3174,44 +3202,26 @@ dependencies = [
|
|||||||
"profiling",
|
"profiling",
|
||||||
"range-alloc",
|
"range-alloc",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"raw-window-metal",
|
|
||||||
"renderdoc-sys",
|
"renderdoc-sys",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"static_assertions",
|
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wayland-sys",
|
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"wgpu-naga-bridge",
|
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-core",
|
"windows-core",
|
||||||
"windows-result",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wgpu-naga-bridge"
|
|
||||||
version = "30.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2f62e73117bb7a62bfd9c5a5841438a823f6566c6442a808ee269d2d055c081"
|
|
||||||
dependencies = [
|
|
||||||
"naga",
|
|
||||||
"wgpu-types",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wgpu-types"
|
name = "wgpu-types"
|
||||||
version = "30.0.1"
|
version = "28.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "99dad6f1fbdbbdb4c278a6508b059d44688f5cebddf78d005a46a31340269286"
|
checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"naga-types",
|
|
||||||
"raw-window-handle",
|
|
||||||
"static_assertions",
|
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3575,12 +3585,12 @@ dependencies = [
|
|||||||
"android-activity",
|
"android-activity",
|
||||||
"atomic-waker",
|
"atomic-waker",
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"block2 0.5.1",
|
"block2",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"calloop",
|
"calloop",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"core-foundation",
|
"core-foundation 0.9.4",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"cursor-icon",
|
"cursor-icon",
|
||||||
"dpi",
|
"dpi",
|
||||||
@@ -3847,6 +3857,12 @@ dependencies = [
|
|||||||
"syn 3.0.5",
|
"syn 3.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -3862,11 +3878,20 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.4.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core 0.4.12",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-jpeg"
|
name = "zune-jpeg"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5"
|
checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zune-core",
|
"zune-core 0.5.0",
|
||||||
]
|
]
|
||||||
+2
-3
@@ -29,13 +29,12 @@ edition = "2024"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
pollster = "0.4.0"
|
pollster = "0.4.0"
|
||||||
winit = "0.30.12"
|
winit = "0.30.12"
|
||||||
wgpu = "30.0.1"
|
wgpu = "28.0.0"
|
||||||
bytemuck = "1.23.1"
|
bytemuck = "1.23.1"
|
||||||
image = "0.25.10"
|
image = "0.25.6"
|
||||||
parley = "0.11.1"
|
parley = "0.11.1"
|
||||||
swash = "0.2.10"
|
swash = "0.2.10"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
log = "0.4.29"
|
|
||||||
arboard = "3.6.1"
|
arboard = "3.6.1"
|
||||||
iris-core = { path = "core" }
|
iris-core = { path = "core" }
|
||||||
iris-macro = { path = "macro" }
|
iris-macro = { path = "macro" }
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
images
|
images
|
||||||
settings (sampler)
|
settings (sampler)
|
||||||
consider typed TextureHandle<T> variants for distinct texture uses
|
|
||||||
|
text
|
||||||
|
figure out ways to speed up / what costs the most
|
||||||
|
resizing (per frame) is really slow (assuming painter isn't griefing)
|
||||||
|
j is weird / fix x offset
|
||||||
|
|
||||||
|
masks r just made to bare minimum work
|
||||||
|
|
||||||
|
scaling
|
||||||
|
could be just a simple scaling factor that multiplies abs
|
||||||
|
and need to ensure text uses raw abs and not scaled abs
|
||||||
|
naming? (pt, px)
|
||||||
|
want to keep (drawn) regions using px? or should I add another field to UiScalar/Vec
|
||||||
|
field could be best solution so redrawing stuff isn't needed & you can specify both as user
|
||||||
|
|
||||||
WidgetRef<W> or smth instead of Id
|
WidgetRef<W> or smth instead of Id
|
||||||
enum that's either an Id or an actual concrete instance of W
|
enum that's either an Id or an actual concrete instance of W
|
||||||
@@ -11,6 +24,17 @@ WidgetRef<W> or smth instead of Id
|
|||||||
maybe introduce InnerWidget trait to allow for editors to expose & modify inner type
|
maybe introduce InnerWidget trait to allow for editors to expose & modify inner type
|
||||||
maybe could also store a parent widget and keep using InnerWidget trait? unsure if possible
|
maybe could also store a parent widget and keep using InnerWidget trait? unsure if possible
|
||||||
|
|
||||||
|
really weird limitation:
|
||||||
|
I don't think you can currently remove an element from a parent and put it in a child of the same parent
|
||||||
|
because it removes the unused children after the entire parent redraw
|
||||||
|
but the child gets drawn during that, so it will think the child is still active !!!
|
||||||
|
or something like that idk, maybe I need a special enum for parent that includes a undecided state where it may or may not get redrawn by the parent
|
||||||
|
or just do ref counting and ensure all drawn things == 1 afterwards (seems like best way)
|
||||||
|
ok so I'm removing the limit for now
|
||||||
|
|
||||||
|
don't forget I'm streaming
|
||||||
|
|
||||||
|
tags
|
||||||
vecs for each widget type?
|
vecs for each widget type?
|
||||||
|
|
||||||
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||||
@@ -10,4 +10,3 @@ image = { workspace = true }
|
|||||||
parley = { workspace = true }
|
parley = { workspace = true }
|
||||||
swash = { workspace = true }
|
swash = { workspace = true }
|
||||||
fxhash = { workspace = true }
|
fxhash = { workspace = true }
|
||||||
log = { workspace = true }
|
|
||||||
@@ -79,7 +79,6 @@ type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>
|
|||||||
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
|
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
|
||||||
// TODO: reduce visiblity!!
|
// TODO: reduce visiblity!!
|
||||||
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
|
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
|
||||||
pub global: E::Global,
|
|
||||||
map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
|
map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +107,6 @@ impl<Rsc: HasEvents, E: Event> Default for TypeEventManager<Rsc, E> {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
active: Default::default(),
|
active: Default::default(),
|
||||||
global: Default::default(),
|
|
||||||
map: Default::default(),
|
map: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,13 +138,11 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
|||||||
pub fn run_fn<'a>(
|
pub fn run_fn<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: impl IdLike,
|
id: impl IdLike,
|
||||||
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) -> bool + 'a {
|
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) + 'a {
|
||||||
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
||||||
move |ctx, rsc| {
|
move |ctx, rsc| {
|
||||||
let mut consumed = false;
|
|
||||||
for (e, f) in fs {
|
for (e, f) in fs {
|
||||||
if let Some(data) = e.should_run(&ctx.data) {
|
if let Some(data) = e.should_run(&ctx.data) {
|
||||||
consumed |= e.consumes(&data);
|
|
||||||
f(
|
f(
|
||||||
EventCtx {
|
EventCtx {
|
||||||
state: ctx.state,
|
state: ctx.state,
|
||||||
@@ -156,7 +152,6 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
consumed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,19 +9,10 @@ pub use rsc::*;
|
|||||||
pub trait Event: Sized + 'static + Clone {
|
pub trait Event: Sized + 'static + Clone {
|
||||||
type Data<'a>: Clone = ();
|
type Data<'a>: Clone = ();
|
||||||
type State: Default = ();
|
type State: Default = ();
|
||||||
/// State the whole event type keeps, rather than one copy per widget.
|
|
||||||
type Global: Default = ();
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||||
Some(data.clone())
|
Some(data.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether having run on this data uses up whatever triggered it, so
|
|
||||||
/// nothing further should see it.
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn consumes(&self, data: &Self::Data<'_>) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EventLike {
|
pub trait EventLike {
|
||||||
|
|||||||
@@ -21,13 +21,12 @@ pub trait HasEvents: Sized + UiRsc + HasState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait RunEvents: HasEvents {
|
pub trait RunEvents: HasEvents {
|
||||||
/// Whether anything that ran used up what triggered it.
|
|
||||||
fn run_event<E: EventLike>(
|
fn run_event<E: EventLike>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: impl IdLike,
|
id: impl IdLike,
|
||||||
data: <E::Event as Event>::Data<'_>,
|
data: <E::Event as Event>::Data<'_>,
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
) -> bool {
|
) {
|
||||||
let f = self.events_mut().get_type::<E>().run_fn(id);
|
let f = self.events_mut().get_type::<E>().run_fn(id);
|
||||||
f(EventCtx { state, data }, self)
|
f(EventCtx { state, data }, self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render::{LayerDraws, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
|
||||||
util::to_mut,
|
util::to_mut,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ struct Child {
|
|||||||
tail: usize,
|
tail: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DrawLayers = Layers<LayerDraws>;
|
pub type PrimitiveLayers = Layers<Primitives>;
|
||||||
|
|
||||||
impl<T: Default> Layers<T> {
|
impl<T: Default> Layers<T> {
|
||||||
pub fn new() -> Layers<T> {
|
pub fn new() -> Layers<T> {
|
||||||
@@ -119,7 +119,7 @@ impl<T: Default> Layers<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawLayers {
|
impl PrimitiveLayers {
|
||||||
pub fn write<P: Primitive>(
|
pub fn write<P: Primitive>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layer: LayerId,
|
layer: LayerId,
|
||||||
|
|||||||
+57
-89
@@ -1,11 +1,8 @@
|
|||||||
use crate::{
|
use crate::{Align, GlyphAtlas, GlyphKey, PlacedGlyph, RegionAlign, Textures, UiColor, util::Vec2};
|
||||||
Align, GlyphAtlas, GlyphEntry, GlyphKey, PlacedGlyph, RegionAlign, UiColor, util::Vec2,
|
|
||||||
};
|
|
||||||
use parley::{
|
use parley::{
|
||||||
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
||||||
LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
|
LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
|
||||||
};
|
};
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
|
||||||
use swash::{
|
use swash::{
|
||||||
FontRef,
|
FontRef,
|
||||||
scale::{Render, ScaleContext, Source, StrikeWith},
|
scale::{Render, ScaleContext, Source, StrikeWith},
|
||||||
@@ -13,18 +10,18 @@ use swash::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct TextData {
|
pub struct TextData {
|
||||||
pub font_ctx: FontContext,
|
pub font_cx: FontContext,
|
||||||
pub layout_ctx: LayoutContext<UiColor>,
|
pub layout_cx: LayoutContext<UiColor>,
|
||||||
scale_ctx: ScaleContext,
|
scale_cx: ScaleContext,
|
||||||
pub atlas: GlyphAtlas,
|
pub atlas: GlyphAtlas,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextData {
|
impl Default for TextData {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
font_ctx: FontContext::new(),
|
font_cx: FontContext::new(),
|
||||||
layout_ctx: LayoutContext::new(),
|
layout_cx: LayoutContext::new(),
|
||||||
scale_ctx: ScaleContext::new(),
|
scale_cx: ScaleContext::new(),
|
||||||
atlas: GlyphAtlas::default(),
|
atlas: GlyphAtlas::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,13 +77,7 @@ impl Default for TextAttrs {
|
|||||||
pub struct TextBuffer {
|
pub struct TextBuffer {
|
||||||
text: String,
|
text: String,
|
||||||
layout: Layout<UiColor>,
|
layout: Layout<UiColor>,
|
||||||
layout_key: Option<LayoutKey>,
|
shaped: Option<(TextAttrs, Option<f32>)>,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
struct LayoutKey {
|
|
||||||
attrs: TextAttrs,
|
|
||||||
max_width: Option<f32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBuffer {
|
impl TextBuffer {
|
||||||
@@ -94,7 +85,7 @@ impl TextBuffer {
|
|||||||
Self {
|
Self {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
layout: Layout::new(),
|
layout: Layout::new(),
|
||||||
layout_key: None,
|
shaped: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,13 +109,13 @@ impl TextBuffer {
|
|||||||
let text = text.into();
|
let text = text.into();
|
||||||
if text != self.text {
|
if text != self.text {
|
||||||
self.text = text;
|
self.text = text;
|
||||||
self.layout_key = None;
|
self.shaped = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invalidates the layout and returns the underlying string for editing.
|
/// Invalidates the layout and returns the underlying string for editing.
|
||||||
pub fn edit(&mut self) -> &mut String {
|
pub fn edit(&mut self) -> &mut String {
|
||||||
self.layout_key = None;
|
self.shaped = None;
|
||||||
&mut self.text
|
&mut self.text
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,16 +124,12 @@ impl TextBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn shape(&mut self, data: &mut TextData, attrs: &TextAttrs, width: Option<f32>) {
|
pub fn shape(&mut self, data: &mut TextData, attrs: &TextAttrs, width: Option<f32>) {
|
||||||
let layout_key = LayoutKey {
|
if self.shaped.as_ref() == Some(&(attrs.clone(), width)) {
|
||||||
attrs: attrs.clone(),
|
|
||||||
max_width: width,
|
|
||||||
};
|
|
||||||
if self.layout_key.as_ref() == Some(&layout_key) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut builder = data
|
let mut builder = data
|
||||||
.layout_ctx
|
.layout_cx
|
||||||
.ranged_builder(&mut data.font_ctx, &self.text, 1.0, true);
|
.ranged_builder(&mut data.font_cx, &self.text, 1.0, true);
|
||||||
builder.push_default(StyleProperty::FontFamily(attrs.family.family()));
|
builder.push_default(StyleProperty::FontFamily(attrs.family.family()));
|
||||||
builder.push_default(StyleProperty::FontSize(attrs.font_size));
|
builder.push_default(StyleProperty::FontSize(attrs.font_size));
|
||||||
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
|
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
|
||||||
@@ -153,12 +140,12 @@ impl TextBuffer {
|
|||||||
self.layout.break_all_lines(width);
|
self.layout.break_all_lines(width);
|
||||||
self.layout
|
self.layout
|
||||||
.align(Alignment::Start, AlignmentOptions::default());
|
.align(Alignment::Start, AlignmentOptions::default());
|
||||||
self.layout_key = Some(layout_key);
|
self.shaped = Some((attrs.clone(), width));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextData {
|
impl TextData {
|
||||||
pub fn place(&mut self, buffer: &TextBuffer) -> Vec<PlacedGlyph> {
|
pub fn place(&mut self, buffer: &TextBuffer, textures: &mut Textures) -> Vec<PlacedGlyph> {
|
||||||
let mut placed = Vec::new();
|
let mut placed = Vec::new();
|
||||||
for line in buffer.layout.lines() {
|
for line in buffer.layout.lines() {
|
||||||
for item in line.items() {
|
for item in line.items() {
|
||||||
@@ -173,6 +160,8 @@ impl TextData {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let coords_hash = hash_coords(coords);
|
let coords_hash = hash_coords(coords);
|
||||||
|
// `font.data.id()` rather than the pointer, so the same font
|
||||||
|
// loaded twice is still one set of entries.
|
||||||
let font_id = font.data.id();
|
let font_id = font.data.id();
|
||||||
|
|
||||||
for glyph in run.positioned_glyphs() {
|
for glyph in run.positioned_glyphs() {
|
||||||
@@ -180,20 +169,38 @@ impl TextData {
|
|||||||
let key = GlyphKey {
|
let key = GlyphKey {
|
||||||
font: font_id,
|
font: font_id,
|
||||||
glyph: glyph.id,
|
glyph: glyph.id,
|
||||||
size: glyph_size_key(font_size),
|
size: (font_size * 16.0).round() as u32,
|
||||||
subpixel,
|
subpixel,
|
||||||
coords: coords_hash,
|
coords: coords_hash,
|
||||||
};
|
};
|
||||||
let Some(entry) = self.glyph_entry(GlyphRaster {
|
let entry = match self.atlas.get(&key) {
|
||||||
key,
|
Some(entry) => entry,
|
||||||
font: font_ref,
|
None => {
|
||||||
font_size,
|
let mut scaler = self
|
||||||
coords,
|
.scale_cx
|
||||||
subpixel,
|
.builder(font_ref)
|
||||||
glyph_id: glyph.id,
|
.size(font_size)
|
||||||
}) else {
|
.hint(true)
|
||||||
continue;
|
.normalized_coords(coords)
|
||||||
|
.build();
|
||||||
|
let image = Render::new(&[
|
||||||
|
Source::ColorOutline(0),
|
||||||
|
Source::ColorBitmap(StrikeWith::BestFit),
|
||||||
|
Source::Outline,
|
||||||
|
])
|
||||||
|
.format(Format::Alpha)
|
||||||
|
.offset(Vector::new(subpixel as f32 / 4.0, 0.0))
|
||||||
|
.render(&mut scaler, glyph.id as u16);
|
||||||
|
match image {
|
||||||
|
Some(image) => self.atlas.insert(key, &image, textures),
|
||||||
|
None => {
|
||||||
|
self.atlas.insert_empty(key);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
let Some(entry) = entry else { continue };
|
||||||
placed.push(PlacedGlyph {
|
placed.push(PlacedGlyph {
|
||||||
entry,
|
entry,
|
||||||
offset: Vec2::new(
|
offset: Vec2::new(
|
||||||
@@ -206,60 +213,20 @@ impl TextData {
|
|||||||
}
|
}
|
||||||
placed
|
placed
|
||||||
}
|
}
|
||||||
|
|
||||||
fn glyph_entry(&mut self, glyph: GlyphRaster<'_>) -> Option<GlyphEntry> {
|
|
||||||
if let Some(entry) = self.atlas.get(&glyph.key) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut scaler = self
|
|
||||||
.scale_ctx
|
|
||||||
.builder(glyph.font)
|
|
||||||
.size(glyph.font_size)
|
|
||||||
.hint(true)
|
|
||||||
.normalized_coords(glyph.coords)
|
|
||||||
.build();
|
|
||||||
let image = Render::new(&[
|
|
||||||
Source::ColorOutline(0),
|
|
||||||
Source::ColorBitmap(StrikeWith::BestFit),
|
|
||||||
Source::Outline,
|
|
||||||
])
|
|
||||||
.format(Format::Alpha)
|
|
||||||
.offset(Vector::new(glyph.subpixel as f32 / 4.0, 0.0))
|
|
||||||
.render(&mut scaler, glyph.glyph_id as u16);
|
|
||||||
|
|
||||||
if let Some(image) = image {
|
|
||||||
self.atlas.insert(glyph.key, &image)
|
|
||||||
} else {
|
|
||||||
self.atlas.insert_empty(glyph.key);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GlyphRaster<'a> {
|
|
||||||
key: GlyphKey,
|
|
||||||
font: FontRef<'a>,
|
|
||||||
font_size: f32,
|
|
||||||
coords: &'a [i16],
|
|
||||||
subpixel: u8,
|
|
||||||
glyph_id: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_coords(coords: &[i16]) -> u64 {
|
fn hash_coords(coords: &[i16]) -> u64 {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
|
||||||
coords.hash(&mut hasher);
|
for c in coords {
|
||||||
hasher.finish()
|
h ^= *c as u16 as u64;
|
||||||
}
|
h = h.wrapping_mul(0x1000_0000_01b3);
|
||||||
|
}
|
||||||
const GLYPH_SIZE_STEPS_PER_PIXEL: f32 = 16.0;
|
h
|
||||||
|
|
||||||
fn glyph_size_key(font_size: f32) -> u32 {
|
|
||||||
(font_size * GLYPH_SIZE_STEPS_PER_PIXEL).round() as u32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct RenderedText {
|
pub struct RenderedText {
|
||||||
pub glyphs: Vec<PlacedGlyph>,
|
pub glyphs: std::sync::Arc<Vec<PlacedGlyph>>,
|
||||||
pub size: Vec2,
|
pub size: Vec2,
|
||||||
pub color: UiColor,
|
pub color: UiColor,
|
||||||
}
|
}
|
||||||
@@ -270,11 +237,12 @@ impl TextData {
|
|||||||
buffer: &mut TextBuffer,
|
buffer: &mut TextBuffer,
|
||||||
attrs: &TextAttrs,
|
attrs: &TextAttrs,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
|
textures: &mut Textures,
|
||||||
) -> RenderedText {
|
) -> RenderedText {
|
||||||
buffer.shape(self, attrs, width);
|
buffer.shape(self, attrs, width);
|
||||||
let glyphs = self.place(buffer);
|
let glyphs = self.place(buffer, textures);
|
||||||
RenderedText {
|
RenderedText {
|
||||||
glyphs,
|
glyphs: std::sync::Arc::new(glyphs),
|
||||||
size: buffer.size(),
|
size: buffer.size(),
|
||||||
color: attrs.color,
|
color: attrs.color,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::util::{RefCounter, Vec2};
|
use crate::{
|
||||||
|
render::TexturePrimitive,
|
||||||
|
util::{RefCounter, Vec2},
|
||||||
|
};
|
||||||
use image::{DynamicImage, GenericImageView};
|
use image::{DynamicImage, GenericImageView};
|
||||||
use std::{
|
use std::{
|
||||||
ops::Index,
|
ops::Index,
|
||||||
@@ -7,7 +10,7 @@ use std::{
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextureHandle {
|
pub struct TextureHandle {
|
||||||
slot: u32,
|
inner: TexturePrimitive,
|
||||||
size: Vec2,
|
size: Vec2,
|
||||||
counter: RefCounter,
|
counter: RefCounter,
|
||||||
send: Sender<u32>,
|
send: Sender<u32>,
|
||||||
@@ -28,8 +31,6 @@ pub enum TextureUpdate<'a> {
|
|||||||
Set(u32, &'a DynamicImage),
|
Set(u32, &'a DynamicImage),
|
||||||
Patch(u32, PatchRect, &'a DynamicImage),
|
Patch(u32, PatchRect, &'a DynamicImage),
|
||||||
Free(u32),
|
Free(u32),
|
||||||
/// Added and freed before the renderer drained either update. It still has
|
|
||||||
/// to push a slot to stay lined up with `images`; `Free` then empties it.
|
|
||||||
PushFree,
|
PushFree,
|
||||||
SetFree,
|
SetFree,
|
||||||
}
|
}
|
||||||
@@ -63,8 +64,14 @@ impl Textures {
|
|||||||
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
pub fn add(&mut self, image: impl Into<DynamicImage>) -> TextureHandle {
|
||||||
let image = image.into();
|
let image = image.into();
|
||||||
let size = image.dimensions().into();
|
let size = image.dimensions().into();
|
||||||
|
let view_idx = self.push(image);
|
||||||
|
// 0 == default in renderer; TODO: actually create samplers here
|
||||||
|
let sampler_idx = 0;
|
||||||
TextureHandle {
|
TextureHandle {
|
||||||
slot: self.push(image),
|
inner: TexturePrimitive {
|
||||||
|
view_idx,
|
||||||
|
sampler_idx,
|
||||||
|
},
|
||||||
size,
|
size,
|
||||||
counter: RefCounter::new(),
|
counter: RefCounter::new(),
|
||||||
send: self.send.clone(),
|
send: self.send.clone(),
|
||||||
@@ -85,20 +92,15 @@ impl Textures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_mut(&mut self, handle: &TextureHandle) -> &mut DynamicImage {
|
pub fn image_mut(&mut self, handle: &TextureHandle) -> &mut DynamicImage {
|
||||||
self.images[handle.slot as usize]
|
self.images[handle.inner.view_idx as usize]
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("texture was freed while still held")
|
.expect("texture was freed while still held")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue an upload of just `rect`, after writing it with `image_mut`.
|
/// Queue an upload of just `rect`, after writing it with `image_mut`.
|
||||||
pub fn patch(&mut self, handle: &TextureHandle, rect: PatchRect) {
|
pub fn patch(&mut self, handle: &TextureHandle, rect: PatchRect) {
|
||||||
self.updates.push(Update::Patch(handle.slot, rect));
|
self.updates
|
||||||
}
|
.push(Update::Patch(handle.inner.view_idx, rect));
|
||||||
|
|
||||||
/// How many textures are live, which is what a ui can ask; the renderer's
|
|
||||||
/// copies follow from the updates it drains.
|
|
||||||
pub fn count(&self) -> usize {
|
|
||||||
self.images.iter().flatten().count()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(&mut self) {
|
pub fn free(&mut self) {
|
||||||
@@ -129,9 +131,8 @@ impl Textures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextureHandle {
|
impl TextureHandle {
|
||||||
/// Index into `Textures`, and into the renderer's parallel slots.
|
pub fn primitive(&self) -> TexturePrimitive {
|
||||||
pub fn slot(&self) -> u32 {
|
self.inner
|
||||||
self.slot
|
|
||||||
}
|
}
|
||||||
pub fn size(&self) -> Vec2 {
|
pub fn size(&self) -> Vec2 {
|
||||||
self.size
|
self.size
|
||||||
@@ -141,7 +142,7 @@ impl TextureHandle {
|
|||||||
impl Drop for TextureHandle {
|
impl Drop for TextureHandle {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.counter.drop() {
|
if self.counter.drop() {
|
||||||
let _ = self.send.send(self.slot);
|
let _ = self.send.send(self.inner.view_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +151,7 @@ impl Index<&TextureHandle> for Textures {
|
|||||||
type Output = DynamicImage;
|
type Output = DynamicImage;
|
||||||
|
|
||||||
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
fn index(&self, index: &TextureHandle) -> &Self::Output {
|
||||||
self.images[index.slot as usize].as_ref().unwrap()
|
self.images[index.inner.view_idx as usize].as_ref().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+89
-134
@@ -1,12 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
PatchRect,
|
PatchRect, TextureHandle, Textures,
|
||||||
util::{HashMap, Vec2},
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
use image::RgbaImage;
|
use image::RgbaImage;
|
||||||
use swash::scale::image::{Content, Image};
|
use swash::scale::image::{Content, Image};
|
||||||
|
|
||||||
/// Side of one page, and so of every layer of `render::page`'s array texture.
|
const PAGE: u32 = 1024;
|
||||||
pub(crate) const PAGE: u32 = 1024;
|
|
||||||
|
|
||||||
/// Transparent margin kept around every glyph, so that sampling one cannot
|
/// Transparent margin kept around every glyph, so that sampling one cannot
|
||||||
/// pick up its neighbour along a shared edge.
|
/// pick up its neighbour along a shared edge.
|
||||||
@@ -28,47 +27,31 @@ pub struct GlyphKey {
|
|||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct GlyphEntry {
|
pub struct GlyphEntry {
|
||||||
pub uv_min: Vec2,
|
pub uv_min: [f32; 2],
|
||||||
pub uv_max: Vec2,
|
pub uv_max: [f32; 2],
|
||||||
/// Offset from the glyph's pen position to the top-left of its pixels.
|
/// Offset from the glyph's pen position to the top-left of its pixels.
|
||||||
pub left: i32,
|
pub left: i32,
|
||||||
pub top: i32,
|
pub top: i32,
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub is_colored: bool,
|
pub is_color: bool,
|
||||||
/// Which atlas array layer this glyph is on.
|
pub view_idx: u32,
|
||||||
pub layer: u32,
|
pub sampler_idx: u32,
|
||||||
}
|
|
||||||
|
|
||||||
impl GlyphEntry {
|
|
||||||
const IS_COLORED: u32 = 1;
|
|
||||||
|
|
||||||
pub(crate) fn flags(&self) -> u32 {
|
|
||||||
if self.is_colored { Self::IS_COLORED } else { 0 }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Page {
|
struct Page {
|
||||||
image: RgbaImage,
|
handle: TextureHandle,
|
||||||
x: u32,
|
x: u32,
|
||||||
y: u32,
|
y: u32,
|
||||||
shelf_height: u32,
|
shelf_height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A rectangle of one page the renderer has not uploaded yet.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct PageUpload {
|
|
||||||
pub layer: u32,
|
|
||||||
pub rect: PatchRect,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GlyphAtlas {
|
pub struct GlyphAtlas {
|
||||||
pages: Vec<Page>,
|
pages: Vec<Page>,
|
||||||
/// `None` for a glyph that rasterised to nothing -- a space, say. Cached
|
/// `None` for a glyph that rasterised to nothing -- a space, say. Cached
|
||||||
/// too, so it is not re-rasterised on every layout.
|
/// too, so it is not re-rasterised on every layout.
|
||||||
entries: HashMap<GlyphKey, Option<GlyphEntry>>,
|
entries: HashMap<GlyphKey, Option<GlyphEntry>>,
|
||||||
uploads: Vec<PageUpload>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlyphAtlas {
|
impl GlyphAtlas {
|
||||||
@@ -76,96 +59,88 @@ impl GlyphAtlas {
|
|||||||
self.entries.get(key).copied()
|
self.entries.get(key).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, key: GlyphKey, image: &Image) -> Option<GlyphEntry> {
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
key: GlyphKey,
|
||||||
|
image: &Image,
|
||||||
|
textures: &mut Textures,
|
||||||
|
) -> Option<GlyphEntry> {
|
||||||
let w = image.placement.width;
|
let w = image.placement.width;
|
||||||
let h = image.placement.height;
|
let h = image.placement.height;
|
||||||
if w == 0 || h == 0 {
|
if w == 0 || h == 0 {
|
||||||
log::warn!(
|
|
||||||
"glyph {} in font {} rasterized at {w}x{h}; skipping it",
|
|
||||||
key.glyph,
|
|
||||||
key.font,
|
|
||||||
);
|
|
||||||
self.entries.insert(key, None);
|
self.entries.insert(key, None);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if w > PAGE - PAD * 2 || h > PAGE - PAD * 2 {
|
if w + PAD * 2 > PAGE || h + PAD * 2 > PAGE {
|
||||||
log::warn!(
|
|
||||||
"glyph {} in font {} rasterized at {w}x{h}, too large for the {PAGE}x{PAGE} atlas; skipping it",
|
|
||||||
key.glyph,
|
|
||||||
key.font,
|
|
||||||
);
|
|
||||||
self.entries.insert(key, None);
|
self.entries.insert(key, None);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let upload = self.allocate(w, h);
|
let (page_idx, x, y) = self.allocate(w, h, textures);
|
||||||
let PatchRect { x, y, .. } = upload.rect;
|
let page = &self.pages[page_idx];
|
||||||
write_glyph(&mut self.pages[upload.layer as usize].image, image, x, y);
|
|
||||||
self.uploads.push(upload);
|
|
||||||
|
|
||||||
let scale = 1.0 / PAGE as f32;
|
let img = textures.image_mut(&page.handle);
|
||||||
let entry = GlyphEntry {
|
let rgba = img.as_mut_rgba8().expect("atlas page is rgba8");
|
||||||
uv_min: Vec2::new(x as f32 * scale, y as f32 * scale),
|
write_glyph(rgba, image, x, y);
|
||||||
uv_max: Vec2::new((x + w) as f32 * scale, (y + h) as f32 * scale),
|
|
||||||
left: image.placement.left,
|
|
||||||
top: image.placement.top,
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
is_colored: matches!(image.content, Content::Color),
|
|
||||||
layer: upload.layer,
|
|
||||||
};
|
|
||||||
self.entries.insert(key, Some(entry));
|
|
||||||
Some(entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reserves room for a `w` by `h` glyph, adding a page if none has it.
|
let handle = page.handle.clone();
|
||||||
fn allocate(&mut self, w: u32, h: u32) -> PageUpload {
|
let rect = PatchRect {
|
||||||
let rect = |x, y| PatchRect {
|
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
};
|
};
|
||||||
if let Some((i, (x, y))) = self
|
textures.patch(&handle, rect);
|
||||||
.pages
|
|
||||||
.iter_mut()
|
let page = &self.pages[page_idx];
|
||||||
.enumerate()
|
let scale = 1.0 / PAGE as f32;
|
||||||
.find_map(|(i, page)| page.allocate(w, h).map(|position| (i, position)))
|
let entry = GlyphEntry {
|
||||||
{
|
uv_min: [x as f32 * scale, y as f32 * scale],
|
||||||
return PageUpload {
|
uv_max: [(x + w) as f32 * scale, (y + h) as f32 * scale],
|
||||||
layer: i as u32,
|
left: image.placement.left,
|
||||||
rect: rect(x, y),
|
top: image.placement.top,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
is_color: matches!(image.content, Content::Color),
|
||||||
|
view_idx: page.handle.primitive().view_idx,
|
||||||
|
sampler_idx: page.handle.primitive().sampler_idx,
|
||||||
};
|
};
|
||||||
|
self.entries.insert(key, Some(entry));
|
||||||
|
Some(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn allocate(&mut self, w: u32, h: u32, textures: &mut Textures) -> (usize, u32, u32) {
|
||||||
|
let need_w = w + PAD;
|
||||||
|
let need_h = h + PAD;
|
||||||
|
if let Some(i) = self.pages.iter().position(|p| fits(p, need_w, need_h)) {
|
||||||
|
let page = &mut self.pages[i];
|
||||||
|
if page.x + need_w > PAGE {
|
||||||
|
page.y += page.shelf_height;
|
||||||
|
page.x = PAD;
|
||||||
|
page.shelf_height = 0;
|
||||||
|
}
|
||||||
|
let (x, y) = (page.x, page.y);
|
||||||
|
page.x += need_w;
|
||||||
|
page.shelf_height = page.shelf_height.max(need_h);
|
||||||
|
return (i, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = textures.add(RgbaImage::new(PAGE, PAGE));
|
||||||
self.pages.push(Page {
|
self.pages.push(Page {
|
||||||
image: RgbaImage::new(PAGE, PAGE),
|
handle,
|
||||||
x: PAD + w + PAD,
|
x: PAD + w + PAD,
|
||||||
y: PAD,
|
y: PAD,
|
||||||
shelf_height: h + PAD,
|
shelf_height: h + PAD,
|
||||||
});
|
});
|
||||||
PageUpload {
|
(self.pages.len() - 1, PAD, PAD)
|
||||||
layer: self.pages.len() as u32 - 1,
|
|
||||||
rect: rect(PAD, PAD),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drains what has been written since the last call, for the renderer to
|
|
||||||
/// upload. A new page needs nothing more: wgpu leaves the rest of a fresh
|
|
||||||
/// layer transparent, which is what an atlas wants.
|
|
||||||
pub fn uploads(&mut self) -> impl Iterator<Item = (PageUpload, &RgbaImage)> {
|
|
||||||
let pages = &self.pages;
|
|
||||||
self.uploads
|
|
||||||
.drain(..)
|
|
||||||
.map(|upload| (upload, &pages[upload.layer as usize].image))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_empty(&mut self, key: GlyphKey) {
|
pub fn insert_empty(&mut self, key: GlyphKey) {
|
||||||
self.entries.insert(key, None);
|
self.entries.insert(key, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn page_count(&self) -> u32 {
|
pub fn page_count(&self) -> usize {
|
||||||
self.pages.len() as u32
|
self.pages.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn glyph_count(&self) -> usize {
|
pub fn glyph_count(&self) -> usize {
|
||||||
@@ -173,65 +148,45 @@ impl GlyphAtlas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
fn fits(page: &Page, need_w: u32, need_h: u32) -> bool {
|
||||||
fn allocate(&mut self, w: u32, h: u32) -> Option<(u32, u32)> {
|
(page.x + need_w <= PAGE && page.y + need_h <= PAGE)
|
||||||
let need_w = w + PAD;
|
|| (need_w + PAD <= PAGE && page.y + page.shelf_height + need_h <= PAGE)
|
||||||
let need_h = h + PAD;
|
|
||||||
if self.x + need_w > PAGE {
|
|
||||||
if need_w + PAD > PAGE || self.y + self.shelf_height + need_h > PAGE {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.y += self.shelf_height;
|
|
||||||
self.x = PAD;
|
|
||||||
self.shelf_height = 0;
|
|
||||||
} else if self.y + need_h > PAGE {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let position = (self.x, self.y);
|
|
||||||
self.x += need_w;
|
|
||||||
self.shelf_height = self.shelf_height.max(need_h);
|
|
||||||
Some(position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mask glyphs keep coverage in alpha so their raster can be tinted at draw time.
|
/// Mask glyphs keep coverage in alpha so their raster can be tinted at draw time.
|
||||||
fn write_glyph(page: &mut RgbaImage, image: &Image, x: u32, y: u32) {
|
fn write_glyph(page: &mut RgbaImage, image: &Image, x: u32, y: u32) {
|
||||||
let width = image.placement.width as usize;
|
let w = image.placement.width;
|
||||||
let height = image.placement.height as usize;
|
let h = image.placement.height;
|
||||||
let page_stride = page.width() as usize * 4;
|
|
||||||
let x = x as usize * 4;
|
|
||||||
let y = y as usize;
|
|
||||||
let page = page.as_mut();
|
|
||||||
|
|
||||||
for row in 0..height {
|
|
||||||
let start = (y + row) * page_stride + x;
|
|
||||||
let target = &mut page[start..start + width * 4];
|
|
||||||
match image.content {
|
match image.content {
|
||||||
Content::Color => {
|
|
||||||
let start = row * width * 4;
|
|
||||||
target.copy_from_slice(&image.data[start..start + width * 4]);
|
|
||||||
}
|
|
||||||
Content::Mask => {
|
Content::Mask => {
|
||||||
let start = row * width;
|
for row in 0..h {
|
||||||
for (target, &alpha) in target
|
for col in 0..w {
|
||||||
.as_chunks_mut::<4>()
|
let a = image.data[(row * w + col) as usize];
|
||||||
.0
|
page.put_pixel(x + col, y + row, image::Rgba([255, 255, 255, a]));
|
||||||
.iter_mut()
|
}
|
||||||
.zip(&image.data[start..start + width])
|
}
|
||||||
{
|
}
|
||||||
target.copy_from_slice(&[255, 255, 255, alpha]);
|
Content::Color => {
|
||||||
|
for row in 0..h {
|
||||||
|
for col in 0..w {
|
||||||
|
let i = ((row * w + col) * 4) as usize;
|
||||||
|
let px = [
|
||||||
|
image.data[i],
|
||||||
|
image.data[i + 1],
|
||||||
|
image.data[i + 2],
|
||||||
|
image.data[i + 3],
|
||||||
|
];
|
||||||
|
page.put_pixel(x + col, y + row, image::Rgba(px));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Content::SubpixelMask => {
|
Content::SubpixelMask => {
|
||||||
let start = row * width * 4;
|
// Preserve readable output if the rasterizer returns a subpixel mask.
|
||||||
for (target, source) in target
|
for row in 0..h {
|
||||||
.as_chunks_mut::<4>()
|
for col in 0..w {
|
||||||
.0
|
let i = ((row * w + col) * 4) as usize;
|
||||||
.iter_mut()
|
let a = image.data[i + 1];
|
||||||
.zip(image.data[start..start + width * 4].as_chunks::<4>().0)
|
page.put_pixel(x + col, y + row, image::Rgba([255, 255, 255, a]));
|
||||||
{
|
|
||||||
target.copy_from_slice(&[255, 255, 255, source[1]]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,20 @@ pub struct WindowUniform {
|
|||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct PrimitiveInstance {
|
pub struct PrimitiveInstance {
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
|
pub binding: u32,
|
||||||
|
pub idx: u32,
|
||||||
pub mask_idx: MaskIdx,
|
pub mask_idx: MaskIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveInstance {
|
impl PrimitiveInstance {
|
||||||
const ATTRIBS: [VertexAttribute; 5] = vertex_attr_array![
|
const ATTRIBS: [VertexAttribute; 7] = vertex_attr_array![
|
||||||
0 => Float32x2,
|
0 => Float32x2,
|
||||||
1 => Float32x2,
|
1 => Float32x2,
|
||||||
2 => Float32x2,
|
2 => Float32x2,
|
||||||
3 => Float32x2,
|
3 => Float32x2,
|
||||||
4 => Uint32,
|
4 => Uint32,
|
||||||
|
5 => Uint32,
|
||||||
|
6 => Uint32,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn desc() -> VertexBufferLayout<'static> {
|
pub fn desc() -> VertexBufferLayout<'static> {
|
||||||
|
|||||||
+191
-200
@@ -1,6 +1,8 @@
|
|||||||
|
use std::num::NonZero;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
UiData, UiRenderState,
|
UiData, UiRenderState,
|
||||||
render::{data::PrimitiveInstance, util::ArrBuf},
|
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
|
||||||
util::{HashMap, Vec2},
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
use data::WindowUniform;
|
use data::WindowUniform;
|
||||||
@@ -11,7 +13,6 @@ use wgpu::{
|
|||||||
|
|
||||||
mod atlas;
|
mod atlas;
|
||||||
mod data;
|
mod data;
|
||||||
mod page;
|
|
||||||
mod primitive;
|
mod primitive;
|
||||||
mod texture;
|
mod texture;
|
||||||
mod util;
|
mod util;
|
||||||
@@ -20,63 +21,42 @@ pub use atlas::*;
|
|||||||
pub use data::{Mask, MaskIdx};
|
pub use data::{Mask, MaskIdx};
|
||||||
pub use primitive::*;
|
pub use primitive::*;
|
||||||
|
|
||||||
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
|
||||||
|
|
||||||
pub struct UiRenderNode {
|
pub struct UiRenderNode {
|
||||||
shared_layout: BindGroupLayout,
|
uniform_group: BindGroup,
|
||||||
shared_group: BindGroup,
|
primitive_layout: BindGroupLayout,
|
||||||
format: TextureFormat,
|
rsc_layout: BindGroupLayout,
|
||||||
|
rsc_group: BindGroup,
|
||||||
|
|
||||||
/// One per registered primitive, in id order.
|
pipeline: RenderPipeline,
|
||||||
primitives: Vec<PrimitivePipeline>,
|
|
||||||
|
|
||||||
layers: HashMap<usize, RenderLayer>,
|
layers: HashMap<usize, RenderLayer>,
|
||||||
active: Vec<usize>,
|
active: Vec<usize>,
|
||||||
window_buffer: Buffer,
|
window_buffer: Buffer,
|
||||||
|
textures: GpuTextures,
|
||||||
masks: ArrBuf<Mask>,
|
masks: ArrBuf<Mask>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RenderLayer {
|
struct RenderLayer {
|
||||||
/// One per registered primitive, `None` where this layer draws none.
|
|
||||||
primitives: Vec<Option<ListBuffers>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What draws one registered primitive.
|
|
||||||
struct PrimitivePipeline {
|
|
||||||
data_layout: BindGroupLayout,
|
|
||||||
pipeline: RenderPipeline,
|
|
||||||
render: Box<dyn PrimitiveRender>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One list's vertex buffer and the data its shader reads.
|
|
||||||
struct ListBuffers {
|
|
||||||
instance: ArrBuf<PrimitiveInstance>,
|
instance: ArrBuf<PrimitiveInstance>,
|
||||||
data: ArrBuf<u8>,
|
primitives: PrimitiveBuffers,
|
||||||
group: Option<BindGroup>,
|
primitive_group: BindGroup,
|
||||||
/// What the primitive asked to keep per instance, if anything.
|
|
||||||
bindings: Vec<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiRenderNode {
|
impl UiRenderNode {
|
||||||
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
|
||||||
pass.set_bind_group(0, &self.shared_group, &[]);
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
pass.set_bind_group(0, &self.uniform_group, &[]);
|
||||||
|
pass.set_bind_group(2, &self.rsc_group, &[]);
|
||||||
for i in &self.active {
|
for i in &self.active {
|
||||||
let layer = &self.layers[i];
|
let layer = &self.layers[i];
|
||||||
for (id, list) in layer.primitives.iter().enumerate() {
|
if layer.instance.len() == 0 {
|
||||||
let Some(list) = list else { continue };
|
continue;
|
||||||
let Some(group) = &list.group else { continue };
|
|
||||||
let primitive = &self.primitives[id];
|
|
||||||
pass.set_pipeline(&primitive.pipeline);
|
|
||||||
pass.set_bind_group(1, group, &[]);
|
|
||||||
pass.set_vertex_buffer(0, list.instance.buffer.slice(..));
|
|
||||||
primitive.render.draw(
|
|
||||||
pass,
|
|
||||||
ListDraw {
|
|
||||||
instances: list.instance.len() as u32,
|
|
||||||
bindings: &list.bindings,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
pass.set_bind_group(1, &layer.primitive_group, &[]);
|
||||||
|
pass.set_vertex_buffer(0, layer.instance.buffer.slice(..));
|
||||||
|
pass.draw(0..4, 0..layer.instance.len() as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,56 +67,55 @@ impl UiRenderNode {
|
|||||||
ui: &mut UiData,
|
ui: &mut UiData,
|
||||||
ui_render: &mut UiRenderState,
|
ui_render: &mut UiRenderState,
|
||||||
) {
|
) {
|
||||||
// Before the layers: each list is given its pipeline's data layout.
|
|
||||||
self.build_pipelines(device, queue, &ui.primitives);
|
|
||||||
self.active.clear();
|
self.active.clear();
|
||||||
for (i, draws) in ui_render.layers.iter_mut() {
|
for (i, primitives) in ui_render.layers.iter_mut() {
|
||||||
self.active.push(i);
|
self.active.push(i);
|
||||||
for change in draws.apply_free() {
|
for change in primitives.apply_free() {
|
||||||
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
if let Some(inst) = ui_render.active.get_mut(&change.id) {
|
||||||
for h in &mut inst.primitives {
|
for h in &mut inst.primitives {
|
||||||
if h.layer == i && h.kind == change.kind && h.inst_idx == change.old {
|
if h.layer == i && h.inst_idx == change.old {
|
||||||
h.inst_idx = change.new;
|
h.inst_idx = change.new;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let rlayer = self.layers.entry(i).or_insert_with(RenderLayer::new);
|
let rlayer = self.layers.entry(i).or_insert_with(|| {
|
||||||
if draws.updated {
|
let primitives = PrimitiveBuffers::new(device);
|
||||||
let lists = draws.primitives();
|
let primitive_group =
|
||||||
// The zip would otherwise skip a list with no pipeline.
|
Self::primitive_group(device, &self.primitive_layout, primitives.buffers());
|
||||||
assert!(lists.len() <= self.primitives.len());
|
RenderLayer {
|
||||||
rlayer.primitives.resize_with(lists.len(), || None);
|
instance: ArrBuf::new(
|
||||||
for ((buffers, list), primitive) in rlayer
|
device,
|
||||||
.primitives
|
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
||||||
.iter_mut()
|
"instance",
|
||||||
.zip(lists)
|
),
|
||||||
.zip(&self.primitives)
|
primitives,
|
||||||
{
|
primitive_group,
|
||||||
let Some(list) = list else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
buffers
|
|
||||||
.get_or_insert_with(|| ListBuffers::new(device))
|
|
||||||
.update(device, queue, primitive, list);
|
|
||||||
}
|
}
|
||||||
draws.updated = false;
|
});
|
||||||
|
if primitives.updated {
|
||||||
|
rlayer
|
||||||
|
.instance
|
||||||
|
.update(device, queue, primitives.instances());
|
||||||
|
rlayer.primitives.update(device, queue, primitives.data());
|
||||||
|
rlayer.primitive_group = Self::primitive_group(
|
||||||
|
device,
|
||||||
|
&self.primitive_layout,
|
||||||
|
rlayer.primitives.buffers(),
|
||||||
|
);
|
||||||
|
primitives.updated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for primitive in &mut self.primitives {
|
let mut changed = false;
|
||||||
primitive.render.update(ui);
|
changed |= self.textures.update(&mut ui.textures);
|
||||||
}
|
|
||||||
if ui.masks.changed {
|
if ui.masks.changed {
|
||||||
ui.masks.changed = false;
|
ui.masks.changed = false;
|
||||||
if self.masks.update(device, queue, &ui.masks[..]) {
|
self.masks.update(device, queue, &ui.masks[..]);
|
||||||
self.shared_group = Self::shared_group(
|
changed = true;
|
||||||
device,
|
|
||||||
&self.shared_layout,
|
|
||||||
&self.window_buffer,
|
|
||||||
&self.masks,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
if changed {
|
||||||
|
self.rsc_group = Self::rsc_group(device, &self.rsc_layout, &self.textures, &self.masks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +128,17 @@ impl UiRenderNode {
|
|||||||
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
pub fn new(
|
||||||
|
device: &Device,
|
||||||
|
queue: &Queue,
|
||||||
|
config: &SurfaceConfiguration,
|
||||||
|
limits: UiLimits,
|
||||||
|
) -> Self {
|
||||||
|
let shader = device.create_shader_module(ShaderModuleDescriptor {
|
||||||
|
label: Some("UI Shape Shader"),
|
||||||
|
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
|
||||||
|
});
|
||||||
|
|
||||||
let window_uniform = WindowUniform {
|
let window_uniform = WindowUniform {
|
||||||
width: config.width as f32,
|
width: config.width as f32,
|
||||||
height: config.height as f32,
|
height: config.height as f32,
|
||||||
@@ -160,73 +149,67 @@ impl UiRenderNode {
|
|||||||
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
|
||||||
});
|
});
|
||||||
|
|
||||||
let shared_layout = Self::shared_layout(device);
|
let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &[BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}],
|
||||||
|
label: Some("window"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let uniform_group = Self::bind_group_0(device, &uniform_layout, &window_buffer);
|
||||||
|
|
||||||
|
let primitive_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
|
entries: &core::array::from_fn::<_, { PrimitiveBuffers::LEN }, _>(|i| {
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: i as u32,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
label: Some("primitive"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let tex_manager = GpuTextures::new(device, queue);
|
||||||
let masks = ArrBuf::new(
|
let masks = ArrBuf::new(
|
||||||
device,
|
device,
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
"ui masks",
|
"ui masks",
|
||||||
);
|
);
|
||||||
let shared_group = Self::shared_group(device, &shared_layout, &window_buffer, &masks);
|
|
||||||
|
|
||||||
Self {
|
let rsc_layout = Self::rsc_layout(device, &limits);
|
||||||
shared_layout,
|
let rsc_group = Self::rsc_group(device, &rsc_layout, &tex_manager, &masks);
|
||||||
shared_group,
|
|
||||||
format: config.format,
|
|
||||||
primitives: Vec::new(),
|
|
||||||
window_buffer,
|
|
||||||
layers: HashMap::default(),
|
|
||||||
active: Vec::new(),
|
|
||||||
masks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compiles a pipeline for every primitive registered since the last call.
|
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
||||||
/// Sources only ever arrive at the end, so an id keeps its pipeline.
|
label: Some("UI Shape Pipeline Layout"),
|
||||||
fn build_pipelines(&mut self, device: &Device, queue: &Queue, registry: &PrimitiveRegistry) {
|
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
|
||||||
for source in ®istry.sources()[self.primitives.len()..] {
|
|
||||||
let render = (source.render)(device, queue);
|
|
||||||
let data_layout = Self::data_layout(device, source.stride);
|
|
||||||
let mut groups = vec![Some(&self.shared_layout), Some(&data_layout)];
|
|
||||||
groups.extend(render.layout().map(Some));
|
|
||||||
let layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
|
|
||||||
label: Some(source.label),
|
|
||||||
bind_group_layouts: &groups,
|
|
||||||
immediate_size: 0,
|
immediate_size: 0,
|
||||||
});
|
});
|
||||||
let pipeline = Self::pipeline(device, &layout, self.format, source.wgsl, source.label);
|
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
|
||||||
self.primitives.push(PrimitivePipeline {
|
label: Some("UI Shape Pipeline"),
|
||||||
data_layout,
|
layout: Some(&pipeline_layout),
|
||||||
pipeline,
|
|
||||||
render,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pipeline(
|
|
||||||
device: &Device,
|
|
||||||
layout: &PipelineLayout,
|
|
||||||
format: TextureFormat,
|
|
||||||
wgsl: &str,
|
|
||||||
label: &str,
|
|
||||||
) -> RenderPipeline {
|
|
||||||
let module = device.create_shader_module(ShaderModuleDescriptor {
|
|
||||||
label: Some(label),
|
|
||||||
source: ShaderSource::Wgsl(format!("{PRELUDE}\n{wgsl}").into()),
|
|
||||||
});
|
|
||||||
device.create_render_pipeline(&RenderPipelineDescriptor {
|
|
||||||
label: Some(label),
|
|
||||||
layout: Some(layout),
|
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
module: &module,
|
module: &shader,
|
||||||
entry_point: Some("vs_main"),
|
entry_point: Some("vs_main"),
|
||||||
buffers: &[Some(PrimitiveInstance::desc())],
|
buffers: &[PrimitiveInstance::desc()],
|
||||||
compilation_options: Default::default(),
|
compilation_options: Default::default(),
|
||||||
},
|
},
|
||||||
fragment: Some(FragmentState {
|
fragment: Some(FragmentState {
|
||||||
module: &module,
|
module: &shader,
|
||||||
entry_point: Some("fs_main"),
|
entry_point: Some("fs_main"),
|
||||||
targets: &[Some(ColorTargetState {
|
targets: &[Some(ColorTargetState {
|
||||||
format,
|
format: config.format,
|
||||||
blend: Some(BlendState::ALPHA_BLENDING),
|
blend: Some(BlendState::ALPHA_BLENDING),
|
||||||
write_mask: ColorWrites::ALL,
|
write_mask: ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
@@ -249,42 +232,90 @@ impl UiRenderNode {
|
|||||||
},
|
},
|
||||||
multiview_mask: None,
|
multiview_mask: None,
|
||||||
cache: None,
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
uniform_group,
|
||||||
|
primitive_layout,
|
||||||
|
rsc_layout,
|
||||||
|
rsc_group,
|
||||||
|
pipeline,
|
||||||
|
window_buffer,
|
||||||
|
layers: HashMap::default(),
|
||||||
|
active: Vec::new(),
|
||||||
|
textures: tex_manager,
|
||||||
|
masks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind_group_0(
|
||||||
|
device: &Device,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
window_buffer: &Buffer,
|
||||||
|
) -> BindGroup {
|
||||||
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
layout,
|
||||||
|
entries: &[BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: window_buffer.as_entire_binding(),
|
||||||
|
}],
|
||||||
|
label: Some("ui window"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What every draw in the ui is given: the window and the masks.
|
fn primitive_group(
|
||||||
fn shared_layout(device: &Device) -> BindGroupLayout {
|
device: &Device,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
buffers: [(u32, &Buffer); PrimitiveBuffers::LEN],
|
||||||
|
) -> BindGroup {
|
||||||
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
|
layout,
|
||||||
|
entries: &buffers.map(|(binding, buf)| BindGroupEntry {
|
||||||
|
binding,
|
||||||
|
resource: buf.as_entire_binding(),
|
||||||
|
}),
|
||||||
|
label: Some("ui primitives"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rsc_layout(device: &Device, limits: &UiLimits) -> BindGroupLayout {
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
visibility: ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Texture {
|
||||||
ty: BufferBindingType::Uniform,
|
sample_type: TextureSampleType::Float { filterable: false },
|
||||||
has_dynamic_offset: false,
|
view_dimension: TextureViewDimension::D2,
|
||||||
min_binding_size: BufferSize::new(size_of::<WindowUniform>() as u64),
|
multisampled: false,
|
||||||
},
|
},
|
||||||
count: None,
|
count: Some(NonZero::new(limits.max_textures).unwrap()),
|
||||||
},
|
},
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
visibility: ShaderStages::FRAGMENT,
|
visibility: ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
||||||
|
count: Some(NonZero::new(limits.max_samplers).unwrap()),
|
||||||
|
},
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: ShaderStages::FRAGMENT,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
min_binding_size: BufferSize::new(size_of::<Mask>() as u64),
|
min_binding_size: None,
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui shared"),
|
label: Some("ui rsc"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shared_group(
|
fn rsc_group(
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
window: &Buffer,
|
tex_manager: &GpuTextures,
|
||||||
masks: &ArrBuf<Mask>,
|
masks: &ArrBuf<Mask>,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
@@ -292,85 +323,45 @@ impl UiRenderNode {
|
|||||||
entries: &[
|
entries: &[
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
resource: window.as_entire_binding(),
|
resource: BindingResource::TextureViewArray(&tex_manager.views()),
|
||||||
},
|
},
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
binding: 1,
|
binding: 1,
|
||||||
|
resource: BindingResource::SamplerArray(&tex_manager.samplers()),
|
||||||
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
resource: masks.buffer.as_entire_binding(),
|
resource: masks.buffer.as_entire_binding(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui shared"),
|
label: Some("ui rsc"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout for a list of one primitive's data. Every size in the ui is
|
pub fn view_count(&self) -> usize {
|
||||||
/// stated, so "is the buffer big enough for one entry?" is answered when
|
self.textures.view_count()
|
||||||
/// the bind group is made; a `None` size is wgpu's to check on every draw.
|
|
||||||
fn data_layout(device: &Device, stride: u64) -> BindGroupLayout {
|
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
||||||
entries: &[BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Buffer {
|
|
||||||
ty: BufferBindingType::Storage { read_only: true },
|
|
||||||
has_dynamic_offset: false,
|
|
||||||
min_binding_size: BufferSize::new(stride),
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
}],
|
|
||||||
label: Some("ui primitive data"),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderLayer {
|
pub struct UiLimits {
|
||||||
fn new() -> Self {
|
max_textures: u32,
|
||||||
|
max_samplers: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UiLimits {
|
||||||
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
primitives: Vec::new(),
|
max_textures: 100000,
|
||||||
|
max_samplers: 1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListBuffers {
|
impl UiLimits {
|
||||||
fn new(device: &Device) -> Self {
|
pub fn max_binding_array_elements_per_shader_stage(&self) -> u32 {
|
||||||
Self {
|
self.max_textures + self.max_samplers
|
||||||
instance: ArrBuf::new(
|
|
||||||
device,
|
|
||||||
BufferUsages::VERTEX | BufferUsages::COPY_DST,
|
|
||||||
"instance",
|
|
||||||
),
|
|
||||||
data: ArrBuf::new(
|
|
||||||
device,
|
|
||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
|
||||||
"primitive data",
|
|
||||||
),
|
|
||||||
group: None,
|
|
||||||
bindings: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(
|
|
||||||
&mut self,
|
|
||||||
device: &Device,
|
|
||||||
queue: &Queue,
|
|
||||||
primitive: &PrimitivePipeline,
|
|
||||||
list: &InstanceList,
|
|
||||||
) {
|
|
||||||
self.bindings.clear();
|
|
||||||
primitive.render.instance_bindings(list, &mut self.bindings);
|
|
||||||
self.instance.update(device, queue, list.instances());
|
|
||||||
let resized = self.data.update(device, queue, list.data());
|
|
||||||
if list.instances().is_empty() {
|
|
||||||
self.group = None;
|
|
||||||
} else if resized || self.group.is_none() {
|
|
||||||
self.group = Some(device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout: &primitive.data_layout,
|
|
||||||
entries: &[BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: self.data.buffer.as_entire_binding(),
|
|
||||||
}],
|
|
||||||
label: Some("ui primitive data"),
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
pub fn max_binding_array_sampler_elements_per_shader_stage(&self) -> u32 {
|
||||||
|
self.max_samplers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
use wgpu::*;
|
|
||||||
|
|
||||||
use crate::{GlyphAtlas, UiData};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
atlas::PAGE,
|
|
||||||
primitive::{ListDraw, PrimitiveRender},
|
|
||||||
texture::{default_sampler, sampled_group, sampled_layout, write_region},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Draws glyphs from the atlas, which it owns: one array texture bound once
|
|
||||||
/// for a whole list, since every glyph in it reads the same pages.
|
|
||||||
pub struct GlyphRender {
|
|
||||||
pages: GpuPages,
|
|
||||||
layout: BindGroupLayout,
|
|
||||||
sampler: Sampler,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlyphRender {
|
|
||||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
|
||||||
let layout = sampled_layout(device, TextureViewDimension::D2Array, "ui atlas");
|
|
||||||
let sampler = default_sampler(device);
|
|
||||||
Self {
|
|
||||||
pages: GpuPages::new(device, queue, &layout, &sampler),
|
|
||||||
layout,
|
|
||||||
sampler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveRender for GlyphRender {
|
|
||||||
fn layout(&self) -> Option<&BindGroupLayout> {
|
|
||||||
Some(&self.layout)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ui: &mut UiData) {
|
|
||||||
self.pages
|
|
||||||
.update(&mut ui.text.atlas, &self.layout, &self.sampler);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
|
||||||
pass.set_bind_group(2, self.pages.group(), &[]);
|
|
||||||
pass.draw(0..4, 0..list.instances);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The glyph atlas on the GPU: one array texture whose layers are the pages
|
|
||||||
/// `GlyphAtlas` packs.
|
|
||||||
///
|
|
||||||
/// One array rather than a texture per page because a layer index is ordinary
|
|
||||||
/// Vulkan 1.0 / GLES sampling, where a `binding_array` would need
|
|
||||||
/// `VK_EXT_descriptor_indexing`, which a real share of Android GPUs lack.
|
|
||||||
pub struct GpuPages {
|
|
||||||
device: Device,
|
|
||||||
queue: Queue,
|
|
||||||
texture: Texture,
|
|
||||||
group: BindGroup,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GpuPages {
|
|
||||||
pub fn new(
|
|
||||||
device: &Device,
|
|
||||||
queue: &Queue,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
sampler: &Sampler,
|
|
||||||
) -> Self {
|
|
||||||
let texture = create_array(device, 1);
|
|
||||||
Self {
|
|
||||||
device: device.clone(),
|
|
||||||
queue: queue.clone(),
|
|
||||||
group: atlas_group(device, layout, &texture, sampler),
|
|
||||||
texture,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, atlas: &mut GlyphAtlas, layout: &BindGroupLayout, sampler: &Sampler) {
|
|
||||||
if atlas.page_count() > self.texture.depth_or_array_layers() {
|
|
||||||
self.grow(atlas.page_count(), layout, sampler);
|
|
||||||
}
|
|
||||||
for (upload, page) in atlas.uploads() {
|
|
||||||
let dst = TexelCopyTextureInfo {
|
|
||||||
texture: &self.texture,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: Origin3d {
|
|
||||||
x: upload.rect.x,
|
|
||||||
y: upload.rect.y,
|
|
||||||
z: upload.layer,
|
|
||||||
},
|
|
||||||
aspect: TextureAspect::All,
|
|
||||||
};
|
|
||||||
write_region(&self.queue, dst, page, upload.rect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn group(&self) -> &BindGroup {
|
|
||||||
&self.group
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Doubles until `needed` fits and copies the old layers across GPU side.
|
|
||||||
/// The new texture stales the group, so that is rebuilt here.
|
|
||||||
fn grow(&mut self, needed: u32, layout: &BindGroupLayout, sampler: &Sampler) {
|
|
||||||
let old = self.texture.depth_or_array_layers();
|
|
||||||
let mut layers = old;
|
|
||||||
while layers < needed {
|
|
||||||
layers *= 2;
|
|
||||||
}
|
|
||||||
let texture = create_array(&self.device, layers);
|
|
||||||
let mut encoder = self
|
|
||||||
.device
|
|
||||||
.create_command_encoder(&CommandEncoderDescriptor {
|
|
||||||
label: Some("atlas grow"),
|
|
||||||
});
|
|
||||||
encoder.copy_texture_to_texture(
|
|
||||||
self.texture.as_image_copy(),
|
|
||||||
texture.as_image_copy(),
|
|
||||||
Extent3d {
|
|
||||||
width: PAGE,
|
|
||||||
height: PAGE,
|
|
||||||
depth_or_array_layers: old,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
|
||||||
self.group = atlas_group(&self.device, layout, &texture, sampler);
|
|
||||||
self.texture = texture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn atlas_group(
|
|
||||||
device: &Device,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
texture: &Texture,
|
|
||||||
sampler: &Sampler,
|
|
||||||
) -> BindGroup {
|
|
||||||
let view = texture.create_view(&TextureViewDescriptor {
|
|
||||||
dimension: Some(TextureViewDimension::D2Array),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
sampled_group(device, layout, &view, sampler, "ui atlas")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_array(device: &Device, layers: u32) -> Texture {
|
|
||||||
device.create_texture(&TextureDescriptor {
|
|
||||||
label: Some("glyph atlas"),
|
|
||||||
size: Extent3d {
|
|
||||||
width: PAGE,
|
|
||||||
height: PAGE,
|
|
||||||
depth_or_array_layers: layers,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: TextureDimension::D2,
|
|
||||||
format: TextureFormat::Rgba8Unorm,
|
|
||||||
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,
|
|
||||||
view_formats: &[],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+207
-304
@@ -1,247 +1,114 @@
|
|||||||
use std::{any::TypeId, marker::PhantomData};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Color, TextureHandle, UiData, UiRegion, WidgetId,
|
Color, UiRegion, WidgetId,
|
||||||
render::{
|
render::{
|
||||||
|
ArrBuf,
|
||||||
data::{MaskIdx, PrimitiveInstance},
|
data::{MaskIdx, PrimitiveInstance},
|
||||||
page::GlyphRender,
|
|
||||||
texture::ImageRender,
|
|
||||||
},
|
},
|
||||||
util::{HashMap, Vec2},
|
|
||||||
};
|
};
|
||||||
use bytemuck::Pod;
|
use bytemuck::Pod;
|
||||||
use wgpu::{BindGroupLayout, Device, Queue, RenderPass};
|
use wgpu::*;
|
||||||
|
|
||||||
/// One instance of a primitive, laid out as the struct its shader reads.
|
pub struct Primitives {
|
||||||
///
|
|
||||||
/// The type carries its own shader, so drawing one is all the wiring it needs:
|
|
||||||
/// its list, free list, buffers and pipeline follow from being registered.
|
|
||||||
pub trait Primitive: Pod + 'static {
|
|
||||||
/// Compiled after `prelude.wgsl`, which states what it declares and what
|
|
||||||
/// it is given.
|
|
||||||
const WGSL: &'static str;
|
|
||||||
|
|
||||||
/// Made once, the first time the renderer sees this primitive. It owns
|
|
||||||
/// whatever the shader samples and records the primitive's own draws; the
|
|
||||||
/// default owns nothing and draws every instance in one call.
|
|
||||||
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender>
|
|
||||||
where
|
|
||||||
Self: Sized,
|
|
||||||
{
|
|
||||||
let _ = (device, queue);
|
|
||||||
Box::new(Instanced)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The renderer's half of a primitive: what it samples, what it uploads, and
|
|
||||||
/// what draws it records.
|
|
||||||
///
|
|
||||||
/// Everything a draw shares -- the pipeline, the window and masks, the list's
|
|
||||||
/// own data and instance buffer -- is set before this is called. What is left
|
|
||||||
/// is what only this primitive knows: its group 2, and how many draws its
|
|
||||||
/// instances are.
|
|
||||||
pub trait PrimitiveRender {
|
|
||||||
/// The layout its shader reads at group 2. `None` for a primitive whose
|
|
||||||
/// shader samples nothing, whose pipeline then has no group 2 at all.
|
|
||||||
fn layout(&self) -> Option<&BindGroupLayout> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uploads whatever this primitive owns, once a frame, before any draw.
|
|
||||||
fn update(&mut self, ui: &mut UiData) {
|
|
||||||
let _ = ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Keeps what the primitive needs per instance at draw time, read from
|
|
||||||
/// the list's own data. A primitive that binds nothing per instance --
|
|
||||||
/// most of them -- leaves this empty and draws in one call.
|
|
||||||
fn instance_bindings(&self, list: &InstanceList, out: &mut Vec<u32>) {
|
|
||||||
let _ = (list, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What a `PrimitiveRender` draws: this list's instances, and whatever
|
|
||||||
/// `instance_bindings` kept for them.
|
|
||||||
pub struct ListDraw<'a> {
|
|
||||||
pub instances: u32,
|
|
||||||
pub bindings: &'a [u32],
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default: nothing sampled, every instance in one call.
|
|
||||||
pub struct Instanced;
|
|
||||||
|
|
||||||
impl PrimitiveRender for Instanced {
|
|
||||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
|
||||||
pass.draw(0..4, 0..list.instances);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Which registered primitive an instance is.
|
|
||||||
pub struct PrimitiveKind<P> {
|
|
||||||
id: u32,
|
|
||||||
_p: PhantomData<fn(P)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> PrimitiveKind<P> {
|
|
||||||
fn new(id: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
id,
|
|
||||||
_p: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> Clone for PrimitiveKind<P> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> Copy for PrimitiveKind<P> {}
|
|
||||||
|
|
||||||
/// Every primitive a ui can draw, in the order they were first drawn.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PrimitiveRegistry {
|
|
||||||
kinds: Vec<PrimitiveSource>,
|
|
||||||
ids: HashMap<TypeId, u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveSource {
|
|
||||||
pub wgsl: &'static str,
|
|
||||||
pub label: &'static str,
|
|
||||||
/// Size of one instance's entry, stated as the data binding's minimum.
|
|
||||||
pub stride: u64,
|
|
||||||
pub render: fn(&Device, &Queue) -> Box<dyn PrimitiveRender>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveRegistry {
|
|
||||||
/// Registers `P` if this is the first time it has been drawn.
|
|
||||||
pub fn kind<P: Primitive>(&mut self) -> PrimitiveKind<P> {
|
|
||||||
let Self { kinds, ids } = self;
|
|
||||||
let id = *ids.entry(TypeId::of::<P>()).or_insert_with(|| {
|
|
||||||
kinds.push(PrimitiveSource {
|
|
||||||
wgsl: P::WGSL,
|
|
||||||
label: std::any::type_name::<P>(),
|
|
||||||
stride: size_of::<P>() as u64,
|
|
||||||
render: P::render,
|
|
||||||
});
|
|
||||||
kinds.len() as u32 - 1
|
|
||||||
});
|
|
||||||
PrimitiveKind::new(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sources(&self) -> &[PrimitiveSource] {
|
|
||||||
&self.kinds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One registered primitive's instances in one layer. Everything per-instance
|
|
||||||
/// rides here, so it stays in step through a `swap_remove`.
|
|
||||||
pub struct InstanceList {
|
|
||||||
instances: Vec<PrimitiveInstance>,
|
instances: Vec<PrimitiveInstance>,
|
||||||
/// The widget each instance belongs to, for renumbering its handles.
|
|
||||||
assoc: Vec<WidgetId>,
|
assoc: Vec<WidgetId>,
|
||||||
|
data: PrimitiveData,
|
||||||
free: Vec<usize>,
|
free: Vec<usize>,
|
||||||
/// `stride` bytes of the primitive's own data per instance.
|
|
||||||
data: Vec<u8>,
|
|
||||||
/// From the type the list was made for, so a write is never checked.
|
|
||||||
stride: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstanceList {
|
|
||||||
fn new<P: Primitive>() -> Self {
|
|
||||||
Self {
|
|
||||||
instances: Vec::new(),
|
|
||||||
assoc: Vec::new(),
|
|
||||||
free: Vec::new(),
|
|
||||||
data: Vec::new(),
|
|
||||||
stride: size_of::<P>(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn instances(&self) -> &[PrimitiveInstance] {
|
|
||||||
&self.instances
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data(&self) -> &[u8] {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stride(&self) -> usize {
|
|
||||||
self.stride
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, id: WidgetId, inst: PrimitiveInstance, data: &[u8]) -> usize {
|
|
||||||
if let Some(i) = self.free.pop() {
|
|
||||||
self.instances[i] = inst;
|
|
||||||
self.assoc[i] = id;
|
|
||||||
self.data[i * self.stride..][..self.stride].copy_from_slice(data);
|
|
||||||
i
|
|
||||||
} else {
|
|
||||||
let i = self.instances.len();
|
|
||||||
self.instances.push(inst);
|
|
||||||
self.assoc.push(id);
|
|
||||||
self.data.extend_from_slice(data);
|
|
||||||
i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn free(&mut self, i: usize) -> MaskIdx {
|
|
||||||
self.free.push(i);
|
|
||||||
self.instances[i].mask_idx
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_free(&mut self, kind: u32) -> impl Iterator<Item = PrimitiveChange> {
|
|
||||||
self.free.sort_by(|a, b| b.cmp(a));
|
|
||||||
let instances = &mut self.instances;
|
|
||||||
let assoc = &mut self.assoc;
|
|
||||||
let data = &mut self.data;
|
|
||||||
let stride = self.stride;
|
|
||||||
self.free.drain(..).filter_map(move |i| {
|
|
||||||
instances.swap_remove(i);
|
|
||||||
assoc.swap_remove(i);
|
|
||||||
let last = instances.len();
|
|
||||||
data.copy_within(last * stride..(last + 1) * stride, i * stride);
|
|
||||||
data.truncate(last * stride);
|
|
||||||
if i == last {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let id = assoc[i];
|
|
||||||
Some(PrimitiveChange {
|
|
||||||
id,
|
|
||||||
kind,
|
|
||||||
old: last,
|
|
||||||
new: i,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Everything one layer draws, one list per registered primitive.
|
|
||||||
pub struct LayerDraws {
|
|
||||||
/// `None` until this layer draws that primitive, because only the write
|
|
||||||
/// knows the type the list is for.
|
|
||||||
primitives: Vec<Option<InstanceList>>,
|
|
||||||
pub updated: bool,
|
pub updated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LayerDraws {
|
impl Default for Primitives {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
primitives: Vec::new(),
|
instances: Default::default(),
|
||||||
|
assoc: Default::default(),
|
||||||
|
data: Default::default(),
|
||||||
|
free: Vec::new(),
|
||||||
updated: true,
|
updated: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayerDraws {
|
pub trait Primitive: Pod {
|
||||||
|
const BINDING: u32;
|
||||||
|
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! primitives {
|
||||||
|
($($name:ident: $ty:ty => $binding:expr,)*) => {
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct PrimitiveData {
|
||||||
|
$(pub(crate) $name: PrimitiveVec<$ty>,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrimitiveBuffers {
|
||||||
|
$($name: ArrBuf<$ty>,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveBuffers {
|
||||||
|
pub fn update(&mut self, device: &Device, queue: &Queue, data: &PrimitiveData) {
|
||||||
|
$(self.$name.update(device, queue, &data.$name);)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveBuffers {
|
||||||
|
pub const LEN: usize = primitives!(@count $($name)*);
|
||||||
|
pub fn buffers(&self) -> [(u32, &Buffer); Self::LEN] {
|
||||||
|
[
|
||||||
|
$((<$ty>::BINDING, &self.$name.buffer),)*
|
||||||
|
]
|
||||||
|
}
|
||||||
|
pub fn new(device: &Device) -> Self {
|
||||||
|
Self {
|
||||||
|
$($name: ArrBuf::new(
|
||||||
|
device,
|
||||||
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
|
stringify!($name),
|
||||||
|
),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveData {
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
$(self.$name.clear();)*
|
||||||
|
}
|
||||||
|
pub fn free(&mut self, binding: u32, idx: usize) {
|
||||||
|
match binding {
|
||||||
|
$(<$ty>::BINDING => self.$name.free(idx),)*
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
unsafe impl bytemuck::Pod for $ty {}
|
||||||
|
unsafe impl bytemuck::Zeroable for $ty {}
|
||||||
|
impl Primitive for $ty {
|
||||||
|
const BINDING: u32 = $binding;
|
||||||
|
fn vec(data: &mut PrimitiveData) -> &mut PrimitiveVec<Self> {
|
||||||
|
&mut data.$name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
(@count $t1:tt $($t:tt)+) => { 1 + primitives!(@count $($t)+) };
|
||||||
|
(@count $t:tt) => { 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrimitiveInst<P> {
|
||||||
|
pub id: WidgetId,
|
||||||
|
pub primitive: P,
|
||||||
|
pub region: UiRegion,
|
||||||
|
pub mask_idx: MaskIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Primitives {
|
||||||
pub fn write<P: Primitive>(
|
pub fn write<P: Primitive>(
|
||||||
&mut self,
|
&mut self,
|
||||||
layer: usize,
|
layer: usize,
|
||||||
PrimitiveInst {
|
PrimitiveInst {
|
||||||
kind,
|
|
||||||
id,
|
id,
|
||||||
primitive,
|
primitive,
|
||||||
region,
|
region,
|
||||||
@@ -249,67 +116,65 @@ impl LayerDraws {
|
|||||||
}: PrimitiveInst<P>,
|
}: PrimitiveInst<P>,
|
||||||
) -> PrimitiveHandle {
|
) -> PrimitiveHandle {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
// Grown on first use rather than sized from the registry, which a
|
let vec = P::vec(&mut self.data);
|
||||||
// layer cannot see.
|
let i = vec.add(primitive);
|
||||||
if self.primitives.len() <= kind.id as usize {
|
let inst = PrimitiveInstance {
|
||||||
self.primitives.resize_with(kind.id as usize + 1, || None);
|
region,
|
||||||
}
|
idx: i as u32,
|
||||||
let inst_idx = self.primitives[kind.id as usize]
|
mask_idx,
|
||||||
.get_or_insert_with(InstanceList::new::<P>)
|
binding: P::BINDING,
|
||||||
.push(
|
};
|
||||||
id,
|
let inst_i = if let Some(i) = self.free.pop() {
|
||||||
PrimitiveInstance { region, mask_idx },
|
self.instances[i] = inst;
|
||||||
bytemuck::bytes_of(&primitive),
|
self.assoc[i] = id;
|
||||||
);
|
i
|
||||||
PrimitiveHandle {
|
} else {
|
||||||
layer,
|
let i = self.instances.len();
|
||||||
kind: kind.id,
|
self.instances.push(inst);
|
||||||
inst_idx,
|
self.assoc.push(id);
|
||||||
}
|
i
|
||||||
}
|
};
|
||||||
|
PrimitiveHandle::new::<P>(layer, inst_i, i)
|
||||||
pub fn primitives(&self) -> &[Option<InstanceList>] {
|
|
||||||
&self.primitives
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns (old index, new index)
|
||||||
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
pub fn apply_free(&mut self) -> impl Iterator<Item = PrimitiveChange> {
|
||||||
self.primitives
|
self.free.sort_by(|a, b| b.cmp(a));
|
||||||
.iter_mut()
|
self.free.drain(..).filter_map(|i| {
|
||||||
.enumerate()
|
self.instances.swap_remove(i);
|
||||||
.filter_map(|(kind, list)| Some((kind as u32, list.as_mut()?)))
|
self.assoc.swap_remove(i);
|
||||||
.flat_map(|(kind, list)| list.apply_free(kind))
|
if i == self.instances.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let id = self.assoc[i];
|
||||||
|
let old = self.instances.len();
|
||||||
|
Some(PrimitiveChange { id, old, new: i })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
pub fn free(&mut self, h: &PrimitiveHandle) -> MaskIdx {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
self.list(h).free(h.inst_idx)
|
self.data.free(h.binding, h.data_idx);
|
||||||
|
self.free.push(h.inst_idx);
|
||||||
|
self.instances[h.inst_idx].mask_idx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data(&self) -> &PrimitiveData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instances(&self) -> &Vec<PrimitiveInstance> {
|
||||||
|
&self.instances
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
pub fn region_mut(&mut self, h: &PrimitiveHandle) -> &mut UiRegion {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
&mut self.list(h).instances[h.inst_idx].region
|
&mut self.instances[h.inst_idx].region
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle is only ever made by `write`, which is what created the list.
|
|
||||||
fn list(&mut self, h: &PrimitiveHandle) -> &mut InstanceList {
|
|
||||||
self.primitives[h.kind as usize]
|
|
||||||
.as_mut()
|
|
||||||
.expect("handle names a primitive this layer never drew")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrimitiveInst<P> {
|
|
||||||
pub kind: PrimitiveKind<P>,
|
|
||||||
pub id: WidgetId,
|
|
||||||
pub primitive: P,
|
|
||||||
pub region: UiRegion,
|
|
||||||
pub mask_idx: MaskIdx,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveChange {
|
pub struct PrimitiveChange {
|
||||||
pub id: WidgetId,
|
pub id: WidgetId,
|
||||||
/// Which registered primitive's list moved, since they index separately.
|
|
||||||
pub kind: u32,
|
|
||||||
pub old: usize,
|
pub old: usize,
|
||||||
pub new: usize,
|
pub new: usize,
|
||||||
}
|
}
|
||||||
@@ -317,12 +182,30 @@ pub struct PrimitiveChange {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PrimitiveHandle {
|
pub struct PrimitiveHandle {
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub kind: u32,
|
|
||||||
pub inst_idx: usize,
|
pub inst_idx: usize,
|
||||||
|
pub data_idx: usize,
|
||||||
|
pub binding: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PrimitiveHandle {
|
||||||
|
fn new<P: Primitive>(layer: usize, inst_idx: usize, data_idx: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
layer,
|
||||||
|
inst_idx,
|
||||||
|
data_idx,
|
||||||
|
binding: P::BINDING,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
primitives!(
|
||||||
|
rects: RectPrimitive => 0,
|
||||||
|
textures: TexturePrimitive => 1,
|
||||||
|
glyphs: GlyphPrimitive => 2,
|
||||||
|
);
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct RectPrimitive {
|
pub struct RectPrimitive {
|
||||||
pub color: Color<u8>,
|
pub color: Color<u8>,
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
@@ -330,10 +213,6 @@ pub struct RectPrimitive {
|
|||||||
pub inner_radius: f32,
|
pub inner_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive for RectPrimitive {
|
|
||||||
const WGSL: &'static str = include_str!("shader/rect.wgsl");
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RectPrimitive {
|
impl RectPrimitive {
|
||||||
pub fn color(color: Color<u8>) -> Self {
|
pub fn color(color: Color<u8>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -345,51 +224,75 @@ impl RectPrimitive {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `color` is multiplied by the atlas alpha for a mask glyph; a colour glyph
|
#[repr(C)]
|
||||||
/// takes the texel unchanged, which `GlyphEntry::IS_COLORED` selects.
|
#[derive(Debug, Copy, Clone)]
|
||||||
#[repr(C, align(8))]
|
pub struct TexturePrimitive {
|
||||||
|
pub view_idx: u32,
|
||||||
|
pub sampler_idx: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct GlyphPrimitive {
|
pub struct GlyphPrimitive {
|
||||||
pub uv_min: Vec2,
|
pub uv_min: [f32; 2],
|
||||||
pub uv_max: Vec2,
|
pub uv_max: [f32; 2],
|
||||||
/// Which atlas array layer this glyph is on.
|
pub view_idx: u32,
|
||||||
pub layer: u32,
|
pub sampler_idx: u32,
|
||||||
pub color: Color<u8>,
|
pub color: Color<u8>,
|
||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manual rather than derived: the align(8) leaves four bytes of padding, which
|
impl GlyphPrimitive {
|
||||||
// is how WGSL lays the struct out.
|
pub const IS_COLOR: u32 = 1;
|
||||||
unsafe impl bytemuck::Pod for GlyphPrimitive {}
|
|
||||||
unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
|
|
||||||
impl Primitive for GlyphPrimitive {
|
|
||||||
const WGSL: &'static str = include_str!("shader/glyph.wgsl");
|
|
||||||
|
|
||||||
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender> {
|
|
||||||
Box::new(GlyphRender::new(device, queue))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One drawn image. Its shader reads nothing per instance; the slot names the
|
pub struct PrimitiveVec<T> {
|
||||||
/// texture to bind for it.
|
vec: Vec<T>,
|
||||||
#[repr(C)]
|
free: Vec<usize>,
|
||||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
|
||||||
pub struct TexturePrimitive {
|
|
||||||
pub slot: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive for TexturePrimitive {
|
impl<T> PrimitiveVec<T> {
|
||||||
const WGSL: &'static str = include_str!("shader/texture.wgsl");
|
pub fn new() -> Self {
|
||||||
|
|
||||||
fn render(device: &Device, queue: &Queue) -> Box<dyn PrimitiveRender> {
|
|
||||||
Box::new(ImageRender::new(device, queue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&TextureHandle> for TexturePrimitive {
|
|
||||||
fn from(handle: &TextureHandle) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
slot: handle.slot(),
|
vec: Vec::new(),
|
||||||
|
free: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn add(&mut self, t: T) -> usize {
|
||||||
|
if let Some(i) = self.free.pop() {
|
||||||
|
self.vec[i] = t;
|
||||||
|
i
|
||||||
|
} else {
|
||||||
|
let i = self.vec.len();
|
||||||
|
self.vec.push(t);
|
||||||
|
i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn free(&mut self, i: usize) {
|
||||||
|
self.free.push(i);
|
||||||
|
}
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.free.clear();
|
||||||
|
self.vec.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for PrimitiveVec<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for PrimitiveVec<T> {
|
||||||
|
type Target = Vec<T>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.vec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for PrimitiveVec<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.vec
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
const RECT: u32 = 0u;
|
||||||
|
const TEXTURE: u32 = 1u;
|
||||||
|
const GLYPH: u32 = 2u;
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var<uniform> window: WindowUniform;
|
||||||
|
@group(1) @binding(RECT)
|
||||||
|
var<storage> rects: array<Rect>;
|
||||||
|
@group(1) @binding(TEXTURE)
|
||||||
|
var<storage> textures: array<TextureInfo>;
|
||||||
|
@group(1) @binding(GLYPH)
|
||||||
|
var<storage> glyphs: array<GlyphInfo>;
|
||||||
|
|
||||||
|
struct Rect {
|
||||||
|
color: u32,
|
||||||
|
radius: f32,
|
||||||
|
thickness: f32,
|
||||||
|
inner_radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TextureInfo {
|
||||||
|
view_idx: u32,
|
||||||
|
sampler_idx: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlyphInfo {
|
||||||
|
uv_min: vec2<f32>,
|
||||||
|
uv_max: vec2<f32>,
|
||||||
|
view_idx: u32,
|
||||||
|
sampler_idx: u32,
|
||||||
|
color: u32,
|
||||||
|
flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Mask {
|
||||||
|
x: UiSpan,
|
||||||
|
y: UiSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiSpan {
|
||||||
|
start: UiScalar,
|
||||||
|
end: UiScalar,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiScalar {
|
||||||
|
rel: f32,
|
||||||
|
abs: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UiVec2 {
|
||||||
|
rel: vec2<f32>,
|
||||||
|
abs: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(2) @binding(0)
|
||||||
|
var views: binding_array<texture_2d<f32>>;
|
||||||
|
@group(2) @binding(1)
|
||||||
|
var samplers: binding_array<sampler>;
|
||||||
|
@group(2) @binding(2)
|
||||||
|
var<storage> masks: array<Mask>;
|
||||||
|
|
||||||
|
struct WindowUniform {
|
||||||
|
dim: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InstanceInput {
|
||||||
|
@location(0) x_start: vec2<f32>,
|
||||||
|
@location(1) x_end: vec2<f32>,
|
||||||
|
@location(2) y_start: vec2<f32>,
|
||||||
|
@location(3) y_end: vec2<f32>,
|
||||||
|
@location(4) binding: u32,
|
||||||
|
@location(5) idx: u32,
|
||||||
|
@location(6) mask_idx: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@location(0) top_left: vec2<f32>,
|
||||||
|
@location(1) bot_right: vec2<f32>,
|
||||||
|
@location(2) uv: vec2<f32>,
|
||||||
|
@location(3) binding: u32,
|
||||||
|
@location(4) idx: u32,
|
||||||
|
@location(5) mask_idx: u32,
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Region {
|
||||||
|
pos: vec2<f32>,
|
||||||
|
uv: vec2<f32>,
|
||||||
|
top_left: vec2<f32>,
|
||||||
|
bot_right: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(
|
||||||
|
@builtin(vertex_index) vi: u32,
|
||||||
|
in: InstanceInput,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
|
||||||
|
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
|
||||||
|
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
||||||
|
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
||||||
|
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
||||||
|
|
||||||
|
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
||||||
|
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
||||||
|
let size = bot_right - top_left;
|
||||||
|
|
||||||
|
let uv = vec2<f32>(
|
||||||
|
f32(vi % 2u),
|
||||||
|
f32(vi / 2u)
|
||||||
|
);
|
||||||
|
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
||||||
|
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
||||||
|
out.uv = uv;
|
||||||
|
out.binding = in.binding;
|
||||||
|
out.idx = in.idx;
|
||||||
|
out.top_left = top_left;
|
||||||
|
out.bot_right = bot_right;
|
||||||
|
out.mask_idx = in.mask_idx;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(
|
||||||
|
in: VertexOutput
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
let pos = in.clip_position.xy;
|
||||||
|
let region = Region(pos, in.uv, in.top_left, in.bot_right);
|
||||||
|
let i = in.idx;
|
||||||
|
var color: vec4<f32>;
|
||||||
|
switch in.binding {
|
||||||
|
case RECT: {
|
||||||
|
color = draw_rounded_rect(region, rects[i]);
|
||||||
|
}
|
||||||
|
case TEXTURE: {
|
||||||
|
color = draw_texture(region, textures[i]);
|
||||||
|
}
|
||||||
|
case GLYPH: {
|
||||||
|
color = draw_glyph(region, glyphs[i]);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
color = vec4(1.0, 0.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.mask_idx != 4294967295u {
|
||||||
|
let mask = masks[in.mask_idx];
|
||||||
|
let tl = UiVec2(vec2(mask.x.start.rel, mask.y.start.rel), vec2(mask.x.start.abs, mask.y.start.abs));
|
||||||
|
let br = UiVec2(vec2(mask.x.end.rel, mask.y.end.rel), vec2(mask.x.end.abs, mask.y.end.abs));
|
||||||
|
|
||||||
|
let top_left = floor(tl.rel * window.dim) + floor(tl.abs);
|
||||||
|
let bot_right = floor(br.rel * window.dim) + floor(br.abs);
|
||||||
|
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
||||||
|
color *= 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this seems really inefficient (per frag indexing)?
|
||||||
|
fn draw_texture(region: Region, info: TextureInfo) -> vec4<f32> {
|
||||||
|
return textureSample(views[info.view_idx], samplers[info.sampler_idx], region.uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_glyph(region: Region, g: GlyphInfo) -> vec4<f32> {
|
||||||
|
let uv = mix(g.uv_min, g.uv_max, region.uv);
|
||||||
|
let texel = textureSample(views[g.view_idx], samplers[g.sampler_idx], uv);
|
||||||
|
if (g.flags & 1u) != 0u {
|
||||||
|
return texel;
|
||||||
|
}
|
||||||
|
var color = unpack4x8unorm(g.color);
|
||||||
|
color.a *= texel.a;
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_rounded_rect(region: Region, rect: Rect) -> vec4<f32> {
|
||||||
|
var color = unpack4x8unorm(rect.color);
|
||||||
|
|
||||||
|
let edge = 0.5;
|
||||||
|
|
||||||
|
let size = region.bot_right - region.top_left;
|
||||||
|
let corner = size / 2.0;
|
||||||
|
let center = region.top_left + corner;
|
||||||
|
|
||||||
|
let dist = distance_from_rect(region.pos, center, corner, rect.radius);
|
||||||
|
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
||||||
|
|
||||||
|
if rect.thickness > 0.0 {
|
||||||
|
let dist2 = distance_from_rect(region.pos, center, corner - rect.thickness, rect.inner_radius);
|
||||||
|
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
||||||
|
// vec from center to pixel
|
||||||
|
let p = pixel_pos - rect_center;
|
||||||
|
// vec from inner rect corner to pixel
|
||||||
|
let q = abs(p) - (rect_corner - radius);
|
||||||
|
return length(max(q, vec2(0.0))) - radius;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Matches `GlyphEntry::IS_COLORED`.
|
|
||||||
const COLORED: u32 = 1u;
|
|
||||||
|
|
||||||
// The glyph atlas, whose array layers are its pages.
|
|
||||||
@group(2) @binding(0)
|
|
||||||
var atlas: texture_2d_array<f32>;
|
|
||||||
@group(2) @binding(1)
|
|
||||||
var samp: sampler;
|
|
||||||
|
|
||||||
struct GlyphInfo {
|
|
||||||
uv_min: vec2<f32>,
|
|
||||||
uv_max: vec2<f32>,
|
|
||||||
// Which layer of the atlas array this glyph's page is.
|
|
||||||
layer: u32,
|
|
||||||
color: u32,
|
|
||||||
flags: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<storage> glyphs: array<GlyphInfo>;
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
let g = glyphs[in.idx];
|
|
||||||
let uv = mix(g.uv_min, g.uv_max, in.uv);
|
|
||||||
let texel = textureSample(atlas, samp, uv, i32(g.layer));
|
|
||||||
if (g.flags & COLORED) != 0u {
|
|
||||||
return masked(in, texel);
|
|
||||||
}
|
|
||||||
var color = unpack4x8unorm(g.color);
|
|
||||||
color.a *= texel.a;
|
|
||||||
return masked(in, color);
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
// Prepended to every primitive's shader, which declares its own instance data
|
|
||||||
// as `var<storage> <name>: array<T>` at group 1 binding 0, and an `fs_main`
|
|
||||||
// shading one instance of it. What it samples, if anything, is bound at group
|
|
||||||
// 2: the texture at binding 0 and the sampler at binding 1.
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
|
||||||
var<uniform> window: WindowUniform;
|
|
||||||
@group(0) @binding(1)
|
|
||||||
var<storage> masks: array<Mask>;
|
|
||||||
|
|
||||||
struct WindowUniform {
|
|
||||||
dim: vec2<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Mask {
|
|
||||||
x: UiSpan,
|
|
||||||
y: UiSpan,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiSpan {
|
|
||||||
start: UiScalar,
|
|
||||||
end: UiScalar,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UiScalar {
|
|
||||||
rel: f32,
|
|
||||||
abs: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InstanceInput {
|
|
||||||
@location(0) x_start: vec2<f32>,
|
|
||||||
@location(1) x_end: vec2<f32>,
|
|
||||||
@location(2) y_start: vec2<f32>,
|
|
||||||
@location(3) y_end: vec2<f32>,
|
|
||||||
@location(4) mask_idx: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VertexOutput {
|
|
||||||
@location(0) top_left: vec2<f32>,
|
|
||||||
@location(1) bot_right: vec2<f32>,
|
|
||||||
@location(2) uv: vec2<f32>,
|
|
||||||
@location(3) @interpolate(flat) mask_idx: u32,
|
|
||||||
@location(4) @interpolate(flat) idx: u32,
|
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
|
||||||
};
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vs_main(
|
|
||||||
@builtin(vertex_index) vi: u32,
|
|
||||||
@builtin(instance_index) ii: u32,
|
|
||||||
in: InstanceInput,
|
|
||||||
) -> VertexOutput {
|
|
||||||
var out: VertexOutput;
|
|
||||||
|
|
||||||
let top_left_rel = vec2(in.x_start.x, in.y_start.x);
|
|
||||||
let top_left_abs = vec2(in.x_start.y, in.y_start.y);
|
|
||||||
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
|
||||||
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
|
||||||
|
|
||||||
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
|
||||||
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
|
||||||
let size = bot_right - top_left;
|
|
||||||
|
|
||||||
let uv = vec2<f32>(
|
|
||||||
f32(vi % 2u),
|
|
||||||
f32(vi / 2u)
|
|
||||||
);
|
|
||||||
let pos = (top_left + uv * size) / window.dim * 2.0 - 1.0;
|
|
||||||
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
|
|
||||||
out.uv = uv;
|
|
||||||
out.top_left = top_left;
|
|
||||||
out.bot_right = bot_right;
|
|
||||||
out.mask_idx = in.mask_idx;
|
|
||||||
out.idx = ii;
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
|
|
||||||
if in.mask_idx == 4294967295u {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
let mask = masks[in.mask_idx];
|
|
||||||
let tl = vec2(mask.x.start.rel, mask.y.start.rel);
|
|
||||||
let tl_abs = vec2(mask.x.start.abs, mask.y.start.abs);
|
|
||||||
let br = vec2(mask.x.end.rel, mask.y.end.rel);
|
|
||||||
let br_abs = vec2(mask.x.end.abs, mask.y.end.abs);
|
|
||||||
|
|
||||||
let top_left = floor(tl * window.dim) + floor(tl_abs);
|
|
||||||
let bot_right = floor(br * window.dim) + floor(br_abs);
|
|
||||||
let pos = in.clip_position.xy;
|
|
||||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
|
||||||
return color * 0.0;
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
struct Rect {
|
|
||||||
color: u32,
|
|
||||||
radius: f32,
|
|
||||||
thickness: f32,
|
|
||||||
inner_radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
@group(1) @binding(0)
|
|
||||||
var<storage> rects: array<Rect>;
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
let rect = rects[in.idx];
|
|
||||||
var color = unpack4x8unorm(rect.color);
|
|
||||||
|
|
||||||
let edge = 0.5;
|
|
||||||
let size = in.bot_right - in.top_left;
|
|
||||||
let corner = size / 2.0;
|
|
||||||
let center = in.top_left + corner;
|
|
||||||
let pos = in.clip_position.xy;
|
|
||||||
|
|
||||||
let dist = distance_from_rect(pos, center, corner, rect.radius);
|
|
||||||
color.a *= 1.0 - smoothstep(-min(edge, rect.radius), edge, dist);
|
|
||||||
|
|
||||||
if rect.thickness > 0.0 {
|
|
||||||
let dist2 = distance_from_rect(pos, center, corner - rect.thickness, rect.inner_radius);
|
|
||||||
color.a *= smoothstep(-min(edge, rect.inner_radius), edge, dist2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return masked(in, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn distance_from_rect(pixel_pos: vec2<f32>, rect_center: vec2<f32>, rect_corner: vec2<f32>, radius: f32) -> f32 {
|
|
||||||
// vec from center to pixel
|
|
||||||
let p = pixel_pos - rect_center;
|
|
||||||
// vec from inner rect corner to pixel
|
|
||||||
let q = abs(p) - (rect_corner - radius);
|
|
||||||
return length(max(q, vec2(0.0))) - radius;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// The image this instance draws, bound for it alone.
|
|
||||||
@group(2) @binding(0)
|
|
||||||
var image: texture_2d<f32>;
|
|
||||||
@group(2) @binding(1)
|
|
||||||
var samp: sampler;
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|
||||||
return masked(in, textureSample(image, samp, in.uv));
|
|
||||||
}
|
|
||||||
+137
-189
@@ -1,119 +1,119 @@
|
|||||||
use image::{DynamicImage, EncodableLayout, GenericImageView, RgbaImage};
|
use image::{DynamicImage, EncodableLayout, GenericImageView};
|
||||||
use wgpu::{util::DeviceExt, *};
|
use wgpu::{util::DeviceExt, *};
|
||||||
|
|
||||||
use crate::{
|
use crate::{PatchRect, TextureUpdate, Textures};
|
||||||
PatchRect, TextureUpdate, Textures, UiData,
|
|
||||||
render::{
|
|
||||||
TexturePrimitive,
|
|
||||||
primitive::{ListDraw, PrimitiveRender},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Draws standalone images, which it owns. Each is its own texture, so each
|
|
||||||
/// instance binds its own and is a draw of its own.
|
|
||||||
pub struct ImageRender {
|
|
||||||
textures: GpuTextures,
|
|
||||||
layout: BindGroupLayout,
|
|
||||||
sampler: Sampler,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImageRender {
|
|
||||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
|
||||||
Self {
|
|
||||||
textures: GpuTextures::new(device, queue),
|
|
||||||
layout: sampled_layout(device, TextureViewDimension::D2, "ui image"),
|
|
||||||
sampler: default_sampler(device),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveRender for ImageRender {
|
|
||||||
fn layout(&self) -> Option<&BindGroupLayout> {
|
|
||||||
Some(&self.layout)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ui: &mut UiData) {
|
|
||||||
self.textures
|
|
||||||
.update(&mut ui.textures, &self.layout, &self.sampler);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_bindings(&self, list: &super::InstanceList, out: &mut Vec<u32>) {
|
|
||||||
let slots = list
|
|
||||||
.data()
|
|
||||||
.chunks_exact(list.stride())
|
|
||||||
.map(|data| bytemuck::pod_read_unaligned::<TexturePrimitive>(data).slot);
|
|
||||||
out.extend(slots);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw<'a>(&'a self, pass: &mut RenderPass<'a>, list: ListDraw<'a>) {
|
|
||||||
for (i, &slot) in list.bindings.iter().enumerate() {
|
|
||||||
let Some(image) = self.textures.group(slot) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
pass.set_bind_group(2, image, &[]);
|
|
||||||
pass.draw(0..4, i as u32..i as u32 + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The standalone images a ui draws, each its own texture and bind group --
|
|
||||||
/// unlike the glyph atlas in `super::page`, which is one array they share.
|
|
||||||
pub struct GpuTextures {
|
pub struct GpuTextures {
|
||||||
device: Device,
|
device: Device,
|
||||||
queue: Queue,
|
queue: Queue,
|
||||||
slots: Vec<Option<ImageGpu>>,
|
/// Parallel to `views`; patches require textures rather than views.
|
||||||
}
|
textures: Vec<Option<Texture>>,
|
||||||
|
views: Vec<TextureView>,
|
||||||
struct ImageGpu {
|
view_count: usize,
|
||||||
/// Kept for `patch`, which needs the texture rather than the view.
|
samplers: Vec<Sampler>,
|
||||||
texture: Texture,
|
null_view: TextureView,
|
||||||
group: BindGroup,
|
no_views: Vec<TextureView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GpuTextures {
|
impl GpuTextures {
|
||||||
pub fn new(device: &Device, queue: &Queue) -> Self {
|
pub fn update(&mut self, textures: &mut Textures) -> bool {
|
||||||
Self {
|
let mut changed = false;
|
||||||
device: device.clone(),
|
|
||||||
queue: queue.clone(),
|
|
||||||
slots: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&mut self, textures: &mut Textures, layout: &BindGroupLayout, sampler: &Sampler) {
|
|
||||||
for update in textures.updates() {
|
for update in textures.updates() {
|
||||||
match update {
|
match update {
|
||||||
TextureUpdate::Push(image) => {
|
TextureUpdate::Push(image) => {
|
||||||
let image = self.create(image, layout, sampler);
|
self.push(image);
|
||||||
self.slots.push(Some(image));
|
changed = true;
|
||||||
}
|
}
|
||||||
TextureUpdate::Set(i, image) => {
|
TextureUpdate::Set(i, image) => {
|
||||||
let image = self.create(image, layout, sampler);
|
self.set(i, image);
|
||||||
self.slots[i as usize] = Some(image);
|
changed = true;
|
||||||
}
|
}
|
||||||
TextureUpdate::Patch(i, rect, image) => self.patch(i, rect, image),
|
TextureUpdate::Patch(i, rect, image) => {
|
||||||
TextureUpdate::PushFree => self.slots.push(None),
|
// Patching contents leaves the binding array unchanged.
|
||||||
TextureUpdate::SetFree => {}
|
self.patch(i, rect, image);
|
||||||
TextureUpdate::Free(i) => self.slots[i as usize] = None,
|
}
|
||||||
|
TextureUpdate::SetFree => {
|
||||||
|
self.view_count += 1;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TextureUpdate::Free(i) => {
|
||||||
|
self.free(i);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TextureUpdate::PushFree => {
|
||||||
|
self.push_free();
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
fn set(&mut self, i: u32, image: &DynamicImage) {
|
||||||
|
self.view_count += 1;
|
||||||
|
let (texture, view) = self.create(image);
|
||||||
|
self.textures[i as usize] = Some(texture);
|
||||||
|
self.views[i as usize] = view;
|
||||||
|
}
|
||||||
|
fn free(&mut self, i: u32) {
|
||||||
|
self.view_count -= 1;
|
||||||
|
self.textures[i as usize] = None;
|
||||||
|
self.views[i as usize] = self.null_view.clone();
|
||||||
|
}
|
||||||
|
fn push(&mut self, image: &DynamicImage) {
|
||||||
|
self.view_count += 1;
|
||||||
|
let (texture, view) = self.create(image);
|
||||||
|
self.textures.push(Some(texture));
|
||||||
|
self.views.push(view);
|
||||||
|
}
|
||||||
|
fn push_free(&mut self) {
|
||||||
|
self.view_count += 1;
|
||||||
|
self.textures.push(None);
|
||||||
|
self.views.push(self.null_view.clone());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn group(&self, slot: u32) -> Option<&BindGroup> {
|
fn patch(&mut self, i: u32, rect: PatchRect, image: &DynamicImage) {
|
||||||
self.slots.get(slot as usize)?.as_ref().map(|i| &i.group)
|
let Some(texture) = &self.textures[i as usize] else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if rect.width == 0 || rect.height == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// `write_texture` requires tightly packed rows, unlike the atlas image.
|
||||||
|
let sub = image
|
||||||
|
.view(rect.x, rect.y, rect.width, rect.height)
|
||||||
|
.to_image();
|
||||||
|
self.queue.write_texture(
|
||||||
|
TexelCopyTextureInfo {
|
||||||
|
texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Origin3d {
|
||||||
|
x: rect.x,
|
||||||
|
y: rect.y,
|
||||||
|
z: 0,
|
||||||
|
},
|
||||||
|
aspect: TextureAspect::All,
|
||||||
|
},
|
||||||
|
sub.as_bytes(),
|
||||||
|
TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(rect.width * 4),
|
||||||
|
rows_per_image: Some(rect.height),
|
||||||
|
},
|
||||||
|
Extent3d {
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create(
|
fn create(&self, image: &DynamicImage) -> (Texture, TextureView) {
|
||||||
&self,
|
let image = image.to_rgba8();
|
||||||
image: &DynamicImage,
|
let (width, height) = image.dimensions();
|
||||||
layout: &BindGroupLayout,
|
|
||||||
sampler: &Sampler,
|
|
||||||
) -> ImageGpu {
|
|
||||||
let rgba = image.to_rgba8();
|
|
||||||
let (width, height) = rgba.dimensions();
|
|
||||||
let texture = self.device.create_texture_with_data(
|
let texture = self.device.create_texture_with_data(
|
||||||
&self.queue,
|
&self.queue,
|
||||||
&TextureDescriptor {
|
&TextureDescriptor {
|
||||||
label: Some("image"),
|
label: None,
|
||||||
size: Extent3d {
|
size: Extent3d {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@@ -127,115 +127,63 @@ impl GpuTextures {
|
|||||||
view_formats: &[],
|
view_formats: &[],
|
||||||
},
|
},
|
||||||
wgt::TextureDataOrder::MipMajor,
|
wgt::TextureDataOrder::MipMajor,
|
||||||
rgba.as_bytes(),
|
image.as_bytes(),
|
||||||
);
|
);
|
||||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
let view = texture.create_view(&TextureViewDescriptor::default());
|
||||||
let group = sampled_group(&self.device, layout, &view, sampler, "ui image");
|
(texture, view)
|
||||||
ImageGpu { texture, group }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patch(&mut self, i: u32, rect: PatchRect, image: &DynamicImage) {
|
pub fn new(device: &Device, queue: &Queue) -> Self {
|
||||||
let Some(Some(slot)) = self.slots.get(i as usize) else {
|
let null_view = null_texture_view(device);
|
||||||
return;
|
Self {
|
||||||
};
|
device: device.clone(),
|
||||||
let dst = TexelCopyTextureInfo {
|
queue: queue.clone(),
|
||||||
texture: &slot.texture,
|
textures: Vec::new(),
|
||||||
mip_level: 0,
|
views: Vec::new(),
|
||||||
origin: Origin3d {
|
samplers: vec![default_sampler(device)],
|
||||||
x: rect.x,
|
no_views: vec![null_view.clone()],
|
||||||
y: rect.y,
|
null_view,
|
||||||
z: 0,
|
view_count: 0,
|
||||||
},
|
|
||||||
aspect: TextureAspect::All,
|
|
||||||
};
|
|
||||||
match image.as_rgba8() {
|
|
||||||
Some(rgba) => write_region(&self.queue, dst, rgba, rect),
|
|
||||||
// The texture is rgba8, so any other layout has to be converted --
|
|
||||||
// and converting the rectangle is cheaper than the whole image.
|
|
||||||
None => {
|
|
||||||
let sub = image
|
|
||||||
.view(rect.x, rect.y, rect.width, rect.height)
|
|
||||||
.to_image();
|
|
||||||
write_region(&self.queue, dst, &sub, PatchRect { x: 0, y: 0, ..rect });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn views(&self) -> Vec<&TextureView> {
|
||||||
|
if self.views.is_empty() {
|
||||||
|
&self.no_views
|
||||||
|
} else {
|
||||||
|
&self.views
|
||||||
|
}
|
||||||
|
.iter()
|
||||||
|
.by_ref()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn samplers(&self) -> Vec<&Sampler> {
|
||||||
|
self.samplers.iter().by_ref().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_count(&self) -> usize {
|
||||||
|
self.view_count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_region(queue: &Queue, dst: TexelCopyTextureInfo, src: &RgbaImage, rect: PatchRect) {
|
pub fn null_texture_view(device: &Device) -> TextureView {
|
||||||
if rect.width == 0 || rect.height == 0 {
|
device
|
||||||
return;
|
.create_texture(&TextureDescriptor {
|
||||||
}
|
label: Some("null"),
|
||||||
let stride = src.width() * 4;
|
size: Extent3d {
|
||||||
queue.write_texture(
|
width: 1,
|
||||||
dst,
|
height: 1,
|
||||||
src.as_bytes(),
|
|
||||||
TexelCopyBufferLayout {
|
|
||||||
offset: (rect.y * stride + rect.x * 4) as u64,
|
|
||||||
bytes_per_row: Some(stride),
|
|
||||||
rows_per_image: Some(rect.height),
|
|
||||||
},
|
|
||||||
Extent3d {
|
|
||||||
width: rect.width,
|
|
||||||
height: rect.height,
|
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
);
|
mip_level_count: 1,
|
||||||
}
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
/// What a primitive that samples binds: a texture, and the sampler that reads
|
format: TextureFormat::Rgba8Unorm,
|
||||||
/// it.
|
usage: TextureUsages::TEXTURE_BINDING,
|
||||||
pub fn sampled_group(
|
view_formats: &[],
|
||||||
device: &Device,
|
|
||||||
layout: &BindGroupLayout,
|
|
||||||
view: &TextureView,
|
|
||||||
sampler: &Sampler,
|
|
||||||
label: &'static str,
|
|
||||||
) -> BindGroup {
|
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
|
||||||
layout,
|
|
||||||
entries: &[
|
|
||||||
BindGroupEntry {
|
|
||||||
binding: 0,
|
|
||||||
resource: BindingResource::TextureView(view),
|
|
||||||
},
|
|
||||||
BindGroupEntry {
|
|
||||||
binding: 1,
|
|
||||||
resource: BindingResource::Sampler(sampler),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
label: Some(label),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The layout for one of those. The dimension differs -- the atlas is an
|
|
||||||
/// array of pages and an image is not -- and nothing else does.
|
|
||||||
pub fn sampled_layout(
|
|
||||||
device: &Device,
|
|
||||||
dimension: TextureViewDimension,
|
|
||||||
label: &'static str,
|
|
||||||
) -> BindGroupLayout {
|
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
||||||
entries: &[
|
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Texture {
|
|
||||||
sample_type: TextureSampleType::Float { filterable: false },
|
|
||||||
view_dimension: dimension,
|
|
||||||
multisampled: false,
|
|
||||||
},
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
BindGroupLayoutEntry {
|
|
||||||
binding: 1,
|
|
||||||
visibility: ShaderStages::FRAGMENT,
|
|
||||||
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
|
|
||||||
count: None,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
label: Some(label),
|
|
||||||
})
|
})
|
||||||
|
.create_view(&TextureViewDescriptor::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default_sampler(device: &Device) -> Sampler {
|
pub fn default_sampler(device: &Device) -> Sampler {
|
||||||
|
|||||||
@@ -21,25 +21,17 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
_pd: PhantomData,
|
_pd: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns whether the `Buffer` was recreated, which stales any cached
|
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) {
|
||||||
/// `BindGroup` holding it.
|
if self.len != data.len() {
|
||||||
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) -> bool {
|
|
||||||
let resized = self.len != data.len();
|
|
||||||
if resized {
|
|
||||||
self.len = data.len();
|
self.len = data.len();
|
||||||
self.buffer =
|
self.buffer =
|
||||||
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
|
||||||
}
|
}
|
||||||
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
|
||||||
resized
|
|
||||||
}
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.len
|
|
||||||
}
|
}
|
||||||
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
|
||||||
let mut size = size as u64;
|
let mut size = size as u64;
|
||||||
if usage.contains(BufferUsages::STORAGE) {
|
if usage.contains(BufferUsages::STORAGE) {
|
||||||
// A binding cannot be empty or under the layout's minimum.
|
|
||||||
size = size.max(std::mem::size_of::<T>() as u64);
|
size = size.max(std::mem::size_of::<T>() as u64);
|
||||||
}
|
}
|
||||||
device.create_buffer(&BufferDescriptor {
|
device.create_buffer(&BufferDescriptor {
|
||||||
@@ -49,4 +41,8 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
usage,
|
usage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
#[allow(clippy::len_without_is_empty)]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+2
-6
@@ -1,6 +1,4 @@
|
|||||||
use crate::{
|
use crate::{Mask, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena};
|
||||||
Mask, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod active;
|
mod active;
|
||||||
mod cache;
|
mod cache;
|
||||||
@@ -9,15 +7,13 @@ mod render_state;
|
|||||||
mod size;
|
mod size;
|
||||||
|
|
||||||
pub use active::*;
|
pub use active::*;
|
||||||
pub use painter::{Painter, PrimitiveLike};
|
pub use painter::Painter;
|
||||||
pub use render_state::*;
|
pub use render_state::*;
|
||||||
pub use size::*;
|
pub use size::*;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UiData {
|
pub struct UiData {
|
||||||
pub widgets: Widgets,
|
pub widgets: Widgets,
|
||||||
/// Every primitive this ui can draw.
|
|
||||||
pub primitives: PrimitiveRegistry,
|
|
||||||
pub textures: Textures,
|
pub textures: Textures,
|
||||||
pub text: TextData,
|
pub text: TextData,
|
||||||
pub masks: TrackedArena<Mask, u32>,
|
pub masks: TrackedArena<Mask, u32>,
|
||||||
|
|||||||
+30
-51
@@ -1,10 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
||||||
render::{
|
render::{GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||||
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
|
||||||
TexturePrimitive,
|
|
||||||
},
|
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -24,26 +21,15 @@ pub struct Painter<'a> {
|
|||||||
|
|
||||||
impl<'a> Painter<'a> {
|
impl<'a> Painter<'a> {
|
||||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||||
let kind = self.rsc.ui_mut().primitives.kind::<P>();
|
|
||||||
self.write(kind, primitive, region);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Takes the kind, for a caller writing many of one primitive.
|
|
||||||
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
|
||||||
let h = self.state.layers.write(
|
let h = self.state.layers.write(
|
||||||
self.layer,
|
self.layer,
|
||||||
PrimitiveInst {
|
PrimitiveInst {
|
||||||
kind,
|
|
||||||
id: self.id,
|
id: self.id,
|
||||||
primitive,
|
primitive,
|
||||||
region,
|
region,
|
||||||
mask_idx: self.mask,
|
mask_idx: self.mask,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.push_primitive(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_primitive(&mut self, h: PrimitiveHandle) {
|
|
||||||
if self.mask != MaskIdx::NONE {
|
if self.mask != MaskIdx::NONE {
|
||||||
// TODO: I have no clue if this works at all :joy:
|
// TODO: I have no clue if this works at all :joy:
|
||||||
self.rsc.ui_mut().masks.push_ref(self.mask);
|
self.rsc.ui_mut().masks.push_ref(self.mask);
|
||||||
@@ -52,13 +38,11 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a primitive to be rendered
|
/// Writes a primitive to be rendered
|
||||||
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
|
||||||
let primitive = primitive.into_primitive(self);
|
|
||||||
self.primitive_at(primitive, self.region)
|
self.primitive_at(primitive, self.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) {
|
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||||
let primitive = primitive.into_primitive(self);
|
|
||||||
self.primitive_at(primitive, region.within(&self.region));
|
self.primitive_at(primitive, region.within(&self.region));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +75,21 @@ impl<'a> Painter<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||||
|
self.textures.push(handle.clone());
|
||||||
|
self.primitive_at(handle.primitive(), region.within(&self.region));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture(&mut self, handle: &TextureHandle) {
|
||||||
|
self.textures.push(handle.clone());
|
||||||
|
self.primitive(handle.primitive());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||||
|
self.textures.push(handle.clone());
|
||||||
|
self.primitive_at(handle.primitive(), region);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_text(
|
pub fn render_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut TextBuffer,
|
buffer: &mut TextBuffer,
|
||||||
@@ -98,12 +97,17 @@ impl<'a> Painter<'a> {
|
|||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
) -> RenderedText {
|
) -> RenderedText {
|
||||||
let ui = self.rsc.ui_mut();
|
let ui = self.rsc.ui_mut();
|
||||||
ui.text.render(buffer, attrs, width)
|
ui.text.render(buffer, attrs, width, &mut ui.textures)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: merge the text methods into the primitive ones.
|
|
||||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||||
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
let flags_for = |is_color| {
|
||||||
|
if is_color {
|
||||||
|
GlyphPrimitive::IS_COLOR
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
for glyph in text.glyphs.iter() {
|
for glyph in text.glyphs.iter() {
|
||||||
let mut region = origin;
|
let mut region = origin;
|
||||||
region.x.end = region.x.start;
|
region.x.end = region.x.start;
|
||||||
@@ -111,14 +115,14 @@ impl<'a> Painter<'a> {
|
|||||||
let mut region = region.offset(UiVec2::abs(glyph.offset));
|
let mut region = region.offset(UiVec2::abs(glyph.offset));
|
||||||
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
|
||||||
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
|
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
|
||||||
self.write(
|
self.primitive_at(
|
||||||
kind,
|
|
||||||
GlyphPrimitive {
|
GlyphPrimitive {
|
||||||
uv_min: glyph.entry.uv_min,
|
uv_min: glyph.entry.uv_min,
|
||||||
uv_max: glyph.entry.uv_max,
|
uv_max: glyph.entry.uv_max,
|
||||||
layer: glyph.entry.layer,
|
view_idx: glyph.entry.view_idx,
|
||||||
|
sampler_idx: glyph.entry.sampler_idx,
|
||||||
color: text.color,
|
color: text.color,
|
||||||
flags: glyph.entry.flags(),
|
flags: flags_for(glyph.entry.is_color),
|
||||||
},
|
},
|
||||||
region,
|
region,
|
||||||
);
|
);
|
||||||
@@ -172,28 +176,3 @@ impl<'a> Painter<'a> {
|
|||||||
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
self.state.size_ctx(self.id, self.region.size(), self.rsc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What `Painter::primitive` takes: a primitive, or something that yields one
|
|
||||||
/// and does whatever else drawing it needs.
|
|
||||||
pub trait PrimitiveLike {
|
|
||||||
type Primitive: Primitive;
|
|
||||||
fn into_primitive(self, painter: &mut Painter) -> Self::Primitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Primitive> PrimitiveLike for P {
|
|
||||||
type Primitive = P;
|
|
||||||
fn into_primitive(self, _: &mut Painter) -> P {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrimitiveLike for &TextureHandle {
|
|
||||||
type Primitive = TexturePrimitive;
|
|
||||||
|
|
||||||
/// Retains a share of the handle, so the slot the primitive names cannot
|
|
||||||
/// be freed and reused while it is still drawn.
|
|
||||||
fn into_primitive(self, painter: &mut Painter) -> TexturePrimitive {
|
|
||||||
painter.textures.push(self.clone());
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-10
@@ -1,13 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, SizeCtx, StrongWidget,
|
ActiveData, Axis, IdLike, MaskIdx, Painter, PixelRegion, PrimitiveLayers, SizeCtx,
|
||||||
UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
StrongWidget, UiRegion, UiRsc, UiVec2, WidgetId, Widgets,
|
||||||
ui::cache::Cache,
|
ui::cache::Cache,
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UiRenderState {
|
pub struct UiRenderState {
|
||||||
pub active: HashMap<WidgetId, ActiveData>,
|
pub active: HashMap<WidgetId, ActiveData>,
|
||||||
pub layers: DrawLayers,
|
pub layers: PrimitiveLayers,
|
||||||
pub(super) output_size: Vec2,
|
pub(super) output_size: Vec2,
|
||||||
pub cache: Cache,
|
pub cache: Cache,
|
||||||
|
|
||||||
@@ -243,14 +243,14 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_layers(&self) {
|
pub fn debug_layers(&self) {
|
||||||
for ((idx, depth), draws) in self.layers.iter_depth() {
|
for ((idx, depth), primitives) in self.layers.iter_depth() {
|
||||||
let indent = " ".repeat(depth * 2);
|
let indent = " ".repeat(depth * 2);
|
||||||
let counts: Vec<String> = draws
|
let len = primitives.instances().len();
|
||||||
.primitives()
|
print!("{indent}{idx}: {len} primitives");
|
||||||
.iter()
|
if len >= 1 {
|
||||||
.map(|l| l.as_ref().map_or(0, |l| l.instances().len()).to_string())
|
print!(" ({})", primitives.instances()[0].binding);
|
||||||
.collect();
|
}
|
||||||
println!("{indent}{idx}: [{}]", counts.join(", "));
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +308,7 @@ impl UiRenderState {
|
|||||||
source,
|
source,
|
||||||
cache: &mut self.cache,
|
cache: &mut self.cache,
|
||||||
text: &mut ui.text,
|
text: &mut ui.text,
|
||||||
|
textures: &mut ui.textures,
|
||||||
widgets: &ui.widgets,
|
widgets: &ui.widgets,
|
||||||
outer,
|
outer,
|
||||||
output_size: self.output_size,
|
output_size: self.output_size,
|
||||||
|
|||||||
+5
-3
@@ -1,10 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, UiVec2,
|
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures,
|
||||||
WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
|
UiVec2, WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SizeCtx<'a> {
|
pub struct SizeCtx<'a> {
|
||||||
pub text: &'a mut TextData,
|
pub text: &'a mut TextData,
|
||||||
|
pub textures: &'a mut Textures,
|
||||||
pub(super) source: WidgetId,
|
pub(super) source: WidgetId,
|
||||||
pub(super) widgets: &'a Widgets,
|
pub(super) widgets: &'a Widgets,
|
||||||
pub(super) cache: &'a mut Cache,
|
pub(super) cache: &'a mut Cache,
|
||||||
@@ -32,6 +33,7 @@ impl SizeCtx<'_> {
|
|||||||
.get_dyn_dynamic(id)
|
.get_dyn_dynamic(id)
|
||||||
.desired_len::<A>(&mut SizeCtx {
|
.desired_len::<A>(&mut SizeCtx {
|
||||||
text: self.text,
|
text: self.text,
|
||||||
|
textures: self.textures,
|
||||||
source: self.source,
|
source: self.source,
|
||||||
widgets: self.widgets,
|
widgets: self.widgets,
|
||||||
cache: self.cache,
|
cache: self.cache,
|
||||||
@@ -80,7 +82,7 @@ impl SizeCtx<'_> {
|
|||||||
attrs: &TextAttrs,
|
attrs: &TextAttrs,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
) -> RenderedText {
|
) -> RenderedText {
|
||||||
self.text.render(buffer, attrs, width)
|
self.text.render(buffer, attrs, width, self.textures)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn label(&self, id: WidgetId) -> &String {
|
pub fn label(&self, id: WidgetId) -> &String {
|
||||||
|
|||||||
@@ -212,10 +212,10 @@ impl DefaultAppState for Client {
|
|||||||
render: &mut UiRenderState,
|
render: &mut UiRenderState,
|
||||||
) {
|
) {
|
||||||
let new = format!(
|
let new = format!(
|
||||||
"widgets: {}\nactive: {}\ntextures: {}",
|
"widgets: {}\nactive: {}\nviews: {}",
|
||||||
rsc.widgets().len(),
|
rsc.widgets().len(),
|
||||||
render.active_widgets(),
|
render.active_widgets(),
|
||||||
rsc.ui().textures.count(),
|
self.ui_state.renderer.ui.view_count(),
|
||||||
);
|
);
|
||||||
if new != *rsc.widgets()[self.info].content {
|
if new != *rsc.widgets()[self.info].content {
|
||||||
*rsc.widgets_mut()[self.info].content = new;
|
*rsc.widgets_mut()[self.info].content = new;
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,10 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use arboard::Clipboard;
|
use arboard::Clipboard;
|
||||||
use std::{marker::PhantomData, sync::Arc, time::Instant};
|
use std::{
|
||||||
|
marker::{PhantomData, Sized},
|
||||||
|
sync::Arc,
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Ime, WindowEvent},
|
event::{Ime, WindowEvent},
|
||||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||||
|
|||||||
+15
-22
@@ -1,4 +1,4 @@
|
|||||||
use iris_core::{UiData, UiRenderNode, UiRenderState};
|
use iris_core::{UiData, UiLimits, UiRenderNode, UiRenderState};
|
||||||
use pollster::FutureExt;
|
use pollster::FutureExt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu::*;
|
use wgpu::*;
|
||||||
@@ -22,20 +22,7 @@ impl UiRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self) {
|
pub fn draw(&mut self) {
|
||||||
let output = match self.surface.get_current_texture() {
|
let output = self.surface.get_current_texture().unwrap();
|
||||||
CurrentSurfaceTexture::Success(texture) => texture,
|
|
||||||
CurrentSurfaceTexture::Suboptimal(texture) => {
|
|
||||||
self.surface.configure(&self.device, &self.config);
|
|
||||||
texture
|
|
||||||
}
|
|
||||||
CurrentSurfaceTexture::Outdated | CurrentSurfaceTexture::Lost => {
|
|
||||||
self.surface.configure(&self.device, &self.config);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CurrentSurfaceTexture::Timeout
|
|
||||||
| CurrentSurfaceTexture::Occluded
|
|
||||||
| CurrentSurfaceTexture::Validation => return,
|
|
||||||
};
|
|
||||||
let view = output
|
let view = output
|
||||||
.texture
|
.texture
|
||||||
.create_view(&TextureViewDescriptor::default());
|
.create_view(&TextureViewDescriptor::default());
|
||||||
@@ -59,7 +46,7 @@ impl UiRenderer {
|
|||||||
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
self.queue.submit(std::iter::once(encoder.finish()));
|
||||||
self.window.pre_present_notify();
|
self.window.pre_present_notify();
|
||||||
self.queue.present(output);
|
output.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
|
||||||
@@ -78,10 +65,9 @@ impl UiRenderer {
|
|||||||
pub fn new(window: Arc<Window>) -> Self {
|
pub fn new(window: Arc<Window>) -> Self {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
|
|
||||||
let instance = Instance::new(InstanceDescriptor {
|
let instance = Instance::new(&InstanceDescriptor {
|
||||||
backends: Backends::PRIMARY,
|
backends: Backends::PRIMARY,
|
||||||
display: Some(Box::new(window.clone())),
|
..Default::default()
|
||||||
..InstanceDescriptor::new_without_display_handle()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let surface = instance
|
let surface = instance
|
||||||
@@ -93,14 +79,22 @@ impl UiRenderer {
|
|||||||
power_preference: PowerPreference::default(),
|
power_preference: PowerPreference::default(),
|
||||||
compatible_surface: Some(&surface),
|
compatible_surface: Some(&surface),
|
||||||
force_fallback_adapter: false,
|
force_fallback_adapter: false,
|
||||||
apply_limit_buckets: false,
|
|
||||||
})
|
})
|
||||||
.block_on()
|
.block_on()
|
||||||
.expect("Could not get adapter!");
|
.expect("Could not get adapter!");
|
||||||
|
|
||||||
|
let ui_limits = UiLimits::default();
|
||||||
|
|
||||||
let (device, queue) = adapter
|
let (device, queue) = adapter
|
||||||
.request_device(&DeviceDescriptor {
|
.request_device(&DeviceDescriptor {
|
||||||
|
required_features: Features::TEXTURE_BINDING_ARRAY
|
||||||
|
| Features::PARTIALLY_BOUND_BINDING_ARRAY
|
||||||
|
| Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
|
||||||
required_limits: Limits {
|
required_limits: Limits {
|
||||||
|
max_binding_array_elements_per_shader_stage: ui_limits
|
||||||
|
.max_binding_array_elements_per_shader_stage(),
|
||||||
|
max_binding_array_sampler_elements_per_shader_stage: ui_limits
|
||||||
|
.max_binding_array_sampler_elements_per_shader_stage(),
|
||||||
max_buffer_size: 1 << 30,
|
max_buffer_size: 1 << 30,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -120,7 +114,6 @@ impl UiRenderer {
|
|||||||
let config = SurfaceConfiguration {
|
let config = SurfaceConfiguration {
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: surface_format,
|
format: surface_format,
|
||||||
color_space: SurfaceColorSpace::Auto,
|
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
present_mode: PresentMode::AutoVsync,
|
present_mode: PresentMode::AutoVsync,
|
||||||
@@ -133,7 +126,7 @@ impl UiRenderer {
|
|||||||
|
|
||||||
let encoder = Self::create_encoder(&device);
|
let encoder = Self::create_encoder(&device);
|
||||||
|
|
||||||
let ui = UiRenderNode::new(&device, &config);
|
let ui = UiRenderNode::new(&device, &queue, &config, ui_limits);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
surface,
|
surface,
|
||||||
|
|||||||
+29
-89
@@ -4,14 +4,14 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CursorButton {
|
pub enum CursorButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Middle,
|
Middle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CursorSense {
|
pub enum CursorSense {
|
||||||
PressStart(CursorButton),
|
PressStart(CursorButton),
|
||||||
Pressing(CursorButton),
|
Pressing(CursorButton),
|
||||||
@@ -27,10 +27,7 @@ pub struct CursorSenses(Vec<CursorSense>);
|
|||||||
|
|
||||||
impl Event for CursorSenses {
|
impl Event for CursorSenses {
|
||||||
type Data<'a> = CursorData<'a>;
|
type Data<'a> = CursorData<'a>;
|
||||||
/// Who the cursor was inside on the last input, which is what says whose
|
type State = SensorState;
|
||||||
/// hover has ended -- including a widget a higher layer has since covered,
|
|
||||||
/// which this walk never reaches.
|
|
||||||
type Global = Vec<WidgetId>;
|
|
||||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||||
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
|
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
|
||||||
let mut data = data.clone();
|
let mut data = data.clone();
|
||||||
@@ -40,12 +37,6 @@ impl Event for CursorSenses {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A press or a scroll is used up by whatever answered it, so it stops
|
|
||||||
/// there. Hovering is not: a cursor resting somewhere goes on resting.
|
|
||||||
fn consumes(&self, data: &Self::Data<'_>) -> bool {
|
|
||||||
!data.sense.position_only()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorSense {
|
impl CursorSense {
|
||||||
@@ -61,12 +52,6 @@ impl CursorSense {
|
|||||||
pub fn is_dragging(&self) -> bool {
|
pub fn is_dragging(&self) -> bool {
|
||||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// False if the sense is a button or a scroll, true if it is only about
|
|
||||||
/// where the cursor is.
|
|
||||||
fn position_only(&self) -> bool {
|
|
||||||
matches!(self, Self::HoverStart | Self::Hovering | Self::HoverEnd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -111,12 +96,6 @@ impl CursorButtons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CursorState {
|
impl CursorState {
|
||||||
/// True if the cursor is only reporting where it is: no button and no
|
|
||||||
/// scroll this frame.
|
|
||||||
pub fn position_only(&self) -> bool {
|
|
||||||
self.scroll_delta == Vec2::ZERO && self.buttons.iter().all(|(_, state)| state.is_off())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
pub fn end_frame(&mut self) {
|
||||||
self.buttons.end_frame();
|
self.buttons.end_frame();
|
||||||
self.scroll_delta = Vec2::ZERO;
|
self.scroll_delta = Vec2::ZERO;
|
||||||
@@ -144,6 +123,11 @@ pub struct Sensor<Ctx: HasEvents, Data> {
|
|||||||
|
|
||||||
pub type SenseShape = UiRegion;
|
pub type SenseShape = UiRegion;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct SensorState {
|
||||||
|
pub hover: ActivationState,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CursorData<'a> {
|
pub struct CursorData<'a> {
|
||||||
/// where this widget was hit
|
/// where this widget was hit
|
||||||
@@ -179,79 +163,40 @@ impl SensorUi for UiRenderState {
|
|||||||
// this would probably be done through a generic parameter that adds yet another rsc /
|
// this would probably be done through a generic parameter that adds yet another rsc /
|
||||||
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
||||||
// update it there?
|
// update it there?
|
||||||
let active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||||
let was = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().global);
|
|
||||||
let position_only = cursor.position_only();
|
|
||||||
let mut now: Vec<WidgetId> = Vec::new();
|
|
||||||
let region_of = |id| Some(self.active.get(&id)?.region.to_px(window_size));
|
|
||||||
|
|
||||||
for layer in self.layers.indices().rev() {
|
for layer in self.layers.indices().rev() {
|
||||||
let mut consumed = false;
|
let mut sensed = false;
|
||||||
for id in active.get(&layer).into_flat_iter().map(|(id, _)| *id) {
|
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
|
||||||
let Some(region) = region_of(id) else {
|
let shape = self.active.get(id).unwrap().region;
|
||||||
continue;
|
let region = shape.to_px(window_size);
|
||||||
};
|
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||||
if !cursor.exists || !region.contains(cursor.pos) {
|
sensor.hover.update(in_shape);
|
||||||
|
if sensor.hover == ActivationState::Off {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
now.push(id);
|
sensed = true;
|
||||||
let hover = match was.contains(&id) {
|
|
||||||
true => ActivationState::On,
|
|
||||||
false => ActivationState::Start,
|
|
||||||
};
|
|
||||||
// A press or a scroll stops where something answered it, so a
|
|
||||||
// button over a list does not swallow the list's scrolling.
|
|
||||||
consumed |= deliver(self, rsc, state, id, hover, &cursor, region);
|
|
||||||
// A cursor doing neither stops at whatever it is over, so
|
|
||||||
// hovering does not reach through.
|
|
||||||
consumed |= position_only;
|
|
||||||
}
|
|
||||||
// Applied after the layer, never during it: senses on one layer do
|
|
||||||
// not block each other.
|
|
||||||
if consumed {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whatever the cursor was inside and is not now, whether it left or a
|
let cursor = cursor.clone();
|
||||||
// layer above took the input before the walk reached it. A widget that
|
|
||||||
// stopped being drawn has no region to report and is simply dropped.
|
|
||||||
for id in was {
|
|
||||||
if !now.contains(&id)
|
|
||||||
&& let Some(region) = region_of(id)
|
|
||||||
{
|
|
||||||
deliver(self, rsc, state, id, ActivationState::End, &cursor, region);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let senses = rsc.events_mut().get_type::<CursorSense>();
|
|
||||||
senses.active = active;
|
|
||||||
senses.global = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs one widget's cursor senses, and says whether they used up the input.
|
|
||||||
fn deliver<Rsc: HasEvents>(
|
|
||||||
render: &UiRenderState,
|
|
||||||
rsc: &mut Rsc,
|
|
||||||
state: &mut Rsc::State,
|
|
||||||
id: WidgetId,
|
|
||||||
hover: ActivationState,
|
|
||||||
cursor: &CursorState,
|
|
||||||
region: PixelRegion,
|
|
||||||
) -> bool {
|
|
||||||
let data = CursorData {
|
let data = CursorData {
|
||||||
pos: cursor.pos - region.top_left,
|
pos: cursor.pos - region.top_left,
|
||||||
size: region.bot_right - region.top_left,
|
size: region.bot_right - region.top_left,
|
||||||
scroll_delta: cursor.scroll_delta,
|
scroll_delta: cursor.scroll_delta,
|
||||||
hover,
|
hover: sensor.hover,
|
||||||
cursor: cursor.clone(),
|
cursor,
|
||||||
// this does not have any meaning;
|
// this does not have any meaning;
|
||||||
// might wanna set up Event to have a prepare stage
|
// might wanna set up Event to have a prepare stage
|
||||||
sense: CursorSense::Hovering,
|
sense: CursorSense::Hovering,
|
||||||
render,
|
render: self,
|
||||||
};
|
};
|
||||||
rsc.run_event::<CursorSense>(id, data, state)
|
rsc.run_event::<CursorSense>(*id, data, state);
|
||||||
|
}
|
||||||
|
if sensed {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rsc.events_mut().get_type::<CursorSense>().active = active;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_run(
|
pub fn should_run(
|
||||||
@@ -260,11 +205,6 @@ pub fn should_run(
|
|||||||
hover: ActivationState,
|
hover: ActivationState,
|
||||||
) -> Option<CursorSense> {
|
) -> Option<CursorSense> {
|
||||||
for sense in senses.iter() {
|
for sense in senses.iter() {
|
||||||
// A widget the cursor is no longer inside senses only its position:
|
|
||||||
// the press that ended its hover landed on something else.
|
|
||||||
if !hover.is_on() && !sense.position_only() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if match sense {
|
if match sense {
|
||||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ pub struct Image {
|
|||||||
|
|
||||||
impl Widget for Image {
|
impl Widget for Image {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.primitive(&self.handle);
|
painter.texture(&self.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod max_size;
|
|||||||
mod offset;
|
mod offset;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod set_size;
|
mod sized;
|
||||||
mod span;
|
mod span;
|
||||||
mod stack;
|
mod stack;
|
||||||
|
|
||||||
@@ -14,6 +14,6 @@ pub use max_size::*;
|
|||||||
pub use offset::*;
|
pub use offset::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use scroll::*;
|
pub use scroll::*;
|
||||||
pub use set_size::*;
|
pub use sized::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct SetSize {
|
pub struct Sized {
|
||||||
pub inner: StrongWidget,
|
pub inner: StrongWidget,
|
||||||
pub x: Option<Len>,
|
pub x: Option<Len>,
|
||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetSize {
|
impl Sized {
|
||||||
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
||||||
if let Some(x) = self.x {
|
if let Some(x) = self.x {
|
||||||
ctx.outer.x.select_len(x.apply_rest());
|
ctx.outer.x.select_len(x.apply_rest());
|
||||||
@@ -17,7 +17,7 @@ impl SetSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for SetSize {
|
impl Widget for Sized {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.widget(&self.inner);
|
painter.widget(&self.inner);
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::marker::Unsize;
|
use std::marker::{Sized, Unsize};
|
||||||
|
|
||||||
pub struct WidgetPtr {
|
pub struct WidgetPtr {
|
||||||
pub inner: Option<StrongWidget>,
|
pub inner: Option<StrongWidget>,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::marker::PhantomData;
|
use std::marker::{PhantomData, Sized};
|
||||||
|
|
||||||
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> {
|
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
|||||||
+26
-16
@@ -111,7 +111,7 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
self.text.view.buf.layout()
|
self.text.view.buf.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clamp_selection_to_layout(&mut self) {
|
fn refresh(&mut self) {
|
||||||
if let Some(sel) = self.text.selection {
|
if let Some(sel) = self.text.selection {
|
||||||
let layout = self.layout();
|
let layout = self.layout();
|
||||||
self.text.selection = Some(sel.refresh(layout));
|
self.text.selection = Some(sel.refresh(layout));
|
||||||
@@ -119,8 +119,8 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn take(&mut self) -> String {
|
pub fn take(&mut self) -> String {
|
||||||
let text = std::mem::take(self.text.view.buf.edit());
|
let text = self.text.view.buf.text().to_string();
|
||||||
self.text.selection = None;
|
self.set("");
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,8 +219,9 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
if end == 0 {
|
if end == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let start = {
|
||||||
let layout = self.layout();
|
let layout = self.layout();
|
||||||
let start = if word {
|
if word {
|
||||||
sel.focus().previous_logical_word(layout).index()
|
sel.focus().previous_logical_word(layout).index()
|
||||||
} else {
|
} else {
|
||||||
let Some(cluster) = sel.focus().logical_clusters(layout)[0] else {
|
let Some(cluster) = sel.focus().logical_clusters(layout)[0] else {
|
||||||
@@ -235,6 +236,7 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
.next_back()
|
.next_back()
|
||||||
.map_or(range.start, |(start, _)| start)
|
.map_or(range.start, |(start, _)| start)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.delete_range(start, end);
|
self.delete_range(start, end);
|
||||||
}
|
}
|
||||||
@@ -250,8 +252,9 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
if start >= self.text.view.buf.text().len() {
|
if start >= self.text.view.buf.text().len() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let end = {
|
||||||
let layout = self.layout();
|
let layout = self.layout();
|
||||||
let end = if word {
|
if word {
|
||||||
sel.focus().next_logical_word(layout).index()
|
sel.focus().next_logical_word(layout).index()
|
||||||
} else {
|
} else {
|
||||||
let clusters = sel.focus().logical_clusters(layout);
|
let clusters = sel.focus().logical_clusters(layout);
|
||||||
@@ -259,11 +262,17 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
cluster.text_range().end
|
cluster.text_range().end
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.delete_range(start, end);
|
self.delete_range(start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_range(&mut self, start: usize, end: usize) {
|
fn delete_range(&mut self, start: usize, end: usize) {
|
||||||
|
let len = self.text.view.buf.text().len();
|
||||||
|
let (start, end) = (start.min(end).min(len), start.max(end).min(len));
|
||||||
|
if start == end {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.text.view.buf.edit().replace_range(start..end, "");
|
self.text.view.buf.edit().replace_range(start..end, "");
|
||||||
self.text.view.buf.changed = true;
|
self.text.view.buf.changed = true;
|
||||||
self.set_caret(start);
|
self.set_caret(start);
|
||||||
@@ -285,31 +294,32 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
let prev_sel = self.text.selection;
|
let prev_sel = self.text.selection;
|
||||||
let prev_hit = self.text.double_hit;
|
let prev_hit = self.text.double_hit;
|
||||||
|
|
||||||
|
let outcome = {
|
||||||
let layout = self.layout();
|
let layout = self.layout();
|
||||||
let (selection, double_hit) = if drag {
|
if drag {
|
||||||
let Some(selection) = prev_sel else {
|
prev_sel.map(|sel| (Some(sel.extend_to_point(layout, pos.x, pos.y)), prev_hit))
|
||||||
return;
|
|
||||||
};
|
|
||||||
(selection.extend_to_point(layout, pos.x, pos.y), prev_hit)
|
|
||||||
} else {
|
} else {
|
||||||
let hit = Selection::from_point(layout, pos.x, pos.y);
|
let hit = Selection::from_point(layout, pos.x, pos.y);
|
||||||
let index = hit.focus().index();
|
let index = hit.focus().index();
|
||||||
// Successive clicks at one index select the word, then the line.
|
// Successive clicks at one index select the word, then the line.
|
||||||
if recent && prev_hit == Some(index) {
|
Some(if recent && prev_hit == Some(index) {
|
||||||
(Selection::line_from_point(layout, pos.x, pos.y), None)
|
(Some(Selection::line_from_point(layout, pos.x, pos.y)), None)
|
||||||
} else if recent && prev_sel.map(|s| s.focus().index()) == Some(index) {
|
} else if recent && prev_sel.map(|s| s.focus().index()) == Some(index) {
|
||||||
(
|
(
|
||||||
Selection::word_from_point(layout, pos.x, pos.y),
|
Some(Selection::word_from_point(layout, pos.x, pos.y)),
|
||||||
Some(index),
|
Some(index),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(hit, None)
|
(Some(hit), None)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.text.selection = Some(selection);
|
if let Some((selection, double_hit)) = outcome {
|
||||||
|
self.text.selection = selection;
|
||||||
self.text.double_hit = double_hit;
|
self.text.double_hit = double_hit;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deselect(&mut self) {
|
pub fn deselect(&mut self) {
|
||||||
self.text.selection = None;
|
self.text.selection = None;
|
||||||
@@ -324,7 +334,7 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
if let Some((old, selection)) = self.text.history.pop() {
|
if let Some((old, selection)) = self.text.history.pop() {
|
||||||
self.set(&old);
|
self.set(&old);
|
||||||
self.text.selection = selection;
|
self.text.selection = selection;
|
||||||
self.clamp_selection_to_layout();
|
self.refresh();
|
||||||
}
|
}
|
||||||
} else if self.text.view.buf.text() != old.0 {
|
} else if self.text.view.buf.text() != old.0 {
|
||||||
self.text.history.push(old);
|
self.text.history.push(old);
|
||||||
|
|||||||
+22
-19
@@ -23,7 +23,7 @@ pub struct TextView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TextView {
|
impl TextView {
|
||||||
fn is_empty(&self) -> bool {
|
fn is_blank(&self) -> bool {
|
||||||
self.buf.is_empty()
|
self.buf.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,25 +52,31 @@ impl TextView {
|
|||||||
.align(self.align)
|
.align(self.align)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, ctx: &mut SizeCtx) -> &RenderedText {
|
fn render(&mut self, ctx: &mut SizeCtx) -> RenderedText {
|
||||||
let width = if self.attrs.wrap {
|
let width = if self.attrs.wrap {
|
||||||
Some(ctx.px_size().x)
|
Some(ctx.px_size().x)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if width != self.width || self.tex.is_none() || self.attrs.changed || self.buf.changed {
|
if width == self.width
|
||||||
|
&& let Some(tex) = &self.tex
|
||||||
|
&& !self.attrs.changed
|
||||||
|
&& !self.buf.changed
|
||||||
|
{
|
||||||
|
return tex.clone();
|
||||||
|
}
|
||||||
self.width = width;
|
self.width = width;
|
||||||
self.tex = Some(ctx.draw_text(&mut self.buf, &self.attrs, width));
|
let tex = ctx.draw_text(&mut self.buf, &self.attrs, width);
|
||||||
|
self.tex = Some(tex.clone());
|
||||||
self.attrs.changed = false;
|
self.attrs.changed = false;
|
||||||
self.buf.changed = false;
|
self.buf.changed = false;
|
||||||
}
|
tex
|
||||||
self.tex.as_ref().unwrap()
|
|
||||||
}
|
}
|
||||||
pub fn tex(&self) -> Option<&RenderedText> {
|
pub fn tex(&self) -> Option<&RenderedText> {
|
||||||
self.tex.as_ref()
|
self.tex.as_ref()
|
||||||
}
|
}
|
||||||
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
if self.is_empty()
|
if self.is_blank()
|
||||||
&& let Some(hint) = &self.hint
|
&& let Some(hint) = &self.hint
|
||||||
{
|
{
|
||||||
ctx.width(hint)
|
ctx.width(hint)
|
||||||
@@ -79,7 +85,7 @@ impl TextView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
if self.is_empty()
|
if self.is_blank()
|
||||||
&& let Some(hint) = &self.hint
|
&& let Some(hint) = &self.hint
|
||||||
{
|
{
|
||||||
ctx.height(hint)
|
ctx.height(hint)
|
||||||
@@ -88,19 +94,16 @@ impl TextView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
||||||
let align = self.align;
|
|
||||||
if self.is_empty() && self.hint.is_some() {
|
|
||||||
let region = self.render(&mut painter.size_ctx()).size.align(align);
|
|
||||||
if let Some(hint) = &self.hint {
|
|
||||||
painter.widget(hint);
|
|
||||||
}
|
|
||||||
return region;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tex = self.render(&mut painter.size_ctx());
|
let tex = self.render(&mut painter.size_ctx());
|
||||||
let region = tex.size.align(align);
|
let region = tex.size.align(self.align);
|
||||||
|
if self.is_blank()
|
||||||
|
&& let Some(hint) = &self.hint
|
||||||
|
{
|
||||||
|
painter.widget(hint);
|
||||||
|
} else {
|
||||||
let within = region.within(&painter.region());
|
let within = region.within(&painter.region());
|
||||||
painter.glyphs(tex, within);
|
painter.glyphs(&tex, within);
|
||||||
|
}
|
||||||
region
|
region
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, SetSize> {
|
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, Sized> {
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
move |state| SetSize {
|
move |state| Sized {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: Some(size.x),
|
x: Some(size.x),
|
||||||
y: Some(size.y),
|
y: Some(size.y),
|
||||||
@@ -58,18 +58,18 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |state| SetSize {
|
move |state| Sized {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: Some(len),
|
x: Some(len),
|
||||||
y: None,
|
y: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |state| SetSize {
|
move |state| Sized {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: None,
|
x: None,
|
||||||
y: Some(len),
|
y: Some(len),
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
//! What one frame of `UiRenderNode::draw` costs on the CPU, against the number
|
|
||||||
//! of layers it walks. Recording only: the pass is built and dropped without
|
|
||||||
//! being submitted, so this is the loop's cost and not the GPU's.
|
|
||||||
//!
|
|
||||||
//! cargo test --release --test draw_cost -- --ignored --nocapture
|
|
||||||
//!
|
|
||||||
//! Wall time is the wrong number to read for anything under a few percent --
|
|
||||||
//! it varied by 2x between runs of one unchanged binary where instructions
|
|
||||||
//! retired varied by 0.1%. Count those instead:
|
|
||||||
//!
|
|
||||||
//! perf stat -e instructions:u target/release/.../draw_cost-* --ignored
|
|
||||||
//!
|
|
||||||
//! That is how `PrimitiveRender` was measured against a match in the renderer:
|
|
||||||
//! 6 instructions per list drawn, against the ~5,400 wgpu spends recording
|
|
||||||
//! one.
|
|
||||||
//!
|
|
||||||
//! The instance is leaked deliberately. A Vulkan loader may unload the driver
|
|
||||||
//! when the last one drops, which can fault as a thread that used it exits --
|
|
||||||
//! and every test runs on a spawned thread.
|
|
||||||
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use iris::prelude::*;
|
|
||||||
use iris_core::{
|
|
||||||
GlyphPrimitive, MaskIdx, PrimitiveInst, RectPrimitive, TextureHandle, TexturePrimitive, UiData,
|
|
||||||
UiRegion, UiRenderNode, UiRenderState,
|
|
||||||
};
|
|
||||||
use wgpu::{Color as GpuColor, *};
|
|
||||||
|
|
||||||
const SIZE: u32 = 1024;
|
|
||||||
const FRAMES: u32 = 200;
|
|
||||||
/// Reported as the best of this many batches, since the mean moves by more
|
|
||||||
/// than the thing being measured.
|
|
||||||
const BATCHES: u32 = 8;
|
|
||||||
|
|
||||||
fn gpu() -> Option<(Device, Queue)> {
|
|
||||||
// Probed rather than assumed: there may be no Vulkan adapter, and GL is
|
|
||||||
// what is left when there is not.
|
|
||||||
let all = Instance::new(InstanceDescriptor::new_without_display_handle());
|
|
||||||
let instance = match pollster::block_on(all.request_adapter(&RequestAdapterOptions::default()))
|
|
||||||
{
|
|
||||||
Ok(_) => all,
|
|
||||||
Err(_) => Instance::new(InstanceDescriptor {
|
|
||||||
backends: Backends::GL,
|
|
||||||
..InstanceDescriptor::new_without_display_handle()
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
// Leaked rather than dropped: see the note at the top of the file.
|
|
||||||
let instance: &'static Instance = Box::leak(Box::new(instance));
|
|
||||||
let adapter =
|
|
||||||
pollster::block_on(instance.request_adapter(&RequestAdapterOptions::default())).ok()?;
|
|
||||||
println!("adapter: {:?}", adapter.get_info());
|
|
||||||
pollster::block_on(adapter.request_device(&DeviceDescriptor::default())).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config(format: TextureFormat) -> SurfaceConfiguration {
|
|
||||||
SurfaceConfiguration {
|
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
format,
|
|
||||||
color_space: SurfaceColorSpace::Auto,
|
|
||||||
width: SIZE,
|
|
||||||
height: SIZE,
|
|
||||||
present_mode: PresentMode::Fifo,
|
|
||||||
desired_maximum_frame_latency: 2,
|
|
||||||
alpha_mode: CompositeAlphaMode::Auto,
|
|
||||||
view_formats: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Every layer draws all three primitives, so the renderer takes a different
|
|
||||||
/// path for each list it walks -- which is the case a single-primitive layer
|
|
||||||
/// would never exercise. Images are bound per instance, so there are few.
|
|
||||||
fn fill(
|
|
||||||
ui: &mut UiData,
|
|
||||||
render: &mut UiRenderState,
|
|
||||||
layers: usize,
|
|
||||||
per_layer: usize,
|
|
||||||
) -> Vec<TextureHandle> {
|
|
||||||
let rect = ui.primitives.kind::<RectPrimitive>();
|
|
||||||
let glyph = ui.primitives.kind::<GlyphPrimitive>();
|
|
||||||
let texture = ui.primitives.kind::<TexturePrimitive>();
|
|
||||||
let id = ui.widgets.add_strong(Rect::new(UiColor::WHITE)).id();
|
|
||||||
let handles: Vec<_> = (0..4)
|
|
||||||
.map(|_| ui.textures.add(image::RgbaImage::new(4, 4)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut layer = 0;
|
|
||||||
for _ in 0..layers {
|
|
||||||
for _ in 0..per_layer {
|
|
||||||
render.layers.write(
|
|
||||||
layer,
|
|
||||||
PrimitiveInst {
|
|
||||||
kind: rect,
|
|
||||||
id,
|
|
||||||
primitive: RectPrimitive::color(UiColor::WHITE),
|
|
||||||
region: UiRegion::FULL,
|
|
||||||
mask_idx: MaskIdx::NONE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
render.layers.write(
|
|
||||||
layer,
|
|
||||||
PrimitiveInst {
|
|
||||||
kind: glyph,
|
|
||||||
id,
|
|
||||||
primitive: GlyphPrimitive {
|
|
||||||
uv_min: vec2(0.0, 0.0),
|
|
||||||
uv_max: vec2(1.0, 1.0),
|
|
||||||
layer: 0,
|
|
||||||
color: UiColor::WHITE,
|
|
||||||
flags: 0,
|
|
||||||
},
|
|
||||||
region: UiRegion::FULL,
|
|
||||||
mask_idx: MaskIdx::NONE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for h in &handles[..2] {
|
|
||||||
render.layers.write(
|
|
||||||
layer,
|
|
||||||
PrimitiveInst {
|
|
||||||
kind: texture,
|
|
||||||
id,
|
|
||||||
primitive: TexturePrimitive::from(h),
|
|
||||||
region: UiRegion::FULL,
|
|
||||||
mask_idx: MaskIdx::NONE,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
layer = render.layers.next(layer);
|
|
||||||
}
|
|
||||||
handles
|
|
||||||
}
|
|
||||||
|
|
||||||
fn frame_cost(device: &Device, queue: &Queue, layers: usize, per_layer: usize) -> f64 {
|
|
||||||
let format = TextureFormat::Bgra8Unorm;
|
|
||||||
let mut node = UiRenderNode::new(device, &config(format));
|
|
||||||
let mut ui = UiData::default();
|
|
||||||
let mut render = UiRenderState::new();
|
|
||||||
let _handles = fill(&mut ui, &mut render, layers, per_layer);
|
|
||||||
node.update(device, queue, &mut ui, &mut render);
|
|
||||||
|
|
||||||
let target = device.create_texture(&TextureDescriptor {
|
|
||||||
label: Some("draw cost"),
|
|
||||||
size: Extent3d {
|
|
||||||
width: SIZE,
|
|
||||||
height: SIZE,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: TextureDimension::D2,
|
|
||||||
format,
|
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
view_formats: &[],
|
|
||||||
});
|
|
||||||
let view = target.create_view(&TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
let record = |frames: u32| {
|
|
||||||
let start = Instant::now();
|
|
||||||
for _ in 0..frames {
|
|
||||||
let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
|
|
||||||
{
|
|
||||||
let pass = &mut encoder.begin_render_pass(&RenderPassDescriptor {
|
|
||||||
color_attachments: &[Some(RenderPassColorAttachment {
|
|
||||||
view: &view,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: Operations {
|
|
||||||
load: LoadOp::Clear(GpuColor::BLACK),
|
|
||||||
store: StoreOp::Store,
|
|
||||||
},
|
|
||||||
depth_slice: None,
|
|
||||||
})],
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
node.draw(pass);
|
|
||||||
}
|
|
||||||
drop(encoder.finish());
|
|
||||||
}
|
|
||||||
start.elapsed().as_secs_f64() / frames as f64
|
|
||||||
};
|
|
||||||
record(FRAMES / 4);
|
|
||||||
(0..BATCHES)
|
|
||||||
.map(|_| record(FRAMES))
|
|
||||||
.fold(f64::MAX, f64::min)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore = "measurement, not a check"]
|
|
||||||
fn draw_cost_by_layer_count() {
|
|
||||||
let Some((device, queue)) = gpu() else {
|
|
||||||
panic!("no wgpu device; see the this-machine-graphics notes");
|
|
||||||
};
|
|
||||||
println!(
|
|
||||||
"layers, each 8 rects + 8 glyphs + 2 images: us/frame (us per layer), best of {BATCHES}"
|
|
||||||
);
|
|
||||||
let base = frame_cost(&device, &queue, 1, 8) * 1e6;
|
|
||||||
for layers in [8, 64, 256, 1024] {
|
|
||||||
let per_frame = frame_cost(&device, &queue, layers, 8) * 1e6;
|
|
||||||
// Net of the empty pass, which is the same in any version of this.
|
|
||||||
println!(
|
|
||||||
"{layers:>5}: {per_frame:8.1} us ({:.3} us)",
|
|
||||||
(per_frame - base).max(0.0) / layers as f64
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,367 +0,0 @@
|
|||||||
//! Input across layers: what stops at a layer, what passes through it, and
|
|
||||||
//! where hovering stops. These drive `run_sensors` directly, which needs no
|
|
||||||
//! GPU and no window.
|
|
||||||
|
|
||||||
use iris::prelude::*;
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
|
||||||
|
|
||||||
struct SenseRsc {
|
|
||||||
ui: UiData,
|
|
||||||
events: EventManager<SenseRsc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiRsc for SenseRsc {
|
|
||||||
fn ui(&self) -> &UiData {
|
|
||||||
&self.ui
|
|
||||||
}
|
|
||||||
fn ui_mut(&mut self) -> &mut UiData {
|
|
||||||
&mut self.ui
|
|
||||||
}
|
|
||||||
fn on_draw(&mut self, active: &ActiveData) {
|
|
||||||
self.events.draw(active);
|
|
||||||
}
|
|
||||||
fn on_undraw(&mut self, active: &ActiveData) {
|
|
||||||
self.events.undraw(active);
|
|
||||||
}
|
|
||||||
fn on_remove(&mut self, id: WidgetId) {
|
|
||||||
self.events.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasState for SenseRsc {
|
|
||||||
type State = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasEvents for SenseRsc {
|
|
||||||
fn events(&self) -> &EventManager<Self> {
|
|
||||||
&self.events
|
|
||||||
}
|
|
||||||
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
|
||||||
&mut self.events
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const WINDOW: f32 = 100.0;
|
|
||||||
|
|
||||||
/// Every sense that has fired on one widget since it was last read.
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
struct Fired(Rc<RefCell<Vec<CursorSense>>>);
|
|
||||||
|
|
||||||
impl Fired {
|
|
||||||
fn take(&self) -> Vec<CursorSense> {
|
|
||||||
std::mem::take(&mut self.0.borrow_mut())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Ui {
|
|
||||||
rsc: SenseRsc,
|
|
||||||
render: UiRenderState,
|
|
||||||
state: (),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ui {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
rsc: SenseRsc {
|
|
||||||
ui: UiData::default(),
|
|
||||||
events: EventManager::default(),
|
|
||||||
},
|
|
||||||
render: UiRenderState::new(),
|
|
||||||
state: (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn listen<W: Widget + ?core::marker::Sized + 'static>(
|
|
||||||
&mut self,
|
|
||||||
widget: &StrongWidget<W>,
|
|
||||||
senses: impl Into<CursorSenses>,
|
|
||||||
) -> Fired {
|
|
||||||
let fired = Fired::default();
|
|
||||||
let sink = fired.clone();
|
|
||||||
self.rsc
|
|
||||||
.register_event(widget.weak(), senses.into(), move |ctx, _rsc| {
|
|
||||||
sink.0.borrow_mut().push(ctx.data.sense)
|
|
||||||
});
|
|
||||||
fired
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stacks the widgets bottom first, each on its own layer, and lays them
|
|
||||||
/// out in a square window.
|
|
||||||
fn stack(&mut self, children: Vec<StrongWidget>) {
|
|
||||||
let root = self
|
|
||||||
.rsc
|
|
||||||
.ui
|
|
||||||
.widgets
|
|
||||||
.add_strong(Stack {
|
|
||||||
children,
|
|
||||||
size: StackSize::default(),
|
|
||||||
})
|
|
||||||
.any();
|
|
||||||
self.render.resize((WINDOW, WINDOW));
|
|
||||||
self.render.update(&root, &mut self.rsc);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cursor(&mut self, at: (f32, f32)) -> CursorState {
|
|
||||||
CursorState {
|
|
||||||
pos: at.into(),
|
|
||||||
exists: true,
|
|
||||||
buttons: Default::default(),
|
|
||||||
scroll_delta: Vec2::ZERO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&mut self, cursor: CursorState) {
|
|
||||||
self.render.run_sensors(
|
|
||||||
&mut self.rsc,
|
|
||||||
&mut self.state,
|
|
||||||
cursor,
|
|
||||||
(WINDOW, WINDOW).into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full(ui: &mut Ui) -> StrongWidget<Rect> {
|
|
||||||
rect(UiColor::WHITE).add_strong(&mut ui.rsc)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hover_stops_at_the_topmost_widget() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let (bottom, middle, top) = (full(ui), full(ui), full(ui));
|
|
||||||
let bottom_hover = ui.listen(&bottom, CursorSense::HoverStart);
|
|
||||||
let middle_hover = ui.listen(&middle, CursorSense::HoverStart);
|
|
||||||
let top_hover = ui.listen(&top, CursorSense::HoverStart);
|
|
||||||
ui.stack(vec![bottom.any(), middle.any(), top.any()]);
|
|
||||||
|
|
||||||
let cursor = ui.cursor((50.0, 50.0));
|
|
||||||
ui.run(cursor);
|
|
||||||
|
|
||||||
assert_eq!(top_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
assert_eq!(
|
|
||||||
middle_hover.take(),
|
|
||||||
[],
|
|
||||||
"hover is not shared with a layer below"
|
|
||||||
);
|
|
||||||
assert_eq!(bottom_hover.take(), []);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let (list, button, overlay) = (full(ui), full(ui), full(ui));
|
|
||||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
|
||||||
let clicked = ui.listen(&button, CursorSense::click());
|
|
||||||
let overlay_clicked = ui.listen(&overlay, CursorSense::click());
|
|
||||||
ui.stack(vec![list.any(), button.any(), overlay.any()]);
|
|
||||||
|
|
||||||
let mut cursor = ui.cursor((50.0, 50.0));
|
|
||||||
cursor.scroll_delta = (0.0, 10.0).into();
|
|
||||||
ui.run(cursor);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
scrolled.take(),
|
|
||||||
[CursorSense::Scroll],
|
|
||||||
"two layers of click-only widgets do not stop a scroll"
|
|
||||||
);
|
|
||||||
assert_eq!(clicked.take(), []);
|
|
||||||
assert_eq!(overlay_clicked.take(), []);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn only_the_topmost_listener_takes_a_press() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let (below, above) = (full(ui), full(ui));
|
|
||||||
let below_clicked = ui.listen(&below, CursorSense::click());
|
|
||||||
let above_clicked = ui.listen(&above, CursorSense::click());
|
|
||||||
ui.stack(vec![below.any(), above.any()]);
|
|
||||||
|
|
||||||
let mut cursor = ui.cursor((50.0, 50.0));
|
|
||||||
cursor.buttons.left = ActivationState::Start;
|
|
||||||
ui.run(cursor);
|
|
||||||
|
|
||||||
assert_eq!(above_clicked.take(), [CursorSense::click()]);
|
|
||||||
assert_eq!(below_clicked.take(), [], "one press goes to one widget");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_press_beside_the_button_reaches_the_layer_below() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let list = full(ui);
|
|
||||||
// The row above the list covers it, but only its left half is the button.
|
|
||||||
let button = full(ui);
|
|
||||||
let gap = full(ui);
|
|
||||||
let list_clicked = ui.listen(&list, CursorSense::click());
|
|
||||||
let button_clicked = ui.listen(&button, CursorSense::click());
|
|
||||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
|
||||||
children: vec![button.any(), gap.any()],
|
|
||||||
dir: Dir::RIGHT,
|
|
||||||
gap: 0.0,
|
|
||||||
});
|
|
||||||
ui.stack(vec![list.any(), row.any()]);
|
|
||||||
|
|
||||||
let mut on_button = ui.cursor((20.0, 50.0));
|
|
||||||
on_button.buttons.left = ActivationState::Start;
|
|
||||||
ui.run(on_button);
|
|
||||||
assert_eq!(button_clicked.take(), [CursorSense::click()]);
|
|
||||||
assert_eq!(list_clicked.take(), []);
|
|
||||||
|
|
||||||
let mut beside_it = ui.cursor((80.0, 50.0));
|
|
||||||
beside_it.buttons.left = ActivationState::Start;
|
|
||||||
ui.run(beside_it);
|
|
||||||
assert_eq!(button_clicked.take(), [], "the cursor is not on the button");
|
|
||||||
assert_eq!(
|
|
||||||
list_clicked.take(),
|
|
||||||
[CursorSense::click()],
|
|
||||||
"a press beside the button belongs to what is under it"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn leaving_a_widget_still_ends_its_hover() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let widget = full(ui);
|
|
||||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
ui.stack(vec![widget.any()]);
|
|
||||||
|
|
||||||
let cursor = ui.cursor((50.0, 50.0));
|
|
||||||
ui.run(cursor);
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
|
||||||
|
|
||||||
let mut gone = ui.cursor((50.0, 50.0));
|
|
||||||
gone.exists = false;
|
|
||||||
ui.run(gone);
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn leaving_a_widget_does_not_block_the_layer_below() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let below = full(ui);
|
|
||||||
// Only the left half of the layer above is a widget, so the cursor can
|
|
||||||
// leave it without leaving the one underneath.
|
|
||||||
let (above, gap) = (full(ui), full(ui));
|
|
||||||
let below_hover = ui.listen(&below, CursorSense::HoverStart);
|
|
||||||
let above_hover = ui.listen(&above, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
|
||||||
children: vec![above.any(), gap.any()],
|
|
||||||
dir: Dir::RIGHT,
|
|
||||||
gap: 0.0,
|
|
||||||
});
|
|
||||||
ui.stack(vec![below.any(), row.any()]);
|
|
||||||
|
|
||||||
let on_above = ui.cursor((20.0, 50.0));
|
|
||||||
ui.run(on_above);
|
|
||||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
assert_eq!(below_hover.take(), [], "the layer above is over it");
|
|
||||||
|
|
||||||
let beside_it = ui.cursor((80.0, 50.0));
|
|
||||||
ui.run(beside_it);
|
|
||||||
assert_eq!(above_hover.take(), [CursorSense::HoverEnd]);
|
|
||||||
assert_eq!(
|
|
||||||
below_hover.take(),
|
|
||||||
[CursorSense::HoverStart],
|
|
||||||
"ending a hover above must not stop the hover below"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hovering_a_button_above_does_not_stop_a_later_scroll() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let (list, button) = (full(ui), full(ui));
|
|
||||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
|
||||||
let clicked = ui.listen(&button, CursorSense::click());
|
|
||||||
ui.stack(vec![list.any(), button.any()]);
|
|
||||||
|
|
||||||
// The hover arrives in its own frame, as a window delivers it.
|
|
||||||
let hover = ui.cursor((50.0, 50.0));
|
|
||||||
ui.run(hover);
|
|
||||||
assert_eq!(scrolled.take(), []);
|
|
||||||
|
|
||||||
let mut wheel = ui.cursor((50.0, 50.0));
|
|
||||||
wheel.scroll_delta = (0.0, 10.0).into();
|
|
||||||
ui.run(wheel);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
scrolled.take(),
|
|
||||||
[CursorSense::Scroll],
|
|
||||||
"a hover already resting on the button must not consume the wheel"
|
|
||||||
);
|
|
||||||
assert_eq!(clicked.take(), []);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn covering_a_widget_ends_its_hover() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
let below = full(ui);
|
|
||||||
// Only the left half of the layer above is a widget, so the cursor can
|
|
||||||
// start beside it and then move onto it.
|
|
||||||
let (above, gap) = (full(ui), full(ui));
|
|
||||||
let below_hover = ui.listen(&below, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
let above_hover = ui.listen(&above, CursorSense::HoverStart);
|
|
||||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
|
||||||
children: vec![above.any(), gap.any()],
|
|
||||||
dir: Dir::RIGHT,
|
|
||||||
gap: 0.0,
|
|
||||||
});
|
|
||||||
ui.stack(vec![below.any(), row.any()]);
|
|
||||||
|
|
||||||
let beside_it = ui.cursor((80.0, 50.0));
|
|
||||||
ui.run(beside_it);
|
|
||||||
assert_eq!(below_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
|
|
||||||
let onto_above = ui.cursor((20.0, 50.0));
|
|
||||||
ui.run(onto_above);
|
|
||||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
|
||||||
assert_eq!(
|
|
||||||
below_hover.take(),
|
|
||||||
[CursorSense::HoverEnd],
|
|
||||||
"a widget covered by one that took the input is no longer hovered"
|
|
||||||
);
|
|
||||||
|
|
||||||
let off_again = ui.cursor((80.0, 50.0));
|
|
||||||
ui.run(off_again);
|
|
||||||
assert_eq!(
|
|
||||||
below_hover.take(),
|
|
||||||
[CursorSense::HoverStart],
|
|
||||||
"uncovering it hovers it again"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hover_starts_and_ends_once_each() {
|
|
||||||
let ui = &mut Ui::new();
|
|
||||||
// Only the left half is the widget, so the cursor can leave it without
|
|
||||||
// leaving the window.
|
|
||||||
let (widget, gap) = (full(ui), full(ui));
|
|
||||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
|
||||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
|
||||||
children: vec![widget.any(), gap.any()],
|
|
||||||
dir: Dir::RIGHT,
|
|
||||||
gap: 0.0,
|
|
||||||
});
|
|
||||||
ui.stack(vec![row.any()]);
|
|
||||||
|
|
||||||
let inside = ui.cursor((20.0, 50.0));
|
|
||||||
ui.run(inside);
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
|
||||||
|
|
||||||
let further_in = ui.cursor((30.0, 50.0));
|
|
||||||
ui.run(further_in);
|
|
||||||
assert_eq!(hover.take(), [], "staying inside is not a second start");
|
|
||||||
|
|
||||||
let outside = ui.cursor((80.0, 50.0));
|
|
||||||
ui.run(outside);
|
|
||||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
|
||||||
|
|
||||||
let further_out = ui.cursor((90.0, 50.0));
|
|
||||||
ui.run(further_out);
|
|
||||||
assert_eq!(hover.take(), [], "an ended hover does not end again");
|
|
||||||
|
|
||||||
let back_inside = ui.cursor((20.0, 50.0));
|
|
||||||
ui.run(back_inside);
|
|
||||||
assert_eq!(
|
|
||||||
hover.take(),
|
|
||||||
[CursorSense::HoverStart],
|
|
||||||
"re-entering starts it"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user