initial commit

This commit is contained in:
2025-08-08 18:22:52 -04:00
parent eba8481bde
commit 56beeb80f3
31 changed files with 1223 additions and 1197 deletions

313
Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "ab_glyph"
version = "0.2.29"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0"
checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
@@ -14,9 +14,9 @@ dependencies = [
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169"
[[package]]
name = "ahash"
@@ -48,7 +48,7 @@ dependencies = [
"log",
"ndk",
"ndk-context",
"ndk-sys 0.6.0+11769913",
"ndk-sys",
"num_enum",
"thiserror 1.0.69",
]
@@ -154,24 +154,24 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.18.1"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
version = "1.23.1"
version = "1.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.9.3"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29"
dependencies = [
"proc-macro2",
"quote",
@@ -212,9 +212,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.27"
version = "1.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
dependencies = [
"jobserver",
"libc",
@@ -299,6 +299,16 @@ dependencies = [
"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]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -312,8 +322,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-graphics-types",
"core-foundation 0.9.4",
"core-graphics-types 0.1.3",
"foreign-types",
"libc",
]
@@ -325,7 +335,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
dependencies = [
"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.9.1",
"core-foundation 0.10.1",
"libc",
]
@@ -337,9 +358,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "cursor-icon"
@@ -399,18 +420,6 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys 0.59.0",
]
[[package]]
name = "foldhash"
version = "0.1.5"
@@ -497,15 +506,6 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "glam"
version = "0.30.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50a99dbe56b72736564cfa4b85bf9a33079f16ae8b74983ab06af3b1a3696b11"
dependencies = [
"bytemuck",
]
[[package]]
name = "glow"
version = "0.16.0"
@@ -583,7 +583,6 @@ name = "gui"
version = "0.1.0"
dependencies = [
"bytemuck",
"glam",
"notify",
"pollster",
"rhai",
@@ -604,19 +603,13 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.4"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
@@ -631,9 +624,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "indexmap"
version = "2.9.0"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown",
@@ -760,7 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.53.2",
"windows-targets 0.53.3",
]
[[package]]
@@ -771,13 +764,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.3"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
"bitflags 2.9.1",
"libc",
"redox_syscall 0.5.13",
"redox_syscall 0.5.17",
]
[[package]]
@@ -794,9 +787,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litrs"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
[[package]]
name = "lock_api"
@@ -831,22 +824,22 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "memmap2"
version = "0.9.5"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
dependencies = [
"libc",
]
[[package]]
name = "metal"
version = "0.31.0"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e"
checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605"
dependencies = [
"bitflags 2.9.1",
"block",
"core-graphics-types",
"core-graphics-types 0.2.0",
"foreign-types",
"log",
"objc",
@@ -867,25 +860,26 @@ dependencies = [
[[package]]
name = "naga"
version = "25.0.1"
version = "26.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632"
checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c"
dependencies = [
"arrayvec",
"bit-set",
"bitflags 2.9.1",
"cfg-if",
"cfg_aliases",
"codespan-reporting",
"half",
"hashbrown",
"hexf-parse",
"indexmap",
"libm",
"log",
"num-traits",
"once_cell",
"rustc-hash",
"spirv",
"strum",
"thiserror 2.0.12",
"unicode-ident",
]
@@ -899,7 +893,7 @@ dependencies = [
"bitflags 2.9.1",
"jni-sys",
"log",
"ndk-sys 0.6.0+11769913",
"ndk-sys",
"num_enum",
"raw-window-handle",
"thiserror 1.0.69",
@@ -911,15 +905,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
[[package]]
name = "ndk-sys"
version = "0.5.0+25.2.9519653"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
dependencies = [
"jni-sys",
]
[[package]]
name = "ndk-sys"
version = "0.6.0+11769913"
@@ -931,12 +916,11 @@ dependencies = [
[[package]]
name = "notify"
version = "8.0.0"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [
"bitflags 2.9.1",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
@@ -945,7 +929,7 @@ dependencies = [
"mio",
"notify-types",
"walkdir",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1218,18 +1202,18 @@ dependencies = [
[[package]]
name = "ordered-float"
version = "4.6.0"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
checksum = "e2c1f9f56e534ac6a9b8a4600bdf0f530fb393b5f393e7b4d03489c3cf0c3f01"
dependencies = [
"num-traits",
]
[[package]]
name = "owned_ttf_parser"
version = "0.25.0"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4"
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
dependencies = [
"ttf-parser",
]
@@ -1252,7 +1236,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.13",
"redox_syscall 0.5.17",
"smallvec",
"windows-targets 0.52.6",
]
@@ -1303,17 +1287,16 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "polling"
version = "3.8.0"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50"
checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix 1.0.7",
"tracing",
"windows-sys 0.59.0",
"rustix 1.0.8",
"windows-sys 0.60.2",
]
[[package]]
@@ -1328,6 +1311,15 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "presser"
version = "0.3.1"
@@ -1405,9 +1397,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.13"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags 2.9.1",
]
@@ -1421,7 +1413,7 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "rhai"
version = "1.22.2"
source = "git+https://github.com/rhaiscript/rhai#08baaaf4e2d6767f2da4d89a4f91352f8637b45a"
source = "git+https://github.com/rhaiscript/rhai#9508db6a828d8d8b2ac868054d985098c62ba904"
dependencies = [
"ahash",
"bitflags 2.9.1",
@@ -1436,8 +1428,8 @@ dependencies = [
[[package]]
name = "rhai_codegen"
version = "3.0.0"
source = "git+https://github.com/rhaiscript/rhai#08baaaf4e2d6767f2da4d89a4f91352f8637b45a"
version = "3.1.0"
source = "git+https://github.com/rhaiscript/rhai#9508db6a828d8d8b2ac868054d985098c62ba904"
dependencies = [
"proc-macro2",
"quote",
@@ -1465,15 +1457,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -1544,9 +1536,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "slab"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "slotmap"
@@ -1629,28 +1621,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "2.0.104"
@@ -1912,13 +1882,13 @@ dependencies = [
[[package]]
name = "wayland-backend"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121"
checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
dependencies = [
"cc",
"downcast-rs",
"rustix 0.38.44",
"rustix 1.0.8",
"scoped-tls",
"smallvec",
"wayland-sys",
@@ -1926,12 +1896,12 @@ dependencies = [
[[package]]
name = "wayland-client"
version = "0.31.10"
version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61"
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
dependencies = [
"bitflags 2.9.1",
"rustix 0.38.44",
"rustix 1.0.8",
"wayland-backend",
"wayland-scanner",
]
@@ -1949,20 +1919,20 @@ dependencies = [
[[package]]
name = "wayland-cursor"
version = "0.31.10"
version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182"
checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29"
dependencies = [
"rustix 0.38.44",
"rustix 1.0.8",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.32.8"
version = "0.32.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a"
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
dependencies = [
"bitflags 2.9.1",
"wayland-backend",
@@ -1972,9 +1942,9 @@ dependencies = [
[[package]]
name = "wayland-protocols-plasma"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175"
checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032"
dependencies = [
"bitflags 2.9.1",
"wayland-backend",
@@ -1985,9 +1955,9 @@ dependencies = [
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf"
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
dependencies = [
"bitflags 2.9.1",
"wayland-backend",
@@ -1998,9 +1968,9 @@ dependencies = [
[[package]]
name = "wayland-scanner"
version = "0.31.6"
version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484"
checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
dependencies = [
"proc-macro2",
"quick-xml",
@@ -2009,9 +1979,9 @@ dependencies = [
[[package]]
name = "wayland-sys"
version = "0.31.6"
version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615"
checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
dependencies = [
"dlib",
"log",
@@ -2041,12 +2011,13 @@ dependencies = [
[[package]]
name = "wgpu"
version = "25.0.2"
version = "26.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9"
checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798"
dependencies = [
"arrayvec",
"bitflags 2.9.1",
"cfg-if",
"cfg_aliases",
"document-features",
"hashbrown",
@@ -2069,9 +2040,9 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "25.0.2"
version = "26.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500"
checksum = "d5f62f1053bd28c2268f42916f31588f81f64796e2ff91b81293515017ca8bd9"
dependencies = [
"arrayvec",
"bit-set",
@@ -2100,36 +2071,36 @@ dependencies = [
[[package]]
name = "wgpu-core-deps-apple"
version = "25.0.0"
version = "26.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d"
checksum = "18ae5fbde6a4cbebae38358aa73fcd6e0f15c6144b67ef5dc91ded0db125dbdf"
dependencies = [
"wgpu-hal",
]
[[package]]
name = "wgpu-core-deps-emscripten"
version = "25.0.0"
version = "26.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445"
checksum = "d7670e390f416006f746b4600fdd9136455e3627f5bd763abf9a65daa216dd2d"
dependencies = [
"wgpu-hal",
]
[[package]]
name = "wgpu-core-deps-windows-linux-android"
version = "25.0.0"
version = "26.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46"
checksum = "720a5cb9d12b3d337c15ff0e24d3e97ed11490ff3f7506e7f3d98c68fa5d6f14"
dependencies = [
"wgpu-hal",
]
[[package]]
name = "wgpu-hal"
version = "25.0.2"
version = "26.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17"
checksum = "7df2c64ac282a91ad7662c90bc4a77d4a2135bc0b2a2da5a4d4e267afc034b9e"
dependencies = [
"android_system_properties",
"arrayvec",
@@ -2140,7 +2111,7 @@ dependencies = [
"bytemuck",
"cfg-if",
"cfg_aliases",
"core-graphics-types",
"core-graphics-types 0.2.0",
"glow",
"glutin_wgl_sys",
"gpu-alloc",
@@ -2154,11 +2125,12 @@ dependencies = [
"log",
"metal",
"naga",
"ndk-sys 0.5.0+25.2.9519653",
"ndk-sys",
"objc",
"ordered-float",
"parking_lot",
"portable-atomic",
"portable-atomic-util",
"profiling",
"range-alloc",
"raw-window-handle",
@@ -2174,9 +2146,9 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "25.0.0"
version = "26.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc"
checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2"
dependencies = [
"bitflags 2.9.1",
"bytemuck",
@@ -2240,6 +2212,12 @@ dependencies = [
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-result"
version = "0.2.0"
@@ -2292,7 +2270,7 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.2",
"windows-targets 0.53.3",
]
[[package]]
@@ -2343,10 +2321,11 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.53.2"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
@@ -2539,9 +2518,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winit"
version = "0.30.11"
version = "0.30.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4"
checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732"
dependencies = [
"ahash",
"android-activity",
@@ -2552,7 +2531,7 @@ dependencies = [
"calloop",
"cfg_aliases",
"concurrent-queue",
"core-foundation",
"core-foundation 0.9.4",
"core-graphics",
"cursor-icon",
"dpi",
@@ -2591,9 +2570,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.7.11"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [
"memchr",
]
@@ -2641,9 +2620,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
[[package]]
name = "xcursor"
version = "0.3.9"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "635887f4315a33cb714eb059bdbd7c1c92bfa71bc5b9d5115460502f788c2ab5"
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
[[package]]
name = "xkbcommon-dl"
@@ -2666,9 +2645,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
[[package]]
name = "xml-rs"
version = "0.8.26"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda"
checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7"
[[package]]
name = "zerocopy"

View File

@@ -6,11 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytemuck = "1.23.1"
glam = { version = "0.30.4", features = ["bytemuck"] }
pollster = "0.4.0"
wgpu = "25.0.2"
winit = "0.30.11"
rhai = { git = "https://github.com/rhaiscript/rhai", features = ["f32_float"] }
notify = "8.0.0"
wgpu = "26.0.1"
bytemuck = "1.23.1"

66
src/layout/mod.rs Normal file
View File

@@ -0,0 +1,66 @@
mod node;
pub use node::*;
use crate::primitive::{Color, Painter};
pub type UIColor = Color<u8>;
pub trait UINode: 'static {
fn draw(&self, painter: &mut Painter);
}
pub struct UI {
base: Box<dyn UINode>,
}
impl UI {
pub fn to_primitives(&self) -> Painter {
let mut painter = Painter::default();
self.base.draw(&mut painter);
painter
}
}
impl<N: UINode> From<N> for UI {
fn from(node: N) -> Self {
Self {
base: Box::new(node),
}
}
}
impl UINode for Box<dyn UINode> {
fn draw(&self, painter: &mut Painter) {
self.as_ref().draw(painter);
}
}
pub trait NodeArray<const LEN: usize> {
fn to_arr(self) -> [Box<dyn UINode>; LEN];
}
// I hate this language it's so bad why do I even use it
macro_rules! impl_node_arr {
($n:expr;$($T:tt)+) => {
impl<$($T: UINode,)*> NodeArray<$n> for ($($T,)*) {
fn to_arr(self) -> [Box<dyn UINode>; $n] {
#[allow(non_snake_case)]
let ($($T,)*) = self;
[$(Box::new($T),)*]
}
}
};
}
impl_node_arr!(1;A);
impl_node_arr!(2;A B);
impl_node_arr!(3;A B C);
impl_node_arr!(4;A B C D);
impl_node_arr!(5;A B C D E);
impl_node_arr!(6;A B C D E F);
impl_node_arr!(7;A B C D E F G);
impl_node_arr!(8;A B C D E F G H);
impl_node_arr!(9;A B C D E F G H I);
impl_node_arr!(10;A B C D E F G H I J);
impl_node_arr!(11;A B C D E F G H I J K);
impl_node_arr!(12;A B C D E F G H I J K L);

164
src/layout/node.rs Normal file
View File

@@ -0,0 +1,164 @@
use std::ops::Range;
use crate::{
primitive::{Axis, Painter, RoundedRectData, UIRegion},
NodeArray, UIColor, UINode,
};
#[derive(Clone, Copy)]
pub struct RoundedRect {
pub color: UIColor,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl RoundedRect {
pub fn color(mut self, color: UIColor) -> Self {
self.color = color;
self
}
}
impl UINode for RoundedRect {
fn draw(&self, painter: &mut Painter) {
painter.write(RoundedRectData {
color: self.color,
radius: self.radius,
thickness: self.thickness,
inner_radius: self.inner_radius,
});
}
}
pub struct Span {
pub elements: Vec<(Range<f32>, Box<dyn UINode>)>,
pub axis: Axis,
}
impl UINode for Span {
fn draw(&self, painter: &mut Painter) {
for (span, child) in &self.elements {
let mut sub_region = UIRegion::full();
let view = sub_region.axis_mut(self.axis);
*view.top_left.anchor = span.start;
*view.bot_right.anchor = span.end;
painter.draw_within(child, sub_region);
}
}
}
impl Span {
pub fn proportioned<const LEN: usize>(
axis: Axis,
ratios: [impl UINum; LEN],
elements: impl NodeArray<LEN>,
) -> Self {
let ratios = ratios.map(|r| r.to_f32());
let total: f32 = ratios.iter().sum();
let mut start = 0.0;
Self {
elements: elements
.to_arr()
.into_iter()
.zip(ratios)
.map(|(e, r)| {
let end = start + r / total;
let res = (start..end, e);
start = end;
res
})
.collect(),
axis,
}
}
}
pub struct Regioned<N: UINode> {
region: UIRegion,
inner: N,
}
impl<N: UINode> UINode for Regioned<N> {
fn draw(&self, painter: &mut Painter) {
painter.region.select(&self.region);
self.inner.draw(painter);
}
}
pub struct Padding {
left: f32,
right: f32,
top: f32,
bottom: f32,
}
impl Padding {
pub fn uniform(amt: f32) -> Self {
Self {
left: amt,
right: amt,
top: amt,
bottom: amt,
}
}
pub fn region(&self) -> UIRegion {
let mut region = UIRegion::full();
region.top_left.offset.x += self.left;
region.top_left.offset.y += self.top;
region.bot_right.offset.x -= self.right;
region.bot_right.offset.y -= self.bottom;
region
}
}
impl<T: UINum> From<T> for Padding {
fn from(amt: T) -> Self {
Self::uniform(amt.to_f32())
}
}
pub trait NodeUtil: Sized + UINode {
fn pad(self, padding: impl Into<Padding>) -> Regioned<Self>;
}
impl<T: UINode> NodeUtil for T {
fn pad(self, padding: impl Into<Padding>) -> Regioned<Self> {
Regioned {
region: padding.into().region(),
inner: self,
}
}
}
pub trait NodeArrayUtil<const LEN: usize> {
fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span;
}
impl<T: NodeArray<LEN>, const LEN: usize> NodeArrayUtil<LEN> for T {
fn proportioned(self, axis: Axis, ratios: [impl UINum; LEN]) -> Span {
Span::proportioned(axis, ratios, self)
}
}
pub trait UINum {
fn to_f32(self) -> f32;
}
impl UINum for f32 {
fn to_f32(self) -> f32 {
self
}
}
impl UINum for u32 {
fn to_f32(self) -> f32 {
self as f32
}
}
impl UINum for i32 {
fn to_f32(self) -> f32 {
self as f32
}
}

10
src/lib.rs Normal file
View File

@@ -0,0 +1,10 @@
#![feature(macro_metavar_expr_concat)]
#![feature(const_ops)]
#![feature(const_trait_impl)]
#![feature(const_from)]
mod layout;
mod render;
pub use layout::*;
pub use render::*;

View File

@@ -1,36 +1,5 @@
use std::sync::Arc;
use app::App;
use render::Renderer;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
mod app;
mod render;
mod testing;
fn main() {
App::run();
}
pub struct Client {
window: Arc<Window>,
renderer: Renderer,
}
impl Client {
pub fn new(window: Arc<Window>) -> Self {
let renderer = Renderer::new(window.clone());
Self {
window,
renderer,
}
}
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => self.renderer.draw(),
WindowEvent::Resized(size) => self.renderer.resize(&size),
_ => (),
}
}
testing::main();
}

34
src/render/data.rs Normal file
View File

@@ -0,0 +1,34 @@
use crate::primitive::UIRegion;
use wgpu::VertexAttribute;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PrimitiveInstance {
pub region: UIRegion,
pub ptr: u32,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct WindowUniform {
pub width: f32,
pub height: f32,
}
impl PrimitiveInstance {
const ATTRIBS: [VertexAttribute; 5] = wgpu::vertex_attr_array![
0 => Float32x2,
1 => Float32x2,
2 => Float32x2,
3 => Float32x2,
4 => Uint32,
];
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &Self::ATTRIBS,
}
}
}

View File

@@ -1,199 +1,184 @@
use std::sync::{
mpsc::{channel, Receiver},
Arc,
use crate::{
render::{data::PrimitiveInstance, util::ArrBuf},
UI,
};
use notify::{
event::{CreateKind, ModifyKind},
EventKind, RecommendedWatcher, Watcher,
use data::WindowUniform;
use wgpu::{
util::{BufferInitDescriptor, DeviceExt},
*,
};
use pollster::FutureExt;
use primitive::{RoundedRect, UIPos};
use shape::ShapePipeline;
use wgpu::util::StagingBelt;
use winit::{dpi::PhysicalSize, window::Window};
use winit::dpi::PhysicalSize;
mod data;
pub mod primitive;
mod shape;
mod util;
pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK;
const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
pub struct Renderer {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
adapter: wgpu::Adapter,
encoder: wgpu::CommandEncoder,
staging_belt: StagingBelt,
shape_pipeline: ShapePipeline,
recv: Receiver<Vec<RoundedRect>>,
pub struct UIRenderNode {
bind_group_layout: BindGroupLayout,
bind_group: BindGroup,
pipeline: RenderPipeline,
window_buffer: Buffer,
instance: ArrBuf<PrimitiveInstance>,
data: ArrBuf<u32>,
}
impl Renderer {
pub fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();
impl UIRenderNode {
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.bind_group, &[]);
if self.instance.len() != 0 {
pass.set_vertex_buffer(0, self.instance.buffer.slice(..));
pass.draw(0..4, 0..self.instance.len() as u32);
}
}
let (send, recv) = channel();
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &UI) {
let primitives = ui.to_primitives();
self.instance.update(device, queue, &primitives.instances);
self.data.update(device, queue, &primitives.data);
self.bind_group = Self::bind_group(
device,
&self.bind_group_layout,
&self.window_buffer,
&self.data.buffer,
)
}
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("data/test.rhai");
let w2 = window.clone();
std::thread::spawn(move || {
let (tx, rx) = channel();
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &Queue) {
let slice = &[WindowUniform {
width: size.width as f32,
height: size.height as f32,
}];
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
}
let mut watcher = RecommendedWatcher::new(tx, Default::default()).unwrap();
watcher
.watch("data".as_ref(), notify::RecursiveMode::Recursive)
.unwrap();
for res in rx {
let Ok(ev) = res else {
continue;
};
if !ev.paths.contains(&path) {
continue;
}
if !matches!(
ev.kind,
EventKind::Create(CreateKind::File) | EventKind::Modify(ModifyKind::Data(_))
) {
continue;
}
println!("reloaded {ev:?}");
let mut engine = rhai::Engine::new();
engine.build_type::<RoundedRect>();
engine.register_type::<UIPos>();
engine.register_fn("anchor_offset", UIPos::anchor_offset);
engine.register_fn("rect", RoundedRect::default);
let Ok(code) = std::fs::read_to_string(&path) else {
continue;
};
let rects = engine
.eval::<rhai::Array>(&code)
.map_err(|e| println!("{e:?}"))
.unwrap_or_default()
.into_iter()
.filter_map(|d| match rhai::Dynamic::try_cast(d.clone()) {
Some(v) => Some(v),
None => {
println!("{d:?} is not a RoundedRect");
None
}
})
.collect::<Vec<RoundedRect>>();
send.send(rects).unwrap();
w2.request_redraw();
}
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
let shader = device.create_shader_module(ShaderModuleDescriptor {
label: Some("UI Shape Shader"),
source: ShaderSource::Wgsl(SHAPE_SHADER.into()),
});
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
let window_uniform = WindowUniform::default();
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
label: Some("Camera Buffer"),
contents: bytemuck::cast_slice(&[window_uniform]),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
let surface = instance
.create_surface(window.clone())
.expect("Could not create window surface!");
let instance = ArrBuf::new(
device,
BufferUsages::VERTEX | BufferUsages::COPY_DST,
"instance",
);
let data = ArrBuf::new(
device,
BufferUsages::STORAGE | BufferUsages::COPY_DST,
"data",
);
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.block_on()
.expect("Could not get adapter!");
let bind_group_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
BindGroupLayoutEntry {
binding: 0,
visibility: ShaderStages::VERTEX,
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
BindGroupLayoutEntry {
binding: 1,
visibility: ShaderStages::FRAGMENT,
ty: BindingType::Buffer {
ty: BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
label: Some("camera_bind_group_layout"),
});
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
..Default::default()
})
.block_on()
.expect("Could not get device!");
let bind_group = Self::bind_group(device, &bind_group_layout, &window_buffer, &data.buffer);
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2,
view_formats: vec![],
};
surface.configure(&device, &config);
let staging_belt = StagingBelt::new(4096 * 4);
let encoder = Self::create_encoder(&device);
let shape_pipeline = ShapePipeline::new(&device, &config);
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("UI Shape Pipeline"),
layout: Some(&pipeline_layout),
vertex: VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[PrimitiveInstance::desc()],
compilation_options: Default::default(),
},
fragment: Some(FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(ColorTargetState {
format: config.format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: PrimitiveState {
topology: PrimitiveTopology::TriangleStrip,
strip_index_format: None,
front_face: FrontFace::Cw,
cull_mode: Some(Face::Back),
polygon_mode: PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
Self {
surface,
device,
queue,
config,
adapter,
encoder,
staging_belt,
shape_pipeline,
recv,
bind_group_layout,
bind_group,
pipeline,
window_buffer,
instance,
data,
}
}
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
pub fn bind_group(
device: &Device,
layout: &BindGroupLayout,
window_buffer: &Buffer,
data: &Buffer,
) -> BindGroup {
device.create_bind_group(&BindGroupDescriptor {
layout,
entries: &[
BindGroupEntry {
binding: 0,
resource: window_buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: data.as_entire_binding(),
},
],
label: Some("ui_bind_group"),
})
}
pub fn draw(&mut self) {
if let Some(rects) = self.recv.try_iter().last() {
self.shape_pipeline
.update(&self.device, &self.queue, &rects);
}
let output = self.surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
{
let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(CLEAR_COLOR),
store: wgpu::StoreOp::Store,
},
})],
..Default::default()
});
self.shape_pipeline.draw(render_pass);
}
self.queue.submit(std::iter::once(encoder.finish()));
self.staging_belt.finish();
output.present();
self.staging_belt.recall();
}
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.device, &self.config);
self.shape_pipeline.resize(size, &self.queue);
}
}

View File

@@ -1,73 +0,0 @@
use glam::Vec2;
use rhai::{CustomType, TypeBuilder};
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, CustomType)]
pub struct RoundedRect {
pub top_left: UIPos,
pub bottom_right: UIPos,
pub colors: [[f32; 4]; 4],
pub radius: f32,
pub inner_radius: f32,
pub thickness: f32,
}
impl Default for RoundedRect {
fn default() -> Self {
Self {
top_left: Default::default(),
bottom_right: Default::default(),
colors: [
[1.0, 0.0, 0.0, 1.0],
[0.0, 1.0, 0.0, 1.0],
[0.0, 0.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 1.0],
],
radius: Default::default(),
inner_radius: Default::default(),
thickness: Default::default(),
}
}
}
#[derive(PartialEq, Clone)]
pub struct Text {
pub content: String,
pub align: Align,
pub pos: Vec2,
pub bounds: (f32, f32),
}
impl Text {
pub fn empty() -> Self {
Self {
content: String::new(),
align: Align::Left,
pos: Vec2::default(),
bounds: (0.0, 0.0),
}
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct UIPos {
pub anchor: Vec2,
pub offset: Vec2,
}
impl UIPos {
pub fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self {
Self {
anchor: Vec2::new(anchor_x, anchor_y),
offset: Vec2::new(offset_x, offset_y),
}
}
}
#[derive(PartialEq, Clone)]
pub enum Align {
Left,
Right,
Center,
}

View File

@@ -0,0 +1,47 @@
#![allow(clippy::multiple_bound_locations)]
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Zeroable)]
pub struct Color<T: ColorNum> {
r: T,
g: T,
b: T,
a: T,
}
impl<T: ColorNum> Color<T> {
pub const BLACK: Self = Self::rgb(T::MIN, T::MIN, T::MIN);
pub const WHITE: Self = Self::rgb(T::MAX, T::MAX, T::MAX);
pub const RED: Self = Self::rgb(T::MAX, T::MIN, T::MIN);
pub const ORANGE: Self = Self::rgb(T::MAX, T::MID, T::MIN);
pub const YELLOW: Self = Self::rgb(T::MAX, T::MAX, T::MIN);
pub const LIME: Self = Self::rgb(T::MID, T::MAX, T::MIN);
pub const GREEN: Self = Self::rgb(T::MIN, T::MAX, T::MIN);
pub const CYAN: Self = Self::rgb(T::MIN, T::MAX, T::MAX);
pub const BLUE: Self = Self::rgb(T::MIN, T::MIN, T::MAX);
pub const MAGENTA: Self = Self::rgb(T::MAX, T::MIN, T::MAX);
}
impl<T: ColorNum> Color<T> {
pub const fn new(r: T, g: T, b: T, a: T) -> Self {
Self { r, g, b, a }
}
pub const fn rgb(r: T, g: T, b: T) -> Self {
Self { r, g, b, a: T::MAX }
}
}
pub trait ColorNum {
const MIN: Self;
const MID: Self;
const MAX: Self;
}
impl ColorNum for u8 {
const MIN: Self = u8::MIN;
const MID: Self = u8::MAX / 2;
const MAX: Self = u8::MAX;
}
unsafe impl bytemuck::Pod for Color<u8> {}

View File

@@ -0,0 +1,14 @@
use crate::primitive::{Color, PrimitiveData};
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RoundedRectData {
pub color: Color<u8>,
pub radius: f32,
pub thickness: f32,
pub inner_radius: f32,
}
impl PrimitiveData for RoundedRectData {
const DISCRIM: u32 = 0;
}

View File

@@ -0,0 +1,97 @@
use crate::primitive::{point::point, Point};
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct UIPos {
pub anchor: Point,
pub offset: Point,
}
impl UIPos {
pub const fn anchor_offset(anchor_x: f32, anchor_y: f32, offset_x: f32, offset_y: f32) -> Self {
Self {
anchor: point(anchor_x, anchor_y),
offset: point(offset_x, offset_y),
}
}
pub const fn top_left() -> Self {
Self::anchor_offset(0.0, 0.0, 0.0, 0.0)
}
pub const fn bottom_right() -> Self {
Self::anchor_offset(1.0, 1.0, 0.0, 0.0)
}
pub const fn within(&self, region: &UIRegion) -> UIPos {
let range = region.bot_right.anchor - region.top_left.anchor;
let region_offset = region
.top_left
.offset
.lerp(region.bot_right.offset, self.anchor);
UIPos {
anchor: region.top_left.anchor + self.anchor * range,
offset: self.offset + region_offset,
}
}
pub fn axis_mut(&mut self, axis: Axis) -> UIPosAxisView<'_> {
match axis {
Axis::X => UIPosAxisView {
anchor: &mut self.anchor.x,
offset: &mut self.offset.x,
},
Axis::Y => UIPosAxisView {
anchor: &mut self.anchor.y,
offset: &mut self.offset.y,
},
}
}
}
pub struct UIPosAxisView<'a> {
pub anchor: &'a mut f32,
pub offset: &'a mut f32,
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UIRegion {
pub top_left: UIPos,
pub bot_right: UIPos,
}
impl UIRegion {
pub const fn full() -> Self {
Self {
top_left: UIPos::top_left(),
bot_right: UIPos::bottom_right(),
}
}
pub fn within(&self, parent: &Self) -> Self {
Self {
top_left: self.top_left.within(parent),
bot_right: self.bot_right.within(parent),
}
}
pub fn select(&mut self, inner: &Self) {
*self = inner.within(self);
}
pub fn axis_mut(&mut self, axis: Axis) -> UIRegionAxisView<'_> {
UIRegionAxisView {
top_left: self.top_left.axis_mut(axis),
bot_right: self.bot_right.axis_mut(axis),
}
}
}
pub struct UIRegionAxisView<'a> {
pub top_left: UIPosAxisView<'a>,
pub bot_right: UIPosAxisView<'a>,
}
#[derive(Copy, Clone)]
pub enum Axis {
X,
Y,
}

View File

@@ -0,0 +1,50 @@
mod color;
mod def;
mod format;
mod point;
pub use color::*;
pub use def::*;
pub use format::*;
pub use point::*;
use crate::{render::data::PrimitiveInstance, UINode};
use bytemuck::Pod;
pub struct Painter {
pub region: UIRegion,
pub instances: Vec<PrimitiveInstance>,
pub data: Vec<u32>,
}
impl Default for Painter {
fn default() -> Self {
Self {
region: UIRegion::full(),
instances: Default::default(),
data: Default::default(),
}
}
}
/// NOTE: Self must have at least u32 alignment
pub trait PrimitiveData: Pod {
const DISCRIM: u32;
}
impl Painter {
pub fn write<D: PrimitiveData>(&mut self, data: D) {
let ptr = self.data.len() as u32;
let region = self.region;
self.instances.push(PrimitiveInstance { region, ptr });
self.data.push(D::DISCRIM);
self.data
.extend_from_slice(bytemuck::cast_slice::<_, u32>(&[data]));
}
pub fn draw_within(&mut self, node: &impl UINode, region: UIRegion) {
let old = self.region;
self.region.select(&region);
node.draw(self);
self.region = old;
}
}

View File

@@ -0,0 +1,84 @@
use std::ops::*;
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Point {
pub x: f32,
pub y: f32,
}
pub const fn point(x: f32, y: f32) -> Point {
Point::new(x, y)
}
impl Point {
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub const fn lerp(self, to: Self, amt: impl const Into<Self>) -> Self {
let amt = amt.into();
Self {
x: lerp(self.x, to.x, amt.x),
y: lerp(self.y, to.y, amt.y),
}
}
}
const fn lerp(x: f32, y: f32, amt: f32) -> f32 {
(1.0 - amt) * x + y * amt
}
impl const From<f32> for Point {
fn from(v: f32) -> Self {
Self { x: v, y: v }
}
}
macro_rules! impl_op_inner {
($op:ident $fn:ident $opa:ident $fna:ident) => {
impl const $op for Point {
type Output = Self;
fn $fn(self, rhs: Self) -> Self::Output {
Self {
x: self.x.$fn(rhs.x),
y: self.y.$fn(rhs.y),
}
}
}
impl $opa for Point {
fn $fna(&mut self, rhs: Self) {
self.x.$fna(rhs.x);
self.y.$fna(rhs.y);
}
}
impl const $op<f32> for Point {
type Output = Self;
fn $fn(self, rhs: f32) -> Self::Output {
Self {
x: self.x.$fn(rhs),
y: self.y.$fn(rhs),
}
}
}
impl $opa<f32> for Point {
fn $fna(&mut self, rhs: f32) {
self.x.$fna(rhs);
self.y.$fna(rhs);
}
}
};
}
macro_rules! impl_op {
($op:ident $fn:ident) => {
impl_op_inner!($op $fn ${concat($op,Assign)} ${concat($fn,_assign)});
};
}
impl_op!(Add add);
impl_op!(Sub sub);
impl_op!(Mul mul);
impl_op!(Div div);

110
src/render/shader.wgsl Normal file
View File

@@ -0,0 +1,110 @@
@group(0) @binding(0)
var<uniform> window: WindowUniform;
@group(0) @binding(1)
var<storage> data: array<u32>;
struct WindowUniform {
dim: vec2<f32>,
};
struct InstanceInput {
@location(0) top_left_anchor: vec2<f32>,
@location(1) top_left_offset: vec2<f32>,
@location(2) bottom_right_anchor: vec2<f32>,
@location(3) bottom_right_offset: vec2<f32>,
@location(4) pointer: u32,
}
struct RoundedRect {
color: u32,
radius: f32,
thickness: f32,
inner_radius: f32,
}
struct VertexOutput {
@location(0) pointer: u32,
@location(1) top_left: vec2<f32>,
@location(2) bot_right: vec2<f32>,
@builtin(position) clip_position: vec4<f32>,
};
struct Region {
pos: 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 = in.top_left_anchor * window.dim + in.top_left_offset;
let bot_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset;
let size = bot_right - top_left;
var pos = top_left + vec2<f32>(
f32(vi % 2u),
f32(vi / 2u)
) * size;
pos = pos / window.dim * 2.0 - 1.0;
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
out.pointer = in.pointer;
out.top_left = top_left;
out.bot_right = bot_right;
return out;
}
@fragment
fn fs_main(
in: VertexOutput
) -> @location(0) vec4<f32> {
let pos = in.clip_position.xy;
let ty = data[in.pointer];
let dp = in.pointer + 1u;
let region = Region(pos, in.top_left, in.bot_right);
switch ty {
case 0u: {
return draw_rounded_rect(region, RoundedRect(
data[dp + 0u],
bitcast<f32>(data[dp + 1u]),
bitcast<f32>(data[dp + 2u]),
bitcast<f32>(data[dp + 3u]),
));
}
default: {}
}
return vec4(1.0, 0.0, 1.0, 1.0);
}
fn draw_rounded_rect(region: Region, rect: RoundedRect) -> 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<f32>(0.0, 0.0))) - radius;
}

View File

@@ -1,74 +0,0 @@
use wgpu::{RenderPass, VertexAttribute};
use crate::render::primitive::RoundedRect;
pub struct RoundedRectBuffer {
buffer: wgpu::Buffer,
len: usize,
}
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct WindowUniform {
pub width: f32,
pub height: f32,
}
impl RoundedRectBuffer {
pub fn new(device: &wgpu::Device) -> Self {
Self {
buffer: Self::init_buf(device, 0),
len: 0,
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
rects: &[RoundedRect],
) {
if self.len != rects.len() {
self.len = rects.len();
self.buffer = Self::init_buf(device, std::mem::size_of_val(rects));
}
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(rects));
}
fn init_buf(device: &wgpu::Device, size: usize) -> wgpu::Buffer {
device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Buffer"),
size: size as u64,
mapped_at_creation: false,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
})
}
pub fn len(&self) -> usize {
self.len
}
pub fn set_in<'a>(&'a self, pass: &mut RenderPass<'a>) {
pass.set_vertex_buffer(0, self.buffer.slice(..));
}
}
impl RoundedRect {
const ATTRIBS: [VertexAttribute; 11] = wgpu::vertex_attr_array![
0 => Float32x2,
1 => Float32x2,
2 => Float32x2,
3 => Float32x2,
4 => Float32x4,
5 => Float32x4,
6 => Float32x4,
7 => Float32x4,
8 => Float32,
9 => Float32,
10 => Float32,
];
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<RoundedRect>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &Self::ATTRIBS,
}
}
}

View File

@@ -1,106 +0,0 @@
use wgpu::{
util::{BufferInitDescriptor, DeviceExt},
BufferUsages,
};
use crate::render::primitive::RoundedRect;
use super::{
data::{RoundedRectBuffer, WindowUniform},
ShapeBuffers, ShapePipeline, SHAPE_SHADER,
};
impl ShapePipeline {
pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("UI Shape Shader"),
source: wgpu::ShaderSource::Wgsl(SHAPE_SHADER.into()),
});
let window_uniform = WindowUniform::default();
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
label: Some("Camera Buffer"),
contents: bytemuck::cast_slice(&[window_uniform]),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
let instance_buffer = RoundedRectBuffer::new(device);
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
label: Some("camera_bind_group_layout"),
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: window_buffer.as_entire_binding(),
}],
label: Some("camera_bind_group"),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("UI Shape Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[RoundedRect::desc()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: None,
front_face: wgpu::FrontFace::Cw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let buffers = ShapeBuffers {
window: window_buffer,
instance: instance_buffer,
};
Self {
bind_group,
pipeline,
buffers,
}
}
}

View File

@@ -1,44 +0,0 @@
use crate::render::primitive::RoundedRect;
use data::{RoundedRectBuffer, WindowUniform};
use wgpu::{BindGroup, Buffer, RenderPass, RenderPipeline};
use winit::dpi::PhysicalSize;
mod data;
mod layout;
pub const SHAPE_SHADER: &str = include_str!("./shader.wgsl");
pub struct ShapeBuffers {
pub window: Buffer,
pub instance: RoundedRectBuffer,
}
pub struct ShapePipeline {
pub bind_group: BindGroup,
pub pipeline: RenderPipeline,
pub buffers: ShapeBuffers,
}
impl ShapePipeline {
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.bind_group, &[]);
if self.buffers.instance.len() != 0 {
self.buffers.instance.set_in(pass);
pass.draw(0..4, 0..self.buffers.instance.len() as u32);
}
}
pub fn update(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, rects: &[RoundedRect]) {
self.buffers.instance.update(device, queue, rects);
}
pub fn resize(&mut self, size: &PhysicalSize<u32>, queue: &wgpu::Queue) {
let slice = &[WindowUniform {
width: size.width as f32,
height: size.height as f32,
}];
queue.write_buffer(&self.buffers.window, 0, bytemuck::cast_slice(slice));
}
}

View File

@@ -1,97 +0,0 @@
// vertex shader
struct VertexOutput {
@location(0) color: vec4<f32>,
@location(1) center: vec2<f32>,
@location(2) corner: vec2<f32>,
@location(3) radius: f32,
@location(4) inner_radius: f32,
@location(5) thickness: f32,
@builtin(position) clip_position: vec4<f32>,
};
struct WindowUniform {
dim: vec2<f32>,
};
struct InstanceInput {
@location(0) top_left_anchor: vec2<f32>,
@location(1) top_left_offset: vec2<f32>,
@location(2) bottom_right_anchor: vec2<f32>,
@location(3) bottom_right_offset: vec2<f32>,
@location(4) top_right_color: vec4<f32>,
@location(5) top_left_color: vec4<f32>,
@location(6) bottom_right_color: vec4<f32>,
@location(7) bottom_left_color: vec4<f32>,
@location(8) radius: f32,
@location(9) inner_radius: f32,
@location(10) thickness: f32,
}
@group(0) @binding(0)
var<uniform> window: WindowUniform;
@vertex
fn vs_main(
@builtin(vertex_index) vi: u32,
in: InstanceInput,
) -> VertexOutput {
var out: VertexOutput;
let top_left = in.top_left_anchor * window.dim + in.top_left_offset;
let bottom_right = in.bottom_right_anchor * window.dim + in.bottom_right_offset;
let size = bottom_right - top_left;
var pos = top_left + vec2<f32>(
f32(vi % 2u),
f32(vi / 2u)
) * size;
pos = pos / window.dim * 2.0 - 1.0;
out.clip_position = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
if vi == 0u {
out.color = in.top_left_color;
} else if vi == 1u {
out.color = in.top_right_color;
} else if vi == 2u {
out.color = in.bottom_left_color;
} else if vi == 3u {
out.color = in.bottom_right_color;
}
out.corner = size / 2.0;
out.center = top_left + out.corner;
out.radius = in.radius;
out.inner_radius = in.inner_radius;
out.thickness = in.thickness;
return out;
}
@fragment
fn fs_main(
in: VertexOutput
) -> @location(0) vec4<f32> {
var color = in.color;
let edge = 0.5;
let dist = distance_from_rect(in.clip_position.xy, in.center, in.corner, in.radius);
color.a *= 1.0 - smoothstep(-min(edge, in.radius), edge, dist);
if in.thickness > 0.0 {
let dist2 = distance_from_rect(in.clip_position.xy, in.center, in.corner - in.thickness, in.inner_radius);
color.a *= smoothstep(-min(edge, in.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<f32>(0.0, 0.0))) - radius;
}

View File

@@ -1 +0,0 @@
pub mod pipeline;

View File

@@ -1,127 +0,0 @@
use glyphon::{
Attrs, Buffer, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea,
TextAtlas, TextBounds, TextRenderer,
};
use wgpu::{MultisampleState, RenderPass};
use crate::{
client::ui::element::Align,
render::{primitive::TextPrimitive, surface::RenderSurface},
};
pub struct TextPipeline {
pub renderer: glyphon::TextRenderer,
pub font_system: glyphon::FontSystem,
pub atlas: glyphon::TextAtlas,
pub cache: glyphon::SwashCache,
pub text_buffers: Vec<glyphon::Buffer>,
pub old_text: Vec<TextPrimitive>,
}
impl TextPipeline {
pub fn new(surface: &RenderSurface) -> Self {
let RenderSurface {
device,
config,
queue,
..
} = surface;
let font_system = FontSystem::new();
let cache = SwashCache::new();
let mut atlas = TextAtlas::new(&device, &queue, config.format);
let renderer = TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None);
Self {
font_system,
atlas,
cache,
renderer,
text_buffers: Vec::new(),
old_text: Vec::new(),
}
}
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
self.renderer.render(&self.atlas, pass).unwrap();
}
pub fn update(&mut self, surface: &RenderSurface, text: &[TextPrimitive]) {
let buffers = &mut self.text_buffers;
if buffers.len() < text.len() {
self.old_text.resize(text.len(), TextPrimitive::empty());
buffers.resize_with(text.len(), || {
Buffer::new(&mut self.font_system, Metrics::new(20.0, 25.0))
})
}
for ((buffer, text), old) in buffers.iter_mut().zip(text).zip(&mut self.old_text) {
if text != old {
*old = text.clone();
buffer.set_size(&mut self.font_system, f32::MAX, f32::MAX);
buffer.set_text(
&mut self.font_system,
&text.content,
Attrs::new().family(Family::SansSerif),
Shaping::Basic,
);
}
}
let color = Color::rgb(255, 255, 255);
let areas = buffers.iter().zip(text).map(|(buffer, text)| {
let width = measure(&buffer).0;
let mut left = text.pos.x
- match text.align {
Align::Left => 0.0,
Align::Center => width / 2.0,
Align::Right => width,
};
let x = text.pos.x;
let w = text.bounds.0;
let x_bounds = match text.align {
Align::Left => (x, x + w),
Align::Center => (x - w / 2.0, x + w / 2.0),
Align::Right => (x - w, x),
};
if left < x_bounds.0 {
left = x_bounds.0;
}
TextArea {
buffer: &buffer,
left,
top: text.pos.y,
scale: 1.0,
bounds: TextBounds {
left: x_bounds.0 as i32,
top: text.pos.y as i32,
right: x_bounds.1 as i32,
bottom: (text.pos.y + text.bounds.1) as i32,
},
default_color: color,
}
});
self.renderer
.prepare(
&surface.device,
&surface.queue,
&mut self.font_system,
&mut self.atlas,
Resolution {
width: surface.config.width,
height: surface.config.height,
},
areas,
&mut self.cache,
)
.unwrap();
}
}
fn measure(buffer: &glyphon::Buffer) -> (f32, f32) {
let (width, total_lines) = buffer
.layout_runs()
.fold((0.0, 0usize), |(width, total_lines), run| {
(run.line_w.max(width), total_lines + 1)
});
(width, total_lines as f32 * buffer.metrics().line_height)
}

View File

@@ -1,129 +0,0 @@
use wgpu::{
util::DeviceExt,
BindGroup, BindGroupLayout, Device, Queue,
};
use crate::render::surface::RenderSurface;
use super::{
pipeline::{TexturePipeline, TEXTURE_SHADER},
vertex::{TextureVertex, TEXTURE_VERTICES}, texture::GameTexture,
};
impl TexturePipeline {
pub fn new(surface: &RenderSurface) -> Self {
let RenderSurface {
device,
config,
queue,
..
} = surface;
let (bind_group_layout, diffuse_bind_group) = Self::init_textures(device, queue);
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("UI Texture Shader"),
source: wgpu::ShaderSource::Wgsl(TEXTURE_SHADER.into()),
});
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Texture Vertex Buffer"),
contents: bytemuck::cast_slice(TEXTURE_VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("UI Texture Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("UI Texture Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[TextureVertex::desc()],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleStrip,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
Self {
pipeline,
vertex_buffer,
diffuse_bind_group,
}
}
fn init_textures(device: &Device, queue: &Queue) -> (BindGroupLayout, BindGroup) {
let diffuse_bytes = include_bytes!("./textures/happy-tree.png");
let diffuse_texture =
GameTexture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap();
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
// This should match the filterable field of the
// corresponding Texture entry above.
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
],
label: Some("diffuse_bind_group"),
});
(texture_bind_group_layout, diffuse_bind_group)
}
}

View File

@@ -1,22 +0,0 @@
mod init;
mod vertex;
mod texture;
use wgpu::{RenderPass, RenderPipeline};
pub const TEXTURE_SHADER: &str = include_str!("./shader.wgsl");
pub struct TexturePipeline {
pub pipeline: RenderPipeline,
pub vertex_buffer: wgpu::Buffer,
pub diffuse_bind_group: wgpu::BindGroup,
}
impl TexturePipeline {
pub fn draw<'a>(&'a self, pass: &mut RenderPass<'a>) {
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.diffuse_bind_group, &[]);
pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
pass.draw(0..4, 0..1);
}
}

View File

@@ -1,34 +0,0 @@
// Vertex shader
struct VertexInput {
@location(0) position: vec2<f32>,
@location(1) tex_coords: vec2<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
}
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
out.tex_coords = model.tex_coords;
out.clip_position = vec4<f32>(model.position, 0.0, 1.0);
return out;
}
// Fragment shader
@group(0) @binding(0)
var t_diffuse: texture_2d<f32>;
@group(0)@binding(1)
var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSample(t_diffuse, s_diffuse, in.tex_coords);
}

View File

@@ -1,78 +0,0 @@
use image::{GenericImageView, ImageResult};
pub struct GameTexture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl GameTexture {
pub fn from_bytes(
device: &wgpu::Device,
queue: &wgpu::Queue,
bytes: &[u8],
label: &str
) -> ImageResult<Self> {
let img = image::load_from_memory(bytes)?;
Self::from_image(device, queue, &img, Some(label))
}
pub fn from_image(
device: &wgpu::Device,
queue: &wgpu::Queue,
img: &image::DynamicImage,
label: Option<&str>
) -> ImageResult<Self> {
let rgba = img.to_rgba8();
let dimensions = img.dimensions();
let size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
depth_or_array_layers: 1,
};
let texture = device.create_texture(
&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
}
);
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * dimensions.0),
rows_per_image: Some(dimensions.1),
},
size,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(
&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
}
);
Ok(Self { texture, view, sampler })
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,35 +0,0 @@
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct TextureVertex {
position: [f32; 2],
tex_coords: [f32; 2],
}
impl TextureVertex {
const ATTRIBS: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
pub fn desc() -> wgpu::VertexBufferLayout<'static> {
use std::mem;
wgpu::VertexBufferLayout {
array_stride: mem::size_of::<TextureVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}
pub const TEXTURE_VERTICES: &[TextureVertex] = &[
TextureVertex { position: [0.0, 0.0], tex_coords: [0.0, 1.0], },
TextureVertex { position: [0.5, 0.0], tex_coords: [1.0, 1.0], },
TextureVertex { position: [0.0, 0.5], tex_coords: [0.0, 0.0], },
TextureVertex { position: [0.5, 0.5], tex_coords: [1.0, 0.0], },
];
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ShapeVertex {
position: [f32; 2],
tex_coords: [f32; 2],
}

48
src/render/util/mod.rs Normal file
View File

@@ -0,0 +1,48 @@
use std::marker::PhantomData;
use bytemuck::Pod;
use wgpu::*;
pub struct ArrBuf<T: Pod> {
label: &'static str,
usage: BufferUsages,
pub buffer: Buffer,
len: usize,
_pd: PhantomData<T>,
}
impl<T: Pod> ArrBuf<T> {
pub fn new(device: &Device, usage: BufferUsages, label: &'static str) -> Self {
Self {
label,
usage,
buffer: Self::init_buf(device, 0, usage, label),
len: 0,
_pd: PhantomData,
}
}
pub fn update(&mut self, device: &Device, queue: &Queue, data: &[T]) {
if self.len != data.len() {
self.len = data.len();
self.buffer =
Self::init_buf(device, std::mem::size_of_val(data), self.usage, self.label);
}
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(data));
}
fn init_buf(device: &Device, size: usize, usage: BufferUsages, label: &'static str) -> Buffer {
let mut size = size as u64;
if usage.contains(BufferUsages::STORAGE) {
size = size.max(1);
}
device.create_buffer(&BufferDescriptor {
label: Some(label),
size,
mapped_at_creation: false,
usage,
})
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.len
}
}

View File

@@ -30,7 +30,7 @@ impl ApplicationHandler for App {
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
self.client.as_mut().unwrap().event(event, event_loop);
}
}

58
src/testing/mod.rs Normal file
View File

@@ -0,0 +1,58 @@
use std::sync::Arc;
use app::App;
use gui::{primitive::Axis, NodeArrayUtil, NodeUtil, RoundedRect, UIColor};
use render::Renderer;
use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window};
mod app;
mod render;
pub fn main() {
App::run();
}
pub struct Client {
renderer: Renderer,
}
impl Client {
pub fn new(window: Arc<Window>) -> Self {
let mut renderer = Renderer::new(window);
let rect = RoundedRect {
color: UIColor::WHITE,
radius: 10.0,
thickness: 0.0,
inner_radius: 0.0,
};
let ui = (
(
rect.color(UIColor::BLUE),
(
rect.color(UIColor::RED),
(rect.color(UIColor::ORANGE), rect.color(UIColor::LIME))
.proportioned(Axis::Y, [1, 1]),
rect.color(UIColor::YELLOW),
)
.proportioned(Axis::X, [2, 2, 1])
.pad(10),
)
.proportioned(Axis::X, [1, 3]),
rect.color(UIColor::GREEN),
)
.proportioned(Axis::Y, [3, 1])
.pad(10)
.into();
renderer.update(&ui);
Self { renderer }
}
pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => self.renderer.draw(),
WindowEvent::Resized(size) => self.renderer.resize(&size),
_ => (),
}
}
}

132
src/testing/render/mod.rs Normal file
View File

@@ -0,0 +1,132 @@
use gui::{UIRenderNode, UI};
use pollster::FutureExt;
use std::sync::Arc;
use wgpu::util::StagingBelt;
use winit::{dpi::PhysicalSize, window::Window};
pub const CLEAR_COLOR: wgpu::Color = wgpu::Color::BLACK;
pub struct Renderer {
surface: wgpu::Surface<'static>,
device: wgpu::Device,
queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
encoder: wgpu::CommandEncoder,
staging_belt: StagingBelt,
ui_node: UIRenderNode,
}
impl Renderer {
pub fn update(&mut self, ui: &UI) {
self.ui_node.update(&self.device, &self.queue, ui);
}
pub fn draw(&mut self) {
let output = self.surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = std::mem::replace(&mut self.encoder, Self::create_encoder(&self.device));
{
let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(CLEAR_COLOR),
store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
..Default::default()
});
self.ui_node.draw(render_pass);
}
self.queue.submit(std::iter::once(encoder.finish()));
self.staging_belt.finish();
output.present();
self.staging_belt.recall();
}
pub fn resize(&mut self, size: &PhysicalSize<u32>) {
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.device, &self.config);
self.ui_node.resize(size, &self.queue);
}
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
})
}
pub fn new(window: Arc<Window>) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
..Default::default()
});
let surface = instance
.create_surface(window.clone())
.expect("Could not create window surface!");
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.block_on()
.expect("Could not get adapter!");
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
..Default::default()
})
.block_on()
.expect("Could not get device!");
let surface_caps = surface.get_capabilities(&adapter);
let surface_format = surface_caps
.formats
.iter()
.copied()
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::AutoVsync,
alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2,
view_formats: vec![],
};
surface.configure(&device, &config);
let staging_belt = StagingBelt::new(4096 * 4);
let encoder = Self::create_encoder(&device);
let shape_pipeline = UIRenderNode::new(&device, &config);
Self {
surface,
device,
queue,
config,
encoder,
staging_belt,
ui_node: shape_pipeline,
}
}
}