6 Commits

94 changed files with 2749 additions and 3466 deletions

443
Cargo.lock generated
View File

@@ -39,9 +39,9 @@ dependencies = [
[[package]] [[package]]
name = "aligned" name = "aligned"
version = "0.4.3" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
dependencies = [ dependencies = [
"as-slice", "as-slice",
] ]
@@ -55,12 +55,6 @@ dependencies = [
"equator", "equator",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "android-activity" name = "android-activity"
version = "0.6.0" version = "0.6.0"
@@ -125,7 +119,7 @@ dependencies = [
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"windows-sys 0.60.2", "windows-sys 0.59.0",
"wl-clipboard-rs", "wl-clipboard-rs",
"x11rb", "x11rb",
] ]
@@ -297,9 +291,9 @@ checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.1" version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
@@ -367,9 +361,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.51" version = "1.2.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -521,9 +515,9 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-text" name = "cosmic-text"
version = "0.16.0" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cadaea21e24c49c0c82116f2b465ae6a49d63c90e428b0f8d9ae1f638ac91f" checksum = "173852283a9a57a3cbe365d86e74dc428a09c50421477d5ad6fe9d9509e37737"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"fontdb", "fontdb",
@@ -533,7 +527,7 @@ dependencies = [
"rangemap", "rangemap",
"rustc-hash", "rustc-hash",
"self_cell", "self_cell",
"skrifa 0.39.0", "skrifa",
"smol_str", "smol_str",
"swash", "swash",
"sys-locale", "sys-locale",
@@ -698,6 +692,12 @@ dependencies = [
"zune-inflate", "zune-inflate",
] ]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]] [[package]]
name = "fax" name = "fax"
version = "0.2.6" version = "0.2.6"
@@ -729,9 +729,9 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.6" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]] [[package]]
name = "fixedbitset" name = "fixedbitset"
@@ -835,7 +835,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [ dependencies = [
"rustix 1.1.3", "rustix 1.1.2",
"windows-link", "windows-link",
] ]
@@ -894,16 +894,33 @@ dependencies = [
] ]
[[package]] [[package]]
name = "gpu-allocator" name = "gpu-alloc"
version = "0.28.0" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
dependencies = [
"bitflags 2.10.0",
"gpu-alloc-types",
]
[[package]]
name = "gpu-alloc-types"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "gpu-allocator"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
dependencies = [ dependencies = [
"ash",
"hashbrown 0.16.1",
"log", "log",
"presser", "presser",
"thiserror 2.0.17", "thiserror 1.0.69",
"windows", "windows",
] ]
@@ -941,14 +958,14 @@ dependencies = [
[[package]] [[package]]
name = "harfrust" name = "harfrust"
version = "0.4.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0caaee032384c10dd597af4579c67dee16650d862a9ccbe1233ff1a379abc07" checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"bytemuck", "bytemuck",
"core_maths", "core_maths",
"read-fonts 0.36.0", "read-fonts",
"smallvec", "smallvec",
] ]
@@ -967,8 +984,6 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [ dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0", "foldhash 0.2.0",
] ]
@@ -1005,7 +1020,7 @@ dependencies = [
"rgb", "rgb",
"tiff", "tiff",
"zune-core 0.5.0", "zune-core 0.5.0",
"zune-jpeg 0.5.8", "zune-jpeg 0.5.6",
] ]
[[package]] [[package]]
@@ -1055,7 +1070,6 @@ dependencies = [
"iris-core", "iris-core",
"iris-macro", "iris-macro",
"pollster", "pollster",
"tokio",
"unicode-segmentation", "unicode-segmentation",
"wgpu", "wgpu",
"winit", "winit",
@@ -1077,7 +1091,6 @@ dependencies = [
name = "iris-macro" name = "iris-macro"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
@@ -1158,9 +1171,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.179" version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]] [[package]]
name = "libfuzzer-sys" name = "libfuzzer-sys"
@@ -1190,13 +1203,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.12" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"libc", "libc",
"redox_syscall 0.7.0", "redox_syscall 0.5.18",
] ]
[[package]] [[package]]
@@ -1283,9 +1296,9 @@ dependencies = [
[[package]] [[package]]
name = "metal" name = "metal"
version = "0.33.0" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"block", "block",
@@ -1308,9 +1321,9 @@ dependencies = [
[[package]] [[package]]
name = "moxcms" name = "moxcms"
version = "0.7.11" version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608"
dependencies = [ dependencies = [
"num-traits", "num-traits",
"pxfm", "pxfm",
@@ -1318,9 +1331,9 @@ dependencies = [
[[package]] [[package]]
name = "naga" name = "naga"
version = "28.0.0" version = "27.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bit-set", "bit-set",
@@ -1753,11 +1766,10 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "orbclient" name = "orbclient"
version = "0.3.50" version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" checksum = "247ad146e19b9437f8604c21f8652423595cf710ad108af40e77d3ae6e96b827"
dependencies = [ dependencies = [
"libc",
"libredox", "libredox",
] ]
@@ -1896,7 +1908,7 @@ dependencies = [
"concurrent-queue", "concurrent-queue",
"hermit-abi", "hermit-abi",
"pin-project-lite", "pin-project-lite",
"rustix 1.1.3", "rustix 1.1.2",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -1908,9 +1920,9 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.13.0" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]] [[package]]
name = "portable-atomic-util" name = "portable-atomic-util"
@@ -1947,9 +1959,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.104" version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9695f8df41bb4f3d222c95a67532365f569318332d03d5f3f67f37b20e6ebdf0" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -1999,9 +2011,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.38.4" version = "0.37.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -2058,9 +2070,9 @@ checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde"
[[package]] [[package]]
name = "rangemap" name = "rangemap"
version = "1.7.1" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" checksum = "acbbbbea733ec66275512d0b9694f34102e7d5406fdbe2ad8d21b28dce92887c"
[[package]] [[package]]
name = "rav1e" name = "rav1e"
@@ -2143,16 +2155,6 @@ name = "read-fonts"
version = "0.35.0" version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358"
dependencies = [
"bytemuck",
"font-types",
]
[[package]]
name = "read-fonts"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eaa2941a4c05443ee3a7b26ab076a553c343ad5995230cc2b1d3e993bdc6345"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"core_maths", "core_maths",
@@ -2177,15 +2179,6 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
] ]
[[package]]
name = "redox_syscall"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags 2.10.0",
]
[[package]] [[package]]
name = "renderdoc-sys" name = "renderdoc-sys"
version = "1.1.0" version = "1.1.0"
@@ -2225,9 +2218,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.3" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"errno", "errno",
@@ -2278,9 +2271,9 @@ dependencies = [
[[package]] [[package]]
name = "self_cell" name = "self_cell"
version = "1.2.2" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33"
[[package]] [[package]]
name = "serde" name = "serde"
@@ -2340,17 +2333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"read-fonts 0.35.0", "read-fonts",
]
[[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]]
@@ -2441,16 +2424,16 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a"
dependencies = [ dependencies = [
"skrifa 0.37.0", "skrifa",
"yazi", "yazi",
"zeno", "zeno",
] ]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.113" version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2466,6 +2449,19 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "tempfile"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix 1.1.2",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.4.1" version = "1.4.1"
@@ -2569,29 +2565,20 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"pin-project-lite",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.7.5+spec-1.1.0" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.23.10+spec-1.0.0" version = "0.23.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"toml_datetime", "toml_datetime",
@@ -2601,18 +2588,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.0.6+spec-1.1.0" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [ dependencies = [
"winnow", "winnow",
] ]
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.44" version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
dependencies = [ dependencies = [
"pin-project-lite", "pin-project-lite",
"tracing-core", "tracing-core",
@@ -2620,9 +2607,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.36" version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
[[package]] [[package]]
name = "tree_magic_mini" name = "tree_magic_mini"
@@ -2776,13 +2763,13 @@ dependencies = [
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.12" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
dependencies = [ dependencies = [
"cc", "cc",
"downcast-rs", "downcast-rs",
"rustix 1.1.3", "rustix 1.1.2",
"scoped-tls", "scoped-tls",
"smallvec", "smallvec",
"wayland-sys", "wayland-sys",
@@ -2790,12 +2777,12 @@ dependencies = [
[[package]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.31.12" version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"rustix 1.1.3", "rustix 1.1.2",
"wayland-backend", "wayland-backend",
"wayland-scanner", "wayland-scanner",
] ]
@@ -2813,20 +2800,20 @@ dependencies = [
[[package]] [[package]]
name = "wayland-cursor" name = "wayland-cursor"
version = "0.31.12" version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29"
dependencies = [ dependencies = [
"rustix 1.1.3", "rustix 1.1.2",
"wayland-client", "wayland-client",
"xcursor", "xcursor",
] ]
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.32.10" version = "0.32.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"wayland-backend", "wayland-backend",
@@ -2836,9 +2823,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-plasma" name = "wayland-protocols-plasma"
version = "0.3.10" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b" checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"wayland-backend", "wayland-backend",
@@ -2849,9 +2836,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-wlr" name = "wayland-protocols-wlr"
version = "0.3.10" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"wayland-backend", "wayland-backend",
@@ -2862,9 +2849,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-scanner" name = "wayland-scanner"
version = "0.31.8" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml", "quick-xml",
@@ -2873,9 +2860,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-sys" name = "wayland-sys"
version = "0.31.8" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
dependencies = [ dependencies = [
"dlib", "dlib",
"log", "log",
@@ -2911,13 +2898,12 @@ checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]] [[package]]
name = "wgpu" name = "wgpu"
version = "28.0.0" version = "27.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bitflags 2.10.0", "bitflags 2.10.0",
"bytemuck",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"document-features", "document-features",
@@ -2941,9 +2927,9 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-core" name = "wgpu-core"
version = "28.0.0" version = "27.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9" checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"bit-set", "bit-set",
@@ -2973,36 +2959,36 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-core-deps-apple" name = "wgpu-core-deps-apple"
version = "28.0.0" version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da" checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233"
dependencies = [ dependencies = [
"wgpu-hal", "wgpu-hal",
] ]
[[package]] [[package]]
name = "wgpu-core-deps-emscripten" name = "wgpu-core-deps-emscripten"
version = "28.0.0" version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79" checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5"
dependencies = [ dependencies = [
"wgpu-hal", "wgpu-hal",
] ]
[[package]] [[package]]
name = "wgpu-core-deps-windows-linux-android" name = "wgpu-core-deps-windows-linux-android"
version = "28.0.0" version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3"
dependencies = [ dependencies = [
"wgpu-hal", "wgpu-hal",
] ]
[[package]] [[package]]
name = "wgpu-hal" name = "wgpu-hal"
version = "28.0.0" version = "27.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef" checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce"
dependencies = [ dependencies = [
"android_system_properties", "android_system_properties",
"arrayvec", "arrayvec",
@@ -3016,6 +3002,7 @@ dependencies = [
"core-graphics-types 0.2.0", "core-graphics-types 0.2.0",
"glow", "glow",
"glutin_wgl_sys", "glutin_wgl_sys",
"gpu-alloc",
"gpu-allocator", "gpu-allocator",
"gpu-descriptor", "gpu-descriptor",
"hashbrown 0.16.1", "hashbrown 0.16.1",
@@ -3048,14 +3035,15 @@ dependencies = [
[[package]] [[package]]
name = "wgpu-types" name = "wgpu-types"
version = "28.0.0" version = "27.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"bytemuck", "bytemuck",
"js-sys", "js-sys",
"log", "log",
"thiserror 2.0.17",
"web-sys", "web-sys",
] ]
@@ -3070,54 +3058,32 @@ dependencies = [
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.62.2" version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [ dependencies = [
"windows-core", "windows-core",
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.62.2" version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
dependencies = [ dependencies = [
"windows-implement", "windows-implement",
"windows-interface", "windows-interface",
"windows-link",
"windows-result", "windows-result",
"windows-strings", "windows-strings",
] "windows-targets 0.52.6",
[[package]]
name = "windows-future"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
] ]
[[package]] [[package]]
name = "windows-implement" name = "windows-implement"
version = "0.60.2" version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3126,9 +3092,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-interface" name = "windows-interface"
version = "0.59.3" version = "0.58.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3141,32 +3107,23 @@ 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 = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core",
"windows-link",
]
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.4.1" 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 = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [ dependencies = [
"windows-link", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
name = "windows-strings" name = "windows-strings"
version = "0.5.1" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [ dependencies = [
"windows-link", "windows-result",
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
@@ -3196,15 +3153,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@@ -3238,39 +3186,13 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc 0.52.6",
] ]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.2" version = "0.42.2"
@@ -3283,12 +3205,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.42.2" version = "0.42.2"
@@ -3301,12 +3217,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.42.2" version = "0.42.2"
@@ -3319,24 +3229,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.42.2" version = "0.42.2"
@@ -3349,12 +3247,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.42.2" version = "0.42.2"
@@ -3367,12 +3259,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.2" version = "0.42.2"
@@ -3385,12 +3271,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.42.2" version = "0.42.2"
@@ -3403,12 +3283,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.30.12" version = "0.30.12"
@@ -3478,14 +3352,15 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]] [[package]]
name = "wl-clipboard-rs" name = "wl-clipboard-rs"
version = "0.9.3" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" checksum = "8e5ff8d0e60065f549fafd9d6cb626203ea64a798186c80d8e7df4f8af56baeb"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"os_pipe", "os_pipe",
"rustix 1.1.3", "rustix 0.38.44",
"tempfile",
"thiserror 2.0.17", "thiserror 2.0.17",
"tree_magic_mini", "tree_magic_mini",
"wayland-backend", "wayland-backend",
@@ -3516,7 +3391,7 @@ dependencies = [
"libc", "libc",
"libloading", "libloading",
"once_cell", "once_cell",
"rustix 1.1.3", "rustix 1.1.2",
"x11rb-protocol", "x11rb-protocol",
] ]
@@ -3627,9 +3502,9 @@ dependencies = [
[[package]] [[package]]
name = "zune-jpeg" name = "zune-jpeg"
version = "0.5.8" version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5" checksum = "f520eebad972262a1dde0ec455bce4f8b298b1e5154513de58c114c4c54303e8"
dependencies = [ dependencies = [
"zune-core 0.5.0", "zune-core 0.5.0",
] ]

View File

@@ -1,5 +1,6 @@
[package] [package]
name = "iris" name = "iris"
default-run = "test"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
@@ -15,10 +16,6 @@ arboard = { workspace = true, features = ["wayland-data-control"] }
pollster = { workspace = true } pollster = { workspace = true }
wgpu = { workspace = true } wgpu = { workspace = true }
image = { workspace = true } image = { workspace = true }
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] }
[dev-dependencies]
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
[workspace] [workspace]
members = ["core", "macro"] members = ["core", "macro"]
@@ -30,13 +27,12 @@ edition = "2024"
[workspace.dependencies] [workspace.dependencies]
pollster = "0.4.0" pollster = "0.4.0"
winit = "0.30.12" winit = "0.30.12"
wgpu = "28.0.0" wgpu = "27.0.1"
bytemuck = "1.23.1" bytemuck = "1.23.1"
image = "0.25.6" image = "0.25.6"
cosmic-text = "0.16.0" cosmic-text = "0.15.0"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
fxhash = "0.2.1" fxhash = "0.2.1"
arboard = "3.6.1" arboard = "3.6.1"
iris-core = { path = "core" } iris-core = { path = "core" }
iris-macro = { path = "macro" } iris-macro = { path = "macro" }
tokio = "1.49.0"

1
TODO
View File

@@ -16,6 +16,7 @@ scaling
field could be best solution so redrawing stuff isn't needed & you can specify both as user 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
fyi this is not the same as what was just implemented
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
painter takes them in instead of (or in addition to) id painter takes them in instead of (or in addition to) id
then type wrapper widgets to contain them then type wrapper widgets to contain them

View File

@@ -10,3 +10,4 @@ bytemuck ={ workspace = true }
image = { workspace = true } image = { workspace = true }
cosmic-text = { workspace = true } cosmic-text = { workspace = true }
fxhash = { workspace = true } fxhash = { workspace = true }

View File

@@ -1,23 +0,0 @@
use crate::{HasUi, WidgetIdFn, WidgetLike, WidgetRef};
pub trait WidgetAttr<Rsc, W: ?Sized> {
type Input;
fn run(rsc: &mut Rsc, id: WidgetRef<W>, input: Self::Input);
}
pub trait Attrable<Rsc, W: ?Sized, Tag> {
fn attr<A: WidgetAttr<Rsc, W>>(self, input: A::Input) -> impl WidgetIdFn<Rsc, W>;
}
impl<Rsc: HasUi, WL: WidgetLike<Rsc, Tag>, Tag> Attrable<Rsc, WL::Widget, Tag> for WL {
fn attr<A: WidgetAttr<Rsc, WL::Widget>>(
self,
input: A::Input,
) -> impl WidgetIdFn<Rsc, WL::Widget> {
|rsc| {
let id = self.add(rsc);
A::run(rsc, id, input);
id
}
}
}

View File

@@ -1,18 +0,0 @@
use crate::{HasEvents, HasUi, Widget, WidgetRef};
pub struct EventCtx<'a, Rsc: HasEvents, Data> {
pub state: &'a mut Rsc::State,
pub data: Data,
}
pub struct EventIdCtx<'a, Rsc: HasEvents, Data, W: ?Sized> {
pub widget: WidgetRef<W>,
pub state: &'a mut Rsc::State,
pub data: Data,
}
impl<Rsc: HasEvents + HasUi, Data, W: Widget> EventIdCtx<'_, Rsc, Data, W> {
pub fn widget<'a>(&self, rsc: &'a mut Rsc) -> &'a mut W {
&mut rsc.ui_mut()[self.widget]
}
}

View File

@@ -1,156 +0,0 @@
use crate::{
ActiveData, Event, EventCtx, EventFn, EventIdCtx, EventLike, HasEvents, IdLike, LayerId,
Widget, WidgetEventFn, WidgetId, WidgetRef,
util::{HashMap, HashSet, TypeMap},
};
use std::{any::TypeId, rc::Rc};
pub struct EventManager<Rsc> {
widget_to_types: HashMap<WidgetId, HashSet<TypeId>>,
types: TypeMap<dyn EventManagerLike<Rsc>>,
}
impl<Rsc> Default for EventManager<Rsc> {
fn default() -> Self {
Self {
widget_to_types: Default::default(),
types: Default::default(),
}
}
}
impl<Rsc: HasEvents + 'static> EventManager<Rsc> {
pub fn get_type<E: EventLike>(&mut self) -> &mut TypeEventManager<Rsc, E::Event> {
self.types.type_or_default()
}
pub fn register<W: Widget + ?Sized, E: EventLike>(
&mut self,
id: WidgetRef<W>,
event: E,
f: impl WidgetEventFn<Rsc, <E::Event as Event>::Data, W>,
) {
self.get_type::<E>().register(id, event, f);
self.widget_to_types
.entry(id.id())
.or_default()
.insert(Self::type_key::<E>());
}
pub fn type_key<E: EventLike>() -> TypeId {
TypeId::of::<TypeEventManager<Rsc, E::Event>>()
}
}
pub trait EventsLike {
fn remove(&mut self, id: WidgetId);
fn draw(&mut self, active: &ActiveData);
fn undraw(&mut self, active: &ActiveData);
}
impl<Rsc: HasEvents + 'static> EventsLike for EventManager<Rsc> {
fn remove(&mut self, id: WidgetId) {
for t in self.widget_to_types.get(&id).into_flat_iter() {
self.types.get_mut(t).unwrap().remove(id);
}
}
fn draw(&mut self, active: &ActiveData) {
for t in self.widget_to_types.get(&active.id).into_flat_iter() {
self.types.get_mut(t).unwrap().draw(active);
}
}
fn undraw(&mut self, active: &ActiveData) {
for t in self.widget_to_types.get(&active.id).into_flat_iter() {
self.types.get_mut(t).unwrap().undraw(active);
}
}
}
pub trait EventManagerLike<State> {
fn remove(&mut self, id: WidgetId);
fn draw(&mut self, data: &ActiveData);
fn undraw(&mut self, data: &ActiveData);
}
type EventData<Rsc, E> = (E, Rc<dyn EventFn<Rsc, <E as Event>::Data>>);
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
// TODO: reduce visiblity!!
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
}
impl<Rsc: HasEvents, E: Event> EventManagerLike<Rsc> for TypeEventManager<Rsc, E> {
fn remove(&mut self, id: WidgetId) {
self.map.remove(&id);
for layer in self.active.values_mut() {
layer.remove(&id);
}
}
fn draw(&mut self, data: &ActiveData) {
self.active
.entry(data.layer)
.or_default()
.entry(data.id)
.or_default();
}
fn undraw(&mut self, data: &ActiveData) {
if let Some(layer) = self.active.get_mut(&data.layer) {
layer.remove(&data.id);
}
}
}
impl<Rsc: HasEvents, E: Event> Default for TypeEventManager<Rsc, E> {
fn default() -> Self {
Self {
active: Default::default(),
map: Default::default(),
}
}
}
impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
fn register<W: Widget + ?Sized>(
&mut self,
widget: WidgetRef<W>,
event: impl EventLike<Event = E>,
f: impl WidgetEventFn<Rsc, E::Data, W>,
) {
let event = event.into_event();
self.map.entry(widget.id()).or_default().push((
event,
Rc::new(move |ctx, rsc| {
f(
EventIdCtx {
widget,
state: ctx.state,
data: ctx.data,
},
rsc,
);
}),
));
}
pub fn run_fn<'a>(
&mut self,
id: impl IdLike,
) -> impl FnOnce(EventCtx<'_, Rsc, E::Data>, &mut Rsc) + 'a {
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
move |ctx, rsc| {
for (e, f) in fs {
if let Some(data) = e.should_run(&ctx.data) {
f(
EventCtx {
state: ctx.state,
data,
},
rsc,
)
}
}
}
}
}

View File

@@ -1,44 +0,0 @@
mod ctx;
mod manager;
mod rsc;
pub use ctx::*;
pub use manager::*;
pub use rsc::*;
pub trait Event: Sized + 'static + Clone {
type Data: Clone = ();
type State: Default = ();
#[allow(unused_variables)]
fn should_run(&self, data: &Self::Data) -> Option<Self::Data> {
Some(data.clone())
}
}
pub trait EventLike {
type Event: Event;
fn into_event(self) -> Self::Event;
}
impl<E: Event> EventLike for E {
type Event = Self;
fn into_event(self) -> Self::Event {
self
}
}
pub trait EventFn<Rsc: HasEvents, Data>: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static {}
impl<Rsc: HasEvents, F: Fn(EventCtx<Rsc, Data>, &mut Rsc) + 'static, Data> EventFn<Rsc, Data>
for F
{
}
pub trait WidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>:
Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static
{
}
impl<Rsc: HasEvents, F: Fn(EventIdCtx<Rsc, Data, W>, &mut Rsc) + 'static, Data, W: ?Sized>
WidgetEventFn<Rsc, Data, W> for F
{
}

View File

@@ -1,34 +0,0 @@
use crate::{
Event, EventCtx, EventLike, EventManager, HasUi, IdLike, Widget, WidgetEventFn, WidgetRef,
};
pub trait HasState: 'static {
type State;
}
pub trait HasEvents: Sized + HasUi + HasState {
fn events(&self) -> &EventManager<Self>;
fn events_mut(&mut self) -> &mut EventManager<Self>;
fn register_event<W: Widget + ?Sized, E: EventLike>(
&mut self,
id: WidgetRef<W>,
event: E,
f: impl WidgetEventFn<Self, <E::Event as Event>::Data, W>,
) {
self.events_mut().register(id, event, f);
}
}
pub trait RunEvents: HasEvents {
fn run_event<E: EventLike>(
&mut self,
id: impl IdLike,
data: <E::Event as Event>::Data,
state: &mut Self::State,
) {
let f = self.events_mut().get_type::<E>().run_fn(id);
f(EventCtx { state, data }, self)
}
}
impl<T: HasEvents> RunEvents for T {}

20
core/src/layout/attr.rs Normal file
View File

@@ -0,0 +1,20 @@
use crate::layout::{Ui, WidgetIdFn, WidgetLike, WidgetRef};
pub trait WidgetAttr<W: ?Sized> {
type Input;
fn run(ui: &mut Ui, id: &WidgetRef<W>, input: Self::Input);
}
pub trait Attrable<W: ?Sized, Tag> {
fn attr<A: WidgetAttr<W>>(self, input: A::Input) -> impl WidgetIdFn<W>;
}
impl<WL: WidgetLike<Tag>, Tag> Attrable<WL::Widget, Tag> for WL {
fn attr<A: WidgetAttr<WL::Widget>>(self, input: A::Input) -> impl WidgetIdFn<WL::Widget> {
|ui| {
let id = self.add(ui);
A::run(ui, &id, input);
id
}
}
}

187
core/src/layout/event.rs Normal file
View File

@@ -0,0 +1,187 @@
use std::{hash::Hash, rc::Rc};
use crate::{
layout::{IdFnTag, Ui, UiModule, WidgetIdFn, WidgetLike, WidgetRef},
util::{HashMap, Id},
};
pub trait Event: Sized {
type Module<Ctx: 'static>: EventModule<Self, Ctx>;
type Data: Clone;
}
pub struct EventCtx<'a, Ctx, Data> {
pub ui: &'a mut Ui,
pub state: &'a mut Ctx,
pub data: Data,
}
pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>;
pub struct EventIdCtx<'a, Ctx, Data, W: ?Sized> {
pub widget: &'a WidgetRef<W>,
pub ui: &'a mut Ui,
pub state: &'a mut Ctx,
pub data: Data,
}
pub trait EventFn<Ctx, Data>: Fn(EventCtx<Ctx, Data>) + 'static {}
impl<F: Fn(EventCtx<Ctx, Data>) + 'static, Ctx, Data> EventFn<Ctx, Data> for F {}
pub trait WidgetEventFn<Ctx, Data, W: ?Sized>: Fn(EventIdCtx<Ctx, Data, W>) + 'static {}
impl<F: Fn(EventIdCtx<Ctx, Data, W>) + 'static, Ctx, Data, W: ?Sized> WidgetEventFn<Ctx, Data, W>
for F
{
}
impl Ui {
pub fn register_event<W: ?Sized, E: Event, Ctx: 'static>(
&mut self,
id: &WidgetRef<W>,
event: E,
f: impl EventFn<Ctx, E::Data>,
) {
self.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id(), event, f);
}
pub fn register_widget_event<W: ?Sized + 'static, E: Event, Ctx: 'static>(
&mut self,
id: &WidgetRef<W>,
event: E,
f: impl WidgetEventFn<Ctx, E::Data, W>,
) {
let id_ = id.weak();
self.data
.modules
.get_mut::<E::Module<Ctx>>()
.register(id.id(), event, move |ctx| {
f(EventIdCtx {
widget: &id_.expect_strong(),
ui: ctx.ui,
state: ctx.state,
data: ctx.data,
})
});
}
}
pub trait DefaultEvent: Hash + Eq + 'static {
type Data: Clone;
}
impl<E: DefaultEvent> Event for E {
type Module<Ctx: 'static> = DefaultEventModule<E, Ctx>;
type Data = E::Data;
}
pub trait EventModule<E: Event, Ctx>: UiModule + Default {
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, E::Data>);
fn run<'a>(
&self,
id: &Id,
event: E,
) -> Option<impl Fn(EventCtx<Ctx, E::Data>) + use<'a, Self, E, Ctx>>;
}
type EventFnMap<Ctx, Data> = HashMap<Id, Vec<Rc<dyn EventFn<Ctx, Data>>>>;
pub struct DefaultEventModule<E: Event, Ctx> {
map: HashMap<E, EventFnMap<Ctx, <E as Event>::Data>>,
}
impl<E: Event + 'static, Ctx: 'static> UiModule for DefaultEventModule<E, Ctx> {
fn on_remove(&mut self, id: &Id) {
for map in self.map.values_mut() {
map.remove(id);
}
}
}
pub trait HashableEvent: Event + Hash + Eq + 'static {}
impl<E: Event + Hash + Eq + 'static> HashableEvent for E {}
impl<E: HashableEvent, Ctx: 'static> EventModule<E, Ctx> for DefaultEventModule<E, Ctx> {
fn register(&mut self, id: Id, event: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
self.map
.entry(event)
.or_default()
.entry(id)
.or_default()
.push(Rc::new(f));
}
fn run<'a>(
&self,
id: &Id,
event: E,
) -> Option<impl Fn(EventCtx<Ctx, E::Data>) + use<'a, E, Ctx>> {
if let Some(map) = self.map.get(&event)
&& let Some(fs) = map.get(id)
{
let fs = fs.clone();
Some(move |ctx: EventCtx<Ctx, <E as Event>::Data>| {
for f in &fs {
f(EventCtx {
ui: ctx.ui,
state: ctx.state,
data: ctx.data.clone(),
})
}
})
} else {
None
}
}
}
impl<E: HashableEvent, Ctx: 'static> DefaultEventModule<E, Ctx> {
pub fn run_all(&self, event: E, ctx: EventCtx<Ctx, E::Data>)
where
E::Data: Clone,
{
if let Some(map) = self.map.get(&event) {
for fs in map.values() {
for f in fs {
f(EventCtx {
ui: ctx.ui,
state: ctx.state,
data: ctx.data.clone(),
})
}
}
}
}
}
impl<E: Event + 'static, Ctx: 'static> Default for DefaultEventModule<E, Ctx> {
fn default() -> Self {
Self {
map: Default::default(),
}
}
}
impl Ui {
pub fn run_event<E: Event, Ctx: 'static, W: ?Sized>(
&mut self,
ctx: &mut Ctx,
id: &WidgetRef<W>,
event: E,
data: E::Data,
) {
if let Some(f) = self
.data
.modules
.get_mut::<E::Module<Ctx>>()
.run(&id.id(), event)
{
f(EventCtx {
ui: self,
state: ctx,
data,
});
}
}
}

24
core/src/layout/mod.rs Normal file
View File

@@ -0,0 +1,24 @@
mod attr;
mod event;
mod module;
mod num;
mod orientation;
mod painter;
mod primitive;
mod ui;
mod view;
mod widget;
pub use attr::*;
pub use event::*;
pub use module::*;
pub use num::*;
pub use orientation::*;
pub use painter::*;
pub use primitive::*;
pub use ui::*;
pub use view::*;
pub use widget::*;
pub use crate::util::Vec2;
pub type UiColor = Color<u8>;

35
core/src/layout/module.rs Normal file
View File

@@ -0,0 +1,35 @@
use std::any::{Any, TypeId};
use crate::{
layout::WidgetInstance,
util::{HashMap, Id},
};
#[allow(unused_variables)]
pub trait UiModule: Any {
fn on_draw(&mut self, inst: &WidgetInstance) {}
fn on_undraw(&mut self, inst: &WidgetInstance) {}
fn on_remove(&mut self, id: &Id) {}
fn on_move(&mut self, inst: &WidgetInstance) {}
}
#[derive(Default)]
pub struct Modules {
map: HashMap<TypeId, Box<dyn UiModule>>,
}
impl Modules {
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (dyn UiModule + 'static)> {
self.map.values_mut().map(|m| m.as_mut())
}
pub fn get_mut<M: UiModule + Default>(&mut self) -> &mut M {
let rf = self
.map
.entry(TypeId::of::<M>())
.or_insert_with(|| Box::new(M::default()))
.as_mut();
let any: &mut dyn Any = &mut *rf;
any.downcast_mut().unwrap()
}
}

View File

@@ -23,19 +23,6 @@ impl const UiNum for i32 {
} }
} }
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
Vec2::new(x.to_f32(), y.to_f32())
}
impl<T: const UiNum + Copy> const From<T> for Vec2 {
fn from(v: T) -> Self {
Self {
x: v.to_f32(),
y: v.to_f32(),
}
}
}
impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2 impl<T: const UiNum, U: const UiNum> const From<(T, U)> for Vec2
where where
(T, U): const Destruct, (T, U): const Destruct,
@@ -47,3 +34,16 @@ where
} }
} }
} }
impl<T: const UiNum + Copy> const From<T> for Vec2 {
fn from(v: T) -> Self {
Self {
x: v.to_f32(),
y: v.to_f32(),
}
}
}
pub const fn vec2(x: impl const UiNum, y: impl const UiNum) -> Vec2 {
Vec2::new(x.to_f32(), y.to_f32())
}

View File

@@ -1,4 +1,4 @@
use crate::vec2; use crate::layout::vec2;
use super::*; use super::*;

View File

@@ -6,6 +6,9 @@ pub enum Axis {
Y, Y,
} }
pub trait Axis_ {
}
impl std::ops::Not for Axis { impl std::ops::Not for Axis {
type Output = Self; type Output = Self;
@@ -68,48 +71,3 @@ impl Vec2 {
} }
} }
} }
pub const trait AxisT {
fn get() -> Axis;
}
pub struct XAxis;
impl const AxisT for XAxis {
fn get() -> Axis {
Axis::X
}
}
pub struct YAxis;
impl const AxisT for YAxis {
fn get() -> Axis {
Axis::Y
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct BothAxis<T> {
pub x: T,
pub y: T,
}
impl<T> BothAxis<T> {
pub const fn axis<A: const AxisT>(&mut self) -> &mut T {
match A::get() {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub fn take_axis<A: const AxisT>(self) -> T {
match A::get() {
Axis::X => self.x,
Axis::Y => self.y,
}
}
pub fn axis_dyn(&mut self, axis: Axis) -> &mut T {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
}

View File

@@ -1,5 +1,5 @@
use super::*; use super::*;
use crate::{UiNum, util::impl_op}; use crate::{layout::UiNum, util::impl_op};
#[derive(Debug, Default, Clone, Copy, PartialEq)] #[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Size { pub struct Size {

View File

@@ -3,8 +3,7 @@ mod axis;
mod len; mod len;
mod pos; mod pos;
use crate::util::Vec2; use super::Vec2;
pub use align::*; pub use align::*;
pub use axis::*; pub use axis::*;
pub use len::*; pub use len::*;

View File

@@ -2,7 +2,7 @@ use std::{fmt::Display, hash::Hash, marker::Destruct};
use super::*; use super::*;
use crate::{ use crate::{
UiNum, layout::UiNum,
util::{LerpUtil, impl_op}, util::{LerpUtil, impl_op},
}; };

609
core/src/layout/painter.rs Normal file
View File

@@ -0,0 +1,609 @@
use std::{cell::Ref, marker::Unsize, sync::mpsc::Sender};
use crate::{
layout::{
Axis, Len, Modules, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData,
TextureHandle, Textures, UiRegion, UiVec2, Vec2, Widget, WidgetRef, WidgetUpdate, Widgets,
},
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
util::{HashMap, HashSet, Id, TrackedArena},
};
/// makes your surfaces look pretty
pub struct Painter<'a, 'c> {
ctx: &'a mut PainterCtx<'c>,
widget: WidgetRef,
id: Id,
region: UiRegion,
mask: MaskIdx,
textures: Vec<TextureHandle>,
primitives: Vec<PrimitiveHandle>,
children: Vec<Id>,
children_width: HashMap<Id, (UiVec2, Len)>,
children_height: HashMap<Id, (UiVec2, Len)>,
pub layer: usize,
}
/// context for a painter; lets you draw and redraw widgets
struct PainterCtx<'a> {
pub widgets: &'a Widgets,
pub active: &'a mut HashMap<Id, WidgetInstance>,
pub layers: &'a mut PrimitiveLayers,
pub textures: &'a mut Textures,
pub masks: &'a mut TrackedArena<Mask, u32>,
pub text: &'a mut TextData,
pub output_size: Vec2,
pub modules: &'a mut Modules,
pub cache_width: HashMap<Id, (UiVec2, Len)>,
pub cache_height: HashMap<Id, (UiVec2, Len)>,
pub needs_redraw: HashSet<Id>,
draw_started: HashSet<Id>,
}
/// stores information for children about the highest level parent that needed their size
/// so that they can redraw the parent if their size changes
#[derive(Clone, Copy, Debug, Default)]
pub struct ResizeRef {
x: Option<(Id, (UiVec2, Len))>,
y: Option<(Id, (UiVec2, Len))>,
}
/// important non rendering data for retained drawing
#[derive(Debug)]
pub struct WidgetInstance {
pub id: Id,
pub region: UiRegion,
pub parent: Option<Id>,
pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<Id>,
pub resize: ResizeRef,
pub mask: MaskIdx,
pub layer: usize,
}
/// data to be stored in Ui to create PainterCtxs easily
pub struct PainterData {
pub widgets: Widgets,
pub active: HashMap<Id, WidgetInstance>,
pub layers: PrimitiveLayers,
pub textures: Textures,
pub text: TextData,
pub output_size: Vec2,
pub modules: Modules,
pub px_dependent: HashSet<Id>,
pub masks: TrackedArena<Mask, u32>,
}
impl PainterData {
pub fn new(send: Sender<WidgetUpdate>) -> Self {
Self {
widgets: Widgets::new(send),
active: Default::default(),
layers: Default::default(),
textures: Default::default(),
text: Default::default(),
output_size: Default::default(),
modules: Default::default(),
px_dependent: Default::default(),
masks: Default::default(),
}
}
}
impl<'a> PainterCtx<'a> {
/// redraws a widget that's currently active (drawn)
/// can be called on something already drawn or removed,
/// will just return if so
pub fn redraw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, widget: &WidgetRef<W>) {
let id = widget.id();
self.needs_redraw.remove(&id);
if self.draw_started.contains(&id) {
return;
}
let Some(active) = self.active.get(&id) else {
return;
};
let mut resize = active.resize;
// set resize back after redrawing
let finish = |s: &mut Self, resize| {
if let Some(active) = s.active.get_mut(&id) {
// might need to get_or_insert here instead of just assuming
active.resize = resize;
}
};
// check if a parent depends on the desired size of this, if so then redraw it first
// TODO: this is stupid having 2 of these, don't ask me what the consequences are
let mut ret = false;
if let Some((rid, (outer, old_desired))) = &mut resize.x {
let new_desired = SizeCtx {
source: id,
cache_width: &mut self.cache_width,
cache_height: &mut self.cache_height,
text: self.text,
textures: self.textures,
outer: *outer,
output_size: self.output_size,
checked_width: &mut Default::default(),
checked_height: &mut Default::default(),
id,
}
.width(widget);
if new_desired != *old_desired {
// unsure if I need to walk down the tree here
self.redraw(&self.widgets.get(*rid));
*old_desired = new_desired;
if self.draw_started.contains(&id) {
ret = true;
}
}
}
if let Some((rid, (outer, old_desired))) = &mut resize.y {
// NOTE: might need hack in Span here (or also do it properly here)
let new_desired = SizeCtx {
source: id,
cache_width: &mut self.cache_width,
cache_height: &mut self.cache_height,
text: self.text,
textures: self.textures,
outer: *outer,
output_size: self.output_size,
checked_width: &mut Default::default(),
checked_height: &mut Default::default(),
id,
}
.height(widget);
if new_desired != *old_desired {
self.redraw(&self.widgets.get(*rid));
*old_desired = new_desired;
if self.draw_started.contains(&id) {
ret = true;
}
}
}
if ret {
return finish(self, resize);
}
let Some(active) = self.remove(id) else {
return;
};
self.draw_inner(
active.layer,
widget,
active.region,
active.parent,
active.mask,
Some(active.children),
);
finish(self, resize);
}
fn draw_inner<W: Widget + ?Sized + Unsize<dyn Widget>>(
&mut self,
layer: usize,
widget: &WidgetRef<W>,
region: UiRegion,
parent: Option<Id>,
mask: MaskIdx,
old_children: Option<Vec<Id>>,
) {
let id = widget.id();
// I have no idea if these checks work lol
// the idea is u can't redraw stuff u already drew,
// and if parent is different then there's another copy with a different parent
// but this has a very weird issue where you can't move widgets unless u remove first
// so swapping is impossible rn I think?
// there's definitely better solutions like a counter (>1 = panic) but don't care rn
// if self.draw_started.contains(&id) {
// panic!(
// "Cannot draw the same widget ({}) twice (1)",
// self.widgets.data(&id).unwrap().label
// );
// }
let mut old_children = old_children.unwrap_or_default();
let mut resize = ResizeRef::default();
if let Some(active) = self.active.get_mut(&id)
&& !self.needs_redraw.contains(&id)
{
// check to see if we can skip drawing first
if active.parent != parent {
panic!("Cannot draw the same widget twice (2)");
}
if active.region == region {
return;
} else if active.region.size() == region.size() {
// TODO: epsilon?
let from = active.region;
self.mov(id, from, region);
return;
}
// if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id).unwrap();
old_children = active.children;
resize = active.resize;
}
// draw widget
self.draw_started.insert(id);
let mut painter = Painter {
region,
mask,
layer,
widget: widget.as_any(),
id,
textures: Vec::new(),
primitives: Vec::new(),
ctx: self,
children: Vec::new(),
children_width: Default::default(),
children_height: Default::default(),
};
widget.get_mut_quiet().draw(&mut painter);
let children_width = painter.children_width;
let children_height = painter.children_height;
// add to active
let instance = WidgetInstance {
id,
region,
parent,
textures: painter.textures,
primitives: painter.primitives,
children: painter.children,
resize,
mask: painter.mask,
layer,
};
// set resize for children who's size this widget depends on
for (cid, outer) in children_width {
if let Some(w) = self.active.get_mut(&cid)
&& w.resize.x.is_none()
{
w.resize.x = Some((id, outer))
}
}
for (cid, outer) in children_height {
if let Some(w) = self.active.get_mut(&cid)
&& w.resize.y.is_none()
{
w.resize.y = Some((id, outer))
}
}
// remove old children that weren't kept
for c in &old_children {
if !instance.children.contains(c) {
self.remove_rec(*c);
}
}
// update modules
for m in self.modules.iter_mut() {
m.on_draw(&instance);
}
self.active.insert(id, instance);
}
fn mov(&mut self, id: Id, from: UiRegion, to: UiRegion) {
let active = self.active.get_mut(&id).unwrap();
for h in &active.primitives {
let region = self.layers[h.layer].region_mut(h);
*region = region.outside(&from).within(&to);
}
active.region = active.region.outside(&from).within(&to);
for m in self.modules.iter_mut() {
m.on_move(active);
}
// children will not be changed, so this technically should not be needed
// probably need unsafe
let children = active.children.clone();
for child in children {
self.mov(child, from, to);
}
}
/// NOTE: instance textures are cleared and self.textures freed
fn remove(&mut self, id: Id) -> Option<WidgetInstance> {
let mut inst = self.active.remove(&id);
if let Some(inst) = &mut inst {
for h in &inst.primitives {
let mask = self.layers.free(h);
if mask != MaskIdx::NONE {
self.masks.remove(mask);
}
}
inst.textures.clear();
self.textures.free();
for m in self.modules.iter_mut() {
m.on_undraw(inst);
}
}
inst
}
fn remove_rec(&mut self, id: Id) -> Option<WidgetInstance> {
let inst = self.remove(id);
if let Some(inst) = &inst {
for c in &inst.children {
self.remove_rec(*c);
}
}
inst
}
}
impl PainterData {
fn ctx(&mut self, needs_redraw: HashSet<Id>) -> PainterCtx<'_> {
PainterCtx {
widgets: &self.widgets,
active: &mut self.active,
layers: &mut self.layers,
textures: &mut self.textures,
text: &mut self.text,
output_size: self.output_size,
modules: &mut self.modules,
masks: &mut self.masks,
cache_width: Default::default(),
cache_height: Default::default(),
draw_started: Default::default(),
needs_redraw,
}
}
pub fn draw<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetRef<W>) {
let mut ctx = self.ctx(Default::default());
ctx.draw_started.clear();
ctx.layers.clear();
ctx.draw_inner(0, id, UiRegion::FULL, None, MaskIdx::NONE, None);
}
pub fn redraw(&mut self, ids: HashSet<Id>) {
let mut ctx = self.ctx(ids);
while let Some(&id) = ctx.needs_redraw.iter().next() {
ctx.redraw(&ctx.widgets.get(id));
}
}
}
impl<'a, 'c> Painter<'a, 'c> {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
let h = self.ctx.layers.write(
self.layer,
PrimitiveInst {
id: self.id(),
primitive,
region,
mask_idx: self.mask,
},
);
if self.mask != MaskIdx::NONE {
// TODO: I have no clue if this works at all :joy:
self.ctx.masks.push_ref(self.mask);
}
self.primitives.push(h);
}
/// Writes a primitive to be rendered
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
self.primitive_at(primitive, self.region)
}
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
self.primitive_at(primitive, region.within(&self.region));
}
pub fn set_mask(&mut self, region: UiRegion) {
assert!(self.mask == MaskIdx::NONE);
self.mask = self.ctx.masks.push(Mask { region });
}
/// Draws a widget within this widget's region.
pub fn widget<W: Widget + ?Sized + Unsize<dyn Widget>>(&mut self, id: &WidgetRef<W>) {
self.widget_at(id, self.region);
}
/// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas.
pub fn widget_within<W: Widget + ?Sized + Unsize<dyn Widget>>(
&mut self,
id: &WidgetRef<W>,
region: UiRegion,
) {
self.widget_at(id, region.within(&self.region));
}
fn widget_at<W: Widget + ?Sized + Unsize<dyn Widget>>(
&mut self,
id: &WidgetRef<W>,
region: UiRegion,
) {
self.children.push(id.id());
self.ctx
.draw_inner(self.layer, id, region, Some(self.id()), self.mask, None);
}
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);
}
/// returns (handle, offset from top left)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.ctx
.text
.get_mut()
.draw(buffer, attrs, self.ctx.textures)
}
pub fn region(&self) -> UiRegion {
self.region
}
pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetRef<W>) -> Size {
self.size_ctx().size(id)
}
pub fn len_axis<W: Widget + ?Sized>(&mut self, id: &WidgetRef<W>, axis: Axis) -> Len {
match axis {
Axis::X => self.size_ctx().width(id),
Axis::Y => self.size_ctx().height(id),
}
}
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
SizeCtx {
source: self.id(),
id: self.id(),
text: self.ctx.text,
textures: self.ctx.textures,
output_size: self.ctx.output_size,
checked_width: &mut self.children_width,
checked_height: &mut self.children_height,
cache_width: &mut self.ctx.cache_width,
cache_height: &mut self.ctx.cache_height,
outer: self.region.size(),
}
}
pub fn output_size(&self) -> Vec2 {
self.ctx.output_size
}
pub fn px_size(&mut self) -> Vec2 {
self.region.size().to_abs(self.ctx.output_size)
}
pub fn text_data(&mut self) -> &mut TextData {
self.ctx.text
}
pub fn child_layer(&mut self) {
self.layer = self.ctx.layers.child(self.layer);
}
pub fn next_layer(&mut self) {
self.layer = self.ctx.layers.next(self.layer);
}
pub fn label(&self) -> Ref<'_, String> {
self.widget.get_label()
}
pub fn id(&self) -> Id {
self.id
}
}
pub struct SizeCtx<'a> {
pub text: &'a mut TextData,
pub textures: &'a mut Textures,
source: Id,
cache_width: &'a mut HashMap<Id, (UiVec2, Len)>,
cache_height: &'a mut HashMap<Id, (UiVec2, Len)>,
checked_width: &'a mut HashMap<Id, (UiVec2, Len)>,
checked_height: &'a mut HashMap<Id, (UiVec2, Len)>,
/// TODO: should this be pub? rn used for sized
pub outer: UiVec2,
output_size: Vec2,
id: Id,
}
impl SizeCtx<'_> {
pub fn id(&self) -> &Id {
&self.id
}
pub fn source(&self) -> &Id {
&self.source
}
pub fn width<W: Widget + ?Sized>(&mut self, widget: &WidgetRef<W>) -> Len {
// first check cache
// TODO: is this needed? broken rn bc does not store children during upper size check,
// so if something actually using check_* hits cache it fails to add them
// if let Some(&(outer, len)) = self.cache_width.get(&id)
// && outer == self.outer
// {
// self.checked_width.insert(id, (self.outer, len));
// return len;
// }
// store self vars that need to be maintained
let id = widget.id();
let self_outer = self.outer;
let self_id = self.id;
// get size of input id
self.id = id;
let len = widget.get_mut_quiet().desired_width(self);
// restore vars & update cache + checked
self.outer = self_outer;
self.id = self_id;
self.cache_width.insert(id, (self.outer, len));
self.checked_width.insert(id, (self.outer, len));
len
}
// TODO: should be refactored to share code w width_inner
pub fn height<W: Widget + ?Sized>(&mut self, widget: &WidgetRef<W>) -> Len {
// if let Some(&(outer, len)) = self.cache_height.get(&id)
// && outer == self.outer
// {
// self.checked_height.insert(id, (self.outer, len));
// return len;
// }
let id = widget.id();
let self_outer = self.outer;
let self_id = self.id;
self.id = id;
let len = widget.get_mut_quiet().desired_height(self);
self.outer = self_outer;
self.id = self_id;
self.cache_height.insert(id, (self.outer, len));
self.checked_height.insert(id, (self.outer, len));
len
}
pub fn len_axis<W: Widget + ?Sized>(&mut self, id: &WidgetRef<W>, axis: Axis) -> Len {
match axis {
Axis::X => self.width(id),
Axis::Y => self.height(id),
}
}
pub fn size<W: Widget + ?Sized>(&mut self, id: &WidgetRef<W>) -> Size {
Size {
x: self.width(id),
y: self.height(id),
}
}
pub fn px_size(&mut self) -> Vec2 {
self.outer.to_abs(self.output_size)
}
pub fn output_size(&mut self) -> Vec2 {
self.output_size
}
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.text.get_mut().draw(buffer, attrs, self.textures)
}
}

View File

@@ -1,9 +1,11 @@
#![allow(clippy::multiple_bound_locations)]
use std::marker::Destruct; use std::marker::Destruct;
/// stored in linear for sane manipulation /// stored in linear for sane manipulation
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)] #[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
pub struct Color<T> { pub struct Color<T: ColorNum> {
pub r: T, pub r: T,
pub g: T, pub g: T,
pub b: T, pub b: T,

View File

@@ -1,11 +1,6 @@
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use crate::{ use crate::render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives};
render::{MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, Primitives},
util::to_mut,
};
pub type LayerId = usize;
struct LayerNode<T> { struct LayerNode<T> {
next: Ptr, next: Ptr,
@@ -54,13 +49,13 @@ impl<T: Default> Layers<T> {
self.vec.push(LayerNode::head()); self.vec.push(LayerNode::head());
} }
fn push(&mut self, node: LayerNode<T>) -> LayerId { fn push(&mut self, node: LayerNode<T>) -> usize {
let i = self.vec.len(); let i = self.vec.len();
self.vec.push(node); self.vec.push(node);
i i
} }
pub fn next(&mut self, i: LayerId) -> LayerId { pub fn next(&mut self, i: usize) -> usize {
if let Ptr::Next(i) = self.vec[i].next { if let Ptr::Next(i) = self.vec[i].next {
return i; return i;
} }
@@ -80,7 +75,7 @@ impl<T: Default> Layers<T> {
i_new i_new
} }
pub fn child(&mut self, i: LayerId) -> LayerId { pub fn child(&mut self, i: usize) -> usize {
if let Some(c) = self.vec[i].child { if let Some(c) = self.vec[i].child {
return c.head; return c.head;
} }
@@ -105,11 +100,11 @@ impl<T: Default> Layers<T> {
self.vec.iter_mut().map(|n| &mut n.data).enumerate() self.vec.iter_mut().map(|n| &mut n.data).enumerate()
} }
pub fn iter(&self) -> impl Iterator<Item = (LayerId, &T)> { pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
self.indices().map(|i| (i, &self.vec[i].data)) self.indices().map(|i| (i, &self.vec[i].data))
} }
pub fn iter_depth(&self) -> impl Iterator<Item = ((LayerId, usize), &T)> { pub fn iter_depth(&self) -> impl Iterator<Item = ((usize, usize), &T)> {
self.indices() self.indices()
.map(|i| ((i, self.vec[i].depth), &self.vec[i].data)) .map(|i| ((i, self.vec[i].depth), &self.vec[i].data))
} }
@@ -120,11 +115,7 @@ impl<T: Default> Layers<T> {
} }
impl PrimitiveLayers { impl PrimitiveLayers {
pub fn write<P: Primitive>( pub fn write<P: Primitive>(&mut self, layer: usize, info: PrimitiveInst<P>) -> PrimitiveHandle {
&mut self,
layer: LayerId,
info: PrimitiveInst<P>,
) -> PrimitiveHandle {
self[layer].write(layer, info) self[layer].write(layer, info)
} }
@@ -139,16 +130,16 @@ impl<T: Default> Default for Layers<T> {
} }
} }
impl<T> Index<LayerId> for Layers<T> { impl<T> Index<usize> for Layers<T> {
type Output = T; type Output = T;
fn index(&self, index: LayerId) -> &Self::Output { fn index(&self, index: usize) -> &Self::Output {
&self.vec[index].data &self.vec[index].data
} }
} }
impl<T> IndexMut<LayerId> for Layers<T> { impl<T> IndexMut<usize> for Layers<T> {
fn index_mut(&mut self, index: LayerId) -> &mut Self::Output { fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.vec[index].data &mut self.vec[index].data
} }
} }
@@ -179,7 +170,8 @@ impl<'a, T> Iterator for LayerIteratorMut<'a, T> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let i = self.inner.next()?; let i = self.inner.next()?;
// SAFETY: requires index iterator to work properly // SAFETY: requires index iterator to work properly
let layer = unsafe { to_mut(&self.inner.vec[i].data) }; #[allow(mutable_transmutes)]
let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
Some((i, layer)) Some((i, layer))
} }
} }
@@ -188,7 +180,8 @@ impl<'a, T> DoubleEndedIterator for LayerIteratorMut<'a, T> {
fn next_back(&mut self) -> Option<Self::Item> { fn next_back(&mut self) -> Option<Self::Item> {
let i = self.inner.next_back()?; let i = self.inner.next_back()?;
// SAFETY: requires index iterator to work properly // SAFETY: requires index iterator to work properly
let layer = unsafe { to_mut(&self.inner.vec[i].data) }; #[allow(mutable_transmutes)]
let layer = unsafe { std::mem::transmute::<&T, &mut T>(&self.inner.vec[i].data) };
Some((i, layer)) Some((i, layer))
} }
} }

View File

@@ -0,0 +1,46 @@
//! tree structure for masking
use crate::layout::UiRegion;
pub struct Masks {
data: Vec<MaskNode>,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MaskPtr(u32);
#[repr(C)]
pub struct MaskNode {
/// TODO: this is just a rect for now,
/// but would like to support arbitrary masks
/// at some point; custom shader
/// would probably handle that case
/// bc you'd need to render to a special target
/// anyways
region: UiRegion,
prev: MaskPtr,
}
impl MaskPtr {
const NONE: Self = Self(u32::MAX);
}
impl Masks {
pub fn push(&mut self, parent: MaskPtr, region: UiRegion) -> MaskPtr {
match parent.0 {
_ => {
}
u32::MAX => {
let i = self.data.len();
self.data.push(MaskNode {
region,
prev: parent,
});
MaskPtr(i as u32)
}
}
}
pub fn pop(&mut self, i: usize) {}
}

View File

@@ -1,23 +1,29 @@
use crate::{Align, RegionAlign, TextureHandle, Textures, UiColor, util::Vec2}; use std::simd::{Simd, num::SimdUint};
use crate::{
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2},
util::Handle,
};
use cosmic_text::{ use cosmic_text::{
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache, Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
SwashContent, SwashContent,
}; };
use image::{GenericImageView, RgbaImage}; use image::{GenericImageView, RgbaImage};
use std::simd::{Simd, num::SimdUint};
/// TODO: properly wrap this /// TODO: properly wrap this
pub mod text_lib { pub mod text_lib {
pub use cosmic_text::*; pub use cosmic_text::*;
} }
pub struct TextData { pub type TextData = Handle<TextDataInner>;
pub struct TextDataInner {
pub font_system: FontSystem, pub font_system: FontSystem,
pub swash_cache: SwashCache, pub swash_cache: SwashCache,
glyph_cache: Vec<(Placement, CacheKey, Color)>, glyph_cache: Vec<(Placement, CacheKey, Color)>,
} }
impl Default for TextData { impl Default for TextDataInner {
fn default() -> Self { fn default() -> Self {
Self { Self {
font_system: FontSystem::new(), font_system: FontSystem::new(),
@@ -58,7 +64,7 @@ pub type TextBuffer = Buffer;
impl Default for TextAttrs { impl Default for TextAttrs {
fn default() -> Self { fn default() -> Self {
let size = 16.0; let size = 14.0;
Self { Self {
color: UiColor::WHITE, color: UiColor::WHITE,
font_size: size, font_size: size,
@@ -72,7 +78,7 @@ impl Default for TextAttrs {
pub const LINE_HEIGHT_MULT: f32 = 1.1; pub const LINE_HEIGHT_MULT: f32 = 1.1;
impl TextData { impl TextDataInner {
pub fn draw( pub fn draw(
&mut self, &mut self,
buffer: &mut TextBuffer, buffer: &mut TextBuffer,

View File

@@ -1,13 +1,12 @@
use crate::{
render::TexturePrimitive,
util::{RefCounter, Vec2},
};
use image::{DynamicImage, GenericImageView};
use std::{ use std::{
ops::Index, ops::Index,
sync::mpsc::{Receiver, Sender, channel}, sync::mpsc::{Receiver, Sender, channel},
}; };
use image::{DynamicImage, GenericImageView};
use crate::{layout::Vec2, render::TexturePrimitive, util::RefCounter};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TextureHandle { pub struct TextureHandle {
inner: TexturePrimitive, inner: TexturePrimitive,

167
core/src/layout/ui.rs Normal file
View File

@@ -0,0 +1,167 @@
use image::DynamicImage;
use crate::{
layout::{
IdLike, PainterData, PixelRegion, TextureHandle, Vec2, Widget, WidgetInstance, WidgetLike,
WidgetRef, WidgetUpdate,
},
util::{HashSet, Id},
};
use std::sync::mpsc::{Receiver, channel};
pub struct Ui {
pub(crate) data: PainterData,
root: Option<WidgetRef>,
updates: HashSet<Id>,
free: Vec<Id>,
recv: Receiver<WidgetUpdate>,
full_redraw: bool,
resized: bool,
}
impl Ui {
pub fn add<W: Widget, Tag>(&mut self, w: impl WidgetLike<Tag, Widget = W>) -> WidgetRef<W> {
w.add(self)
}
pub fn add_widget<W: Widget>(&mut self, w: W) -> WidgetRef<W> {
self.data.widgets.insert(w)
}
pub fn set_root<Tag>(&mut self, w: impl WidgetLike<Tag>) {
self.root = Some(w.add(self));
self.full_redraw = true;
}
pub fn new() -> Self {
Self::default()
}
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
self.data.textures.add(image)
}
pub fn resize(&mut self, size: impl Into<Vec2>) {
self.data.output_size = size.into();
self.resized = true;
}
fn redraw_all(&mut self) {
for (_, inst) in self.data.active.drain() {
for m in self.data.modules.iter_mut() {
m.on_undraw(&inst);
}
}
// free before bc nothing should exist
self.free();
if let Some(root) = &self.root {
self.data.draw(root);
}
}
pub fn update(&mut self) -> bool {
for update in self.recv.try_iter() {
match update {
WidgetUpdate::Drop(id) => {
self.free.push(id);
}
WidgetUpdate::Mutate(id) => {
self.updates.insert(id);
}
}
}
if self.full_redraw {
self.redraw_all();
self.full_redraw = false;
true
} else if self.resized {
self.resized = false;
self.redraw_all();
true
} else if !self.updates.is_empty() {
self.redraw_updates();
true
} else {
false
}
}
fn redraw_updates(&mut self) {
self.data.redraw(std::mem::take(&mut self.updates));
self.free();
}
/// free any resources that don't have references anymore
fn free(&mut self) {
for id in self.free.drain(..) {
for m in self.data.modules.iter_mut() {
m.on_remove(&id);
}
self.data.widgets.delete(id);
}
self.data.textures.free();
}
pub fn needs_redraw(&self) -> bool {
self.full_redraw || !self.updates.is_empty()
}
pub fn num_widgets(&self) -> usize {
self.data.widgets.len()
}
pub fn active_widgets(&self) -> usize {
self.data.active.len()
}
pub fn debug_layers(&self) {
for ((idx, depth), primitives) in self.data.layers.iter_depth() {
let indent = " ".repeat(depth * 2);
let len = primitives.instances().len();
print!("{indent}{idx}: {len} primitives");
if len >= 1 {
print!(" ({})", primitives.instances()[0].binding);
}
println!();
}
}
pub fn window_region<W>(&self, id: &impl IdLike<W>) -> Option<PixelRegion> {
let region = self.data.active.get(&id.id())?.region;
Some(region.to_px(self.data.output_size))
}
pub fn debug(&self, label: &str) -> impl Iterator<Item = &WidgetInstance> {
self.data.active.iter().filter_map(move |(id, inst)| {
let widget = &self.data.widgets.get(*id);
if widget.get_label().as_str() == label {
Some(inst)
} else {
None
}
})
}
pub fn data(&self) -> &PainterData {
&self.data
}
pub fn data_mut(&mut self) -> &mut PainterData {
&mut self.data
}
}
impl Default for Ui {
fn default() -> Self {
let (send, recv) = channel();
Self {
data: PainterData::new(send),
root: Default::default(),
updates: Default::default(),
free: Default::default(),
full_redraw: false,
recv,
resized: false,
}
}
}

17
core/src/layout/view.rs Normal file
View File

@@ -0,0 +1,17 @@
use crate::layout::{Widget, WidgetLike, WidgetRef};
use std::marker::Unsize;
pub trait WidgetView {
type Widget: Widget + ?Sized + Unsize<dyn Widget> = dyn Widget;
fn view(&self) -> &WidgetRef<Self::Widget>;
}
pub struct ViewTag;
impl<WV: WidgetView> WidgetLike<ViewTag> for WV {
type Widget = WV::Widget;
fn add(self, _ui: &mut super::Ui) -> WidgetRef<Self::Widget> {
self.view().clone()
}
}

View File

@@ -0,0 +1,163 @@
use std::{
cell::{Ref, RefMut},
marker::Unsize,
ops::CoerceUnsized,
sync::mpsc::Sender,
};
use crate::{
layout::{IdFnTag, IdTag, Ui, Widget, WidgetLike},
util::{Handle, Id, WeakHandle},
};
/// An handle for a widget in a UI.
///
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
pub struct WidgetRef<W: ?Sized = dyn Widget>(Handle<Inner<W>>);
pub struct WeakWidgetRef<W: ?Sized = dyn Widget>(WeakHandle<Inner<W>>);
struct Inner<W: ?Sized> {
id: Id,
send: Sender<WidgetUpdate>,
label: String,
widget: W,
}
pub enum WidgetUpdate {
Drop(Id),
Mutate(Id),
}
impl<W> PartialEq for WidgetRef<W> {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl<W> std::fmt::Debug for WidgetRef<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.id().fmt(f)
}
}
impl<W: ?Sized> Clone for WidgetRef<W> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<W: Widget> WidgetRef<W> {
pub(super) fn new(id: Id, widget: W, send: Sender<WidgetUpdate>) -> Self {
let mut label = std::any::type_name::<W>().to_string();
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
}
Self(
Inner {
widget,
id,
send,
label,
}
.into(),
)
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetRef<W> {
pub fn any(self) -> WidgetRef<dyn Widget> {
WidgetRef(self.0)
}
pub fn as_any(&self) -> WidgetRef<dyn Widget> {
WidgetRef(self.0.clone())
}
}
impl<W: ?Sized> WidgetRef<W> {
pub fn id(&self) -> Id {
self.0.get().id
}
pub fn get(&self) -> Ref<'_, W> {
Ref::map(self.0.get(), |i| &i.widget)
}
pub fn get_mut(&self) -> RefMut<'_, W> {
let inner = self.0.get_mut();
let _ = inner.send.send(WidgetUpdate::Mutate(inner.id));
RefMut::map(inner, |i| &mut i.widget)
}
pub fn get_mut_quiet(&self) -> RefMut<'_, W> {
RefMut::map(self.0.get_mut(), |i| &mut i.widget)
}
pub fn get_label(&self) -> Ref<'_, String> {
Ref::map(self.0.get(), |i| &i.label)
}
pub fn set_label(&self, label: impl Into<String>) {
self.0.get_mut().label = label.into();
}
pub fn refs(&self) -> usize {
self.0.refs()
}
pub fn weak(&self) -> WeakWidgetRef<W> {
WeakWidgetRef(self.0.weak())
}
}
impl<W: ?Sized> WeakWidgetRef<W> {
/// should guarantee that widget is still valid to prevent indexing failures
pub(crate) fn expect_strong(&self) -> WidgetRef<W> {
WidgetRef(self.0.strong().expect("widget should not be dropped"))
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WeakWidgetRef<W> {
pub fn any(self) -> WeakWidgetRef<dyn Widget> {
WeakWidgetRef(self.0.clone())
}
}
impl<W: ?Sized> Drop for Inner<W> {
fn drop(&mut self) {
let _ = self.send.send(WidgetUpdate::Drop(self.id));
}
}
pub trait WidgetIdFn<W: ?Sized = dyn Widget>: FnOnce(&mut Ui) -> WidgetRef<W> {}
impl<W: ?Sized, F: FnOnce(&mut Ui) -> WidgetRef<W>> WidgetIdFn<W> for F {}
pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetRef {}
impl<F: FnOnce(&mut Ui) -> WidgetRef> WidgetRet for F {}
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static> WidgetLike<IdTag> for WidgetRef<W> {
type Widget = W;
fn add(self, _: &mut Ui) -> WidgetRef<W> {
self
}
}
impl<W: Widget + ?Sized + Unsize<dyn Widget> + 'static, F: FnOnce(&mut Ui) -> WidgetRef<W>>
WidgetLike<IdFnTag> for F
{
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetRef<W> {
self(ui)
}
}
pub trait IdLike<W> {
fn id(&self) -> Id;
}
impl<W> IdLike<W> for WidgetRef<W> {
fn id(&self) -> Id {
self.id()
}
}
impl<W: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<W> {}

View File

@@ -0,0 +1,79 @@
use super::*;
use std::marker::Unsize;
pub trait WidgetLike<Tag> {
type Widget: Widget + ?Sized + Unsize<dyn Widget> + 'static;
fn add(self, ui: &mut Ui) -> WidgetRef<Self::Widget>;
fn with_id<W2>(
self,
f: impl FnOnce(&mut Ui, WidgetRef<Self::Widget>) -> WidgetRef<W2>,
) -> impl WidgetIdFn<W2>
where
Self: Sized,
{
move |ui| {
let id = self.add(ui);
f(ui, id)
}
}
fn set_root(self, ui: &mut Ui)
where
Self: Sized,
{
ui.set_root(self);
}
}
pub struct WidgetArr<const LEN: usize> {
pub arr: [WidgetRef; LEN],
}
impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [WidgetRef; LEN]) -> Self {
Self { arr }
}
}
pub trait WidgetArrLike<const LEN: usize, Tag> {
fn ui(self, ui: &mut Ui) -> WidgetArr<LEN>;
}
impl<const LEN: usize> WidgetArrLike<LEN, ArrTag> for WidgetArr<LEN> {
fn ui(self, _: &mut Ui) -> WidgetArr<LEN> {
self
}
}
// I hate this language it's so bad why do I even use it
macro_rules! impl_widget_arr {
($n:expr;$($W:ident)*) => {
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
};
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
impl<$($W: WidgetLike<$Tag>,$Tag,)*> WidgetArrLike<$n, ($($Tag,)*)> for ($($W,)*) {
fn ui(self, ui: &mut Ui) -> WidgetArr<$n> {
#[allow(non_snake_case)]
let ($($W,)*) = self;
WidgetArr::new(
[$($W.add(ui),)*],
)
}
}
};
}
impl_widget_arr!(1;A);
impl_widget_arr!(2;A B);
impl_widget_arr!(3;A B C);
impl_widget_arr!(4;A B C D);
impl_widget_arr!(5;A B C D E);
impl_widget_arr!(6;A B C D E F);
impl_widget_arr!(7;A B C D E F G);
impl_widget_arr!(8;A B C D E F G H);
impl_widget_arr!(9;A B C D E F G H I);
impl_widget_arr!(10;A B C D E F G H I J);
impl_widget_arr!(11;A B C D E F G H I J K);
impl_widget_arr!(12;A B C D E F G H I J K L);

View File

@@ -0,0 +1,63 @@
mod handle;
mod like;
mod tag;
mod widgets;
pub use handle::*;
pub use like::*;
pub use tag::*;
pub use widgets::*;
use crate::layout::{Len, Painter, SizeCtx, Ui};
pub trait Widget: 'static {
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
}
impl Widget for () {
fn draw(&mut self, _: &mut Painter) {}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
}
/// A function that returns a widget given a UI.
/// Useful for defining trait functions on widgets that create a parent widget so that the children
/// don't need to be IDs yet
pub trait WidgetFn<W: Widget + ?Sized>: FnOnce(&mut Ui) -> W {}
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetFn<W> for F {}
impl<W: Widget, F: FnOnce(&mut Ui) -> W> WidgetLike<FnTag> for F {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetRef<W> {
self(ui).add(ui)
}
}
impl<W: Widget> WidgetLike<WidgetTag> for W {
type Widget = W;
fn add(self, ui: &mut Ui) -> WidgetRef<W> {
ui.add_widget(self)
}
}
pub trait WidgetOption {
fn get(self, ui: &mut Ui) -> Option<WidgetRef>;
}
impl WidgetOption for () {
fn get(self, _: &mut Ui) -> Option<WidgetRef> {
None
}
}
impl<F: FnOnce(&mut Ui) -> Option<WidgetRef>> WidgetOption for F {
fn get(self, ui: &mut Ui) -> Option<WidgetRef> {
self(ui)
}
}

View File

@@ -0,0 +1,5 @@
pub struct WidgetTag;
pub struct FnTag;
pub struct IdTag;
pub struct IdFnTag;
pub struct ArrTag;

View File

@@ -0,0 +1,50 @@
use std::sync::mpsc::Sender;
use crate::{
layout::{WeakWidgetRef, Widget, WidgetRef, WidgetUpdate},
util::{HashMap, Id, IdTracker},
};
pub struct Widgets {
ids: IdTracker,
map: HashMap<Id, WeakWidgetRef>,
send: Sender<WidgetUpdate>,
}
impl Widgets {
pub fn new(send: Sender<WidgetUpdate>) -> Self {
Self {
ids: IdTracker::default(),
map: HashMap::default(),
send,
}
}
pub fn get(&self, id: Id) -> WidgetRef {
self.map.get(&id).unwrap().expect_strong()
}
pub fn insert<W: Widget>(&mut self, widget: W) -> WidgetRef<W> {
let id = self.ids.next();
let rf = WidgetRef::new(id, widget, self.send.clone());
self.map.insert(id, rf.weak().any());
rf
}
pub fn delete(&mut self, id: Id) {
self.map.remove(&id);
self.ids.free(id);
}
pub fn reserve(&mut self) -> Id {
self.ids.next()
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
}

View File

@@ -11,26 +11,9 @@
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#![feature(unsize)] #![feature(unsize)]
#![feature(coerce_unsized)] #![feature(coerce_unsized)]
#![feature(option_into_flat_iter)]
mod attr;
mod event;
mod num;
mod orientation;
mod primitive;
mod render;
mod ui;
mod widget;
pub mod layout;
pub mod render;
pub mod util; pub mod util;
pub use attr::*; pub use image;
pub use event::*;
pub use num::*;
pub use orientation::*;
pub use primitive::*;
pub use render::*;
pub use ui::*;
pub use widget::*;
pub type UiColor = primitive::Color<u8>;

View File

@@ -1,4 +1,4 @@
use crate::{UiRegion, util::Id}; use crate::{layout::UiRegion, util::Id};
use wgpu::*; use wgpu::*;
#[repr(C)] #[repr(C)]

View File

@@ -1,7 +1,7 @@
use std::num::NonZero; use std::num::NonZero;
use crate::{ use crate::{
Ui, layout::Ui,
render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf}, render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf},
util::HashMap, util::HashMap,
}; };
@@ -61,10 +61,10 @@ impl UiRenderNode {
pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) { pub fn update(&mut self, device: &Device, queue: &Queue, ui: &mut Ui) {
self.active.clear(); self.active.clear();
for (i, primitives) in ui.layers.iter_mut() { for (i, primitives) in ui.data.layers.iter_mut() {
self.active.push(i); self.active.push(i);
for change in primitives.apply_free() { for change in primitives.apply_free() {
if let Some(inst) = ui.active.get_mut(&change.id) { if let Some(inst) = ui.data.active.get_mut(&change.id) {
for h in &mut inst.primitives { for h in &mut inst.primitives {
if h.layer == i && h.inst_idx == change.old { if h.layer == i && h.inst_idx == change.old {
h.inst_idx = change.new; h.inst_idx = change.new;
@@ -101,10 +101,10 @@ impl UiRenderNode {
} }
} }
let mut changed = false; let mut changed = false;
changed |= self.textures.update(&mut ui.textures); changed |= self.textures.update(&mut ui.data.textures);
if ui.masks.changed { if ui.data.masks.changed {
ui.masks.changed = false; ui.data.masks.changed = false;
self.masks.update(device, queue, &ui.masks[..]); self.masks.update(device, queue, &ui.data.masks[..]);
changed = true; changed = true;
} }
if changed { if changed {
@@ -183,7 +183,7 @@ impl UiRenderNode {
let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
label: Some("UI Shape Pipeline Layout"), label: Some("UI Shape Pipeline Layout"),
bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout], bind_group_layouts: &[&uniform_layout, &primitive_layout, &rsc_layout],
immediate_size: 0, push_constant_ranges: &[],
}); });
let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
label: Some("UI Shape Pipeline"), label: Some("UI Shape Pipeline"),
@@ -219,7 +219,7 @@ impl UiRenderNode {
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview_mask: None, multiview: None,
cache: None, cache: None,
}); });

View File

@@ -1,18 +1,19 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use crate::{ use crate::{
Color, UiRegion, WidgetId, layout::{Color, UiRegion},
render::{ render::{
ArrBuf, ArrBuf,
data::{MaskIdx, PrimitiveInstance}, data::{MaskIdx, PrimitiveInstance},
}, },
util::Id,
}; };
use bytemuck::Pod; use bytemuck::Pod;
use wgpu::*; use wgpu::*;
pub struct Primitives { pub struct Primitives {
instances: Vec<PrimitiveInstance>, instances: Vec<PrimitiveInstance>,
assoc: Vec<WidgetId>, assoc: Vec<Id>,
data: PrimitiveData, data: PrimitiveData,
free: Vec<usize>, free: Vec<usize>,
pub updated: bool, pub updated: bool,
@@ -98,7 +99,7 @@ macro_rules! primitives {
} }
pub struct PrimitiveInst<P> { pub struct PrimitiveInst<P> {
pub id: WidgetId, pub id: Id,
pub primitive: P, pub primitive: P,
pub region: UiRegion, pub region: UiRegion,
pub mask_idx: MaskIdx, pub mask_idx: MaskIdx,
@@ -174,7 +175,7 @@ impl Primitives {
} }
pub struct PrimitiveChange { pub struct PrimitiveChange {
pub id: WidgetId, pub id: Id,
pub old: usize, pub old: usize,
pub new: usize, pub new: usize,
} }

View File

@@ -1,7 +1,7 @@
use image::{DynamicImage, EncodableLayout}; use image::{DynamicImage, EncodableLayout};
use wgpu::{util::DeviceExt, *}; use wgpu::{util::DeviceExt, *};
use crate::{TextureUpdate, Textures}; use crate::layout::{TextureUpdate, Textures};
pub struct GpuTextures { pub struct GpuTextures {
device: Device, device: Device,

View File

@@ -1,14 +0,0 @@
use crate::{LayerId, MaskIdx, PrimitiveHandle, TextureHandle, UiRegion, WidgetId};
/// important non rendering data for retained drawing
#[derive(Debug)]
pub struct ActiveData {
pub id: WidgetId,
pub region: UiRegion,
pub parent: Option<WidgetId>,
pub textures: Vec<TextureHandle>,
pub primitives: Vec<PrimitiveHandle>,
pub children: Vec<WidgetId>,
pub mask: MaskIdx,
pub layer: LayerId,
}

View File

@@ -1,18 +0,0 @@
use crate::{BothAxis, Len, UiVec2, WidgetId, util::HashMap};
#[derive(Default)]
pub struct Cache {
pub size: BothAxis<HashMap<WidgetId, (UiVec2, Len)>>,
}
impl Cache {
pub fn remove(&mut self, id: WidgetId) {
self.size.x.remove(&id);
self.size.y.remove(&id);
}
pub fn clear(&mut self) {
self.size.x.clear();
self.size.y.clear();
}
}

View File

@@ -1,232 +0,0 @@
use crate::{
ActiveData, Axis, EventsLike, Painter, SizeCtx, Ui, UiRegion, UiVec2, WidgetId,
render::MaskIdx,
util::{HashSet, forget_ref},
};
use std::ops::{Deref, DerefMut};
/// state maintained between widgets during painting
pub struct DrawState<'a> {
pub(super) ui: &'a mut Ui,
pub(super) events: &'a mut dyn EventsLike,
draw_started: HashSet<WidgetId>,
}
impl<'a> DrawState<'a> {
pub fn new(ui: &'a mut Ui, events: &'a mut dyn EventsLike) -> Self {
Self {
ui,
events,
draw_started: Default::default(),
}
}
pub fn redraw_updates(&mut self) {
while let Some(&id) = self.widgets.needs_redraw.iter().next() {
self.redraw(id);
}
self.ui.free(self.events);
}
/// redraws a widget that's currently active (drawn)
pub fn redraw(&mut self, id: WidgetId) {
self.widgets.needs_redraw.remove(&id);
self.draw_started.remove(&id);
// check if parent depends on the desired size of this, if so then redraw it first
for axis in [Axis::X, Axis::Y] {
if let Some(&(outer, old)) = self.cache.size.axis_dyn(axis).get(&id)
&& let Some(current) = self.active.get(&id)
&& let Some(pid) = current.parent
{
self.cache.size.axis_dyn(axis).remove(&id);
let new = self.size_ctx(id, outer).len_axis(id, axis);
self.cache.size.axis_dyn(axis).insert(id, (outer, new));
if new != old {
self.redraw(pid);
}
}
}
if self.draw_started.contains(&id) {
return;
}
let Some(active) = self.remove(id, false) else {
return;
};
self.draw_inner(
active.layer,
id,
active.region,
active.parent,
active.mask,
Some(active.children),
);
}
pub(super) fn size_ctx<'b>(&'b mut self, source: WidgetId, outer: UiVec2) -> SizeCtx<'b> {
SizeCtx {
source,
cache: &mut self.ui.cache,
text: &mut self.ui.text,
textures: &mut self.ui.textures,
widgets: &self.ui.widgets,
outer,
output_size: self.ui.output_size,
id: source,
}
}
pub fn redraw_all(&mut self) {
// free all resources & cache
for (_, active) in self.ui.active.drain() {
self.events.undraw(&active);
}
self.ui.cache.clear();
self.ui.free(self.events);
self.layers.clear();
self.widgets.needs_redraw.clear();
if let Some(id) = &self.ui.root {
self.draw_inner(0, id.id(), UiRegion::FULL, None, MaskIdx::NONE, None);
}
}
pub(super) fn draw_inner(
&mut self,
layer: usize,
id: WidgetId,
region: UiRegion,
parent: Option<WidgetId>,
mask: MaskIdx,
old_children: Option<Vec<WidgetId>>,
) {
let mut old_children = old_children.unwrap_or_default();
if let Some(active) = self.ui.active.get_mut(&id)
&& !self.ui.widgets.needs_redraw.contains(&id)
{
// check to see if we can skip drawing first
if active.region == region {
return;
} else if active.region.size() == region.size() {
// TODO: epsilon?
let from = active.region;
self.mov(id, from, region);
return;
}
// if not, then maintain resize and track old children to remove unneeded
let active = self.remove(id, false).unwrap();
old_children = active.children;
}
// draw widget
self.draw_started.insert(id);
let mut painter = Painter {
state: self,
region,
mask,
layer,
id,
textures: Vec::new(),
primitives: Vec::new(),
children: Vec::new(),
};
let mut widget = painter.state.widgets.get_dyn_dynamic(id);
widget.draw(&mut painter);
drop(widget);
let Painter {
state: _,
region,
mask,
textures,
primitives,
children,
layer,
id,
} = painter;
// add to active
let active = ActiveData {
id,
region,
parent,
textures,
primitives,
children,
mask,
layer,
};
// remove old children that weren't kept
for c in &old_children {
if !active.children.contains(c) {
self.remove_rec(*c);
}
}
// update modules
self.events.draw(&active);
self.active.insert(id, active);
}
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
let active = self.ui.active.get_mut(&id).unwrap();
for h in &active.primitives {
let region = self.ui.layers[h.layer].region_mut(h);
*region = region.outside(&from).within(&to);
}
active.region = active.region.outside(&from).within(&to);
// SAFETY: children cannot be recursive
let children = unsafe { forget_ref(&active.children) };
for child in children {
self.mov(*child, from, to);
}
}
/// NOTE: instance textures are cleared and self.textures freed
fn remove(&mut self, id: WidgetId, undraw: bool) -> Option<ActiveData> {
let mut active = self.active.remove(&id);
if let Some(active) = &mut active {
for h in &active.primitives {
let mask = self.layers.free(h);
if mask != MaskIdx::NONE {
self.masks.remove(mask);
}
}
active.textures.clear();
self.textures.free();
if undraw {
self.events.undraw(active);
}
}
active
}
fn remove_rec(&mut self, id: WidgetId) -> Option<ActiveData> {
self.cache.remove(id);
let inst = self.remove(id, true);
if let Some(inst) = &inst {
for c in &inst.children {
self.remove_rec(*c);
}
}
inst
}
}
impl Deref for DrawState<'_> {
type Target = Ui;
fn deref(&self) -> &Self::Target {
self.ui
}
}
impl DerefMut for DrawState<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.ui
}
}

View File

@@ -1,211 +0,0 @@
use crate::{
EventsLike, IdLike, Mask, PixelRegion, PrimitiveLayers, TextData, TextureHandle, Textures,
Widget, WidgetHandle, WidgetId, Widgets,
ui::draw_state::DrawState,
util::{HashMap, TrackedArena, Vec2},
};
use image::DynamicImage;
use std::{
ops::{Index, IndexMut},
sync::mpsc::{Receiver, channel},
};
mod active;
mod cache;
mod draw_state;
mod painter;
mod size;
mod state;
pub use active::*;
use cache::*;
pub use painter::Painter;
pub use size::*;
pub struct Ui {
// TODO: edit visibilities
pub widgets: Widgets,
// retained painter state
pub active: HashMap<WidgetId, ActiveData>,
pub layers: PrimitiveLayers,
pub textures: Textures,
pub text: TextData,
output_size: Vec2,
pub masks: TrackedArena<Mask, u32>,
pub cache: Cache,
pub root: Option<WidgetHandle>,
old_root: Option<WidgetId>,
recv: Receiver<WidgetId>,
resized: bool,
}
pub trait HasUi: Sized {
fn ui(&self) -> &Ui;
fn ui_mut(&mut self) -> &mut Ui;
}
impl HasUi for Ui {
fn ui(&self) -> &Ui {
self
}
fn ui_mut(&mut self) -> &mut Ui {
self
}
}
impl Ui {
/// useful for debugging
pub fn set_label(&mut self, id: impl IdLike, label: String) {
self.widgets.data_mut(id.id()).unwrap().label = label;
}
pub fn label(&self, id: impl IdLike) -> &String {
&self.widgets.data(id.id()).unwrap().label
}
pub fn new() -> Self {
Self::default()
}
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
where
I::Widget: Sized + Widget,
{
self.widgets.get(id)
}
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
where
I::Widget: Sized + Widget,
{
self.widgets.get_mut(id)
}
pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle {
self.textures.add(image)
}
pub fn resize(&mut self, size: impl Into<Vec2>) {
self.output_size = size.into();
self.resized = true;
}
pub fn update(&mut self, events: &mut dyn EventsLike) {
if !self.widgets.waiting.is_empty() {
let len = self.widgets.waiting.len();
let all: Vec<_> = self
.widgets
.waiting
.iter()
.map(|&w| format!("'{}' ({w:?})", self.label(w)))
.collect();
panic!(
"{len} widget(s) were never upgraded\n\
this is likely a memory leak; consider upgrading to strong if you plan on using it later\n\
weak widgets: {all:#?}"
);
}
if self.root_changed() {
DrawState::new(self, events).redraw_all();
self.old_root = self.root.as_ref().map(|r| r.id());
} else if self.widgets.has_updates() {
DrawState::new(self, events).redraw_updates();
}
if self.resized {
self.resized = false;
DrawState::new(self, events).redraw_all();
}
}
/// free any resources that don't have references anymore
fn free(&mut self, events: &mut dyn EventsLike) {
for id in self.recv.try_iter() {
events.remove(id);
self.widgets.delete(id);
}
self.textures.free();
}
pub fn root_changed(&self) -> bool {
self.root.as_ref().map(|r| r.id()) != self.old_root
}
pub fn needs_redraw(&self) -> bool {
self.root_changed() || self.widgets.has_updates()
}
pub fn num_widgets(&self) -> usize {
self.widgets.len()
}
pub fn active_widgets(&self) -> usize {
self.active.len()
}
pub fn debug_layers(&self) {
for ((idx, depth), primitives) in self.layers.iter_depth() {
let indent = " ".repeat(depth * 2);
let len = primitives.instances().len();
print!("{indent}{idx}: {len} primitives");
if len >= 1 {
print!(" ({})", primitives.instances()[0].binding);
}
println!();
}
}
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
let region = self.active.get(&id.id())?.region;
Some(region.to_px(self.output_size))
}
pub fn debug(&self, label: &str) -> impl Iterator<Item = &ActiveData> {
self.active.iter().filter_map(move |(&id, inst)| {
let l = self.widgets.label(id);
if l == label { Some(inst) } else { None }
})
}
}
impl<I: IdLike> Index<I> for Ui
where
I::Widget: Sized + Widget,
{
type Output = I::Widget;
fn index(&self, id: I) -> &Self::Output {
self.get(&id).unwrap()
}
}
impl<I: IdLike> IndexMut<I> for Ui
where
I::Widget: Sized + Widget,
{
fn index_mut(&mut self, id: I) -> &mut Self::Output {
self.get_mut(&id).unwrap()
}
}
impl Default for Ui {
fn default() -> Self {
let (send, recv) = channel();
Self {
widgets: Widgets::new(send),
active: Default::default(),
layers: Default::default(),
masks: Default::default(),
text: Default::default(),
textures: Default::default(),
cache: Default::default(),
output_size: Vec2::ZERO,
root: None,
old_root: None,
recv,
resized: false,
}
}
}

View File

@@ -1,140 +0,0 @@
use crate::{
Axis, Len, RenderedText, Size, SizeCtx, TextAttrs, TextBuffer, TextData, TextureHandle,
UiRegion, Widget, WidgetHandle, WidgetId,
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
ui::draw_state::DrawState,
util::Vec2,
};
/// makes your surfaces look pretty
pub struct Painter<'a, 'b> {
pub(super) state: &'a mut DrawState<'b>,
pub(super) region: UiRegion,
pub(super) mask: MaskIdx,
pub(super) textures: Vec<TextureHandle>,
pub(super) primitives: Vec<PrimitiveHandle>,
pub(super) children: Vec<WidgetId>,
pub layer: usize,
pub(super) id: WidgetId,
}
impl<'a, 'c> Painter<'a, 'c> {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
let h = self.state.layers.write(
self.layer,
PrimitiveInst {
id: self.id,
primitive,
region,
mask_idx: self.mask,
},
);
if self.mask != MaskIdx::NONE {
// TODO: I have no clue if this works at all :joy:
self.state.masks.push_ref(self.mask);
}
self.primitives.push(h);
}
/// Writes a primitive to be rendered
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
self.primitive_at(primitive, self.region)
}
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
self.primitive_at(primitive, region.within(&self.region));
}
pub fn set_mask(&mut self, region: UiRegion) {
assert!(self.mask == MaskIdx::NONE);
self.mask = self.state.masks.push(Mask { region });
}
/// Draws a widget within this widget's region.
pub fn widget<W: ?Sized>(&mut self, id: &WidgetHandle<W>) {
self.widget_at(id, self.region);
}
/// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas.
pub fn widget_within<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
self.widget_at(id, region.within(&self.region));
}
fn widget_at<W: ?Sized>(&mut self, id: &WidgetHandle<W>, region: UiRegion) {
self.children.push(id.id());
self.state
.draw_inner(self.layer, id.id(), region, Some(self.id), self.mask, None);
}
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);
}
/// returns (handle, offset from top left)
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.state
.ui
.text
.draw(buffer, attrs, &mut self.state.ui.textures)
}
pub fn region(&self) -> UiRegion {
self.region
}
pub fn size<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<W>) -> Size {
self.size_ctx().size(id)
}
pub fn len_axis<W: ?Sized + Widget>(&mut self, id: &WidgetHandle<W>, axis: Axis) -> Len {
match axis {
Axis::X => self.size_ctx().width(id),
Axis::Y => self.size_ctx().height(id),
}
}
pub fn output_size(&self) -> Vec2 {
self.state.output_size
}
pub fn px_size(&mut self) -> Vec2 {
self.region.size().to_abs(self.state.output_size)
}
pub fn text_data(&mut self) -> &mut TextData {
&mut self.state.text
}
pub fn child_layer(&mut self) {
self.layer = self.state.layers.child(self.layer);
}
pub fn next_layer(&mut self) {
self.layer = self.state.layers.next(self.layer);
}
pub fn label(&self) -> &str {
&self.state.widgets.data(self.id).unwrap().label
}
pub fn id(&self) -> &WidgetId {
&self.id
}
pub fn size_ctx(&mut self) -> SizeCtx<'_> {
self.state.size_ctx(self.id, self.region.size())
}
}

View File

@@ -1,86 +0,0 @@
use crate::{
Axis, AxisT, IdLike, Len, RenderedText, Size, TextAttrs, TextBuffer, TextData, Textures,
UiVec2, WidgetAxisFns, WidgetId, Widgets, XAxis, YAxis, ui::cache::Cache, util::Vec2,
};
pub struct SizeCtx<'a> {
pub text: &'a mut TextData,
pub textures: &'a mut Textures,
pub(super) source: WidgetId,
pub(super) widgets: &'a Widgets,
pub(super) cache: &'a mut Cache,
/// TODO: should this be pub? rn used for sized
pub outer: UiVec2,
pub(super) output_size: Vec2,
pub(super) id: WidgetId,
}
impl SizeCtx<'_> {
pub fn id(&self) -> &WidgetId {
&self.id
}
pub fn source(&self) -> &WidgetId {
&self.source
}
pub(super) fn len_inner<A: const AxisT>(&mut self, id: WidgetId) -> Len {
if let Some((_, len)) = self.cache.size.axis::<A>().get(&id) {
return *len;
}
let len = self
.widgets
.get_dyn_dynamic(id)
.desired_len::<A>(&mut SizeCtx {
text: self.text,
textures: self.textures,
source: self.source,
widgets: self.widgets,
cache: self.cache,
outer: self.outer,
output_size: self.output_size,
id,
});
self.cache.size.axis::<A>().insert(id, (self.outer, len));
len
}
pub fn width(&mut self, id: impl IdLike) -> Len {
self.len_inner::<XAxis>(id.id())
}
pub fn height(&mut self, id: impl IdLike) -> Len {
self.len_inner::<YAxis>(id.id())
}
pub fn len_axis(&mut self, id: impl IdLike, axis: Axis) -> Len {
match axis {
Axis::X => self.width(id),
Axis::Y => self.height(id),
}
}
pub fn size(&mut self, id: impl IdLike) -> Size {
let id = id.id();
Size {
x: self.width(id),
y: self.height(id),
}
}
pub fn px_size(&mut self) -> Vec2 {
self.outer.to_abs(self.output_size)
}
pub fn output_size(&mut self) -> Vec2 {
self.output_size
}
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
self.text.draw(buffer, attrs, self.textures)
}
pub fn label(&self, id: WidgetId) -> &String {
self.widgets.label(id)
}
}

View File

@@ -1,3 +0,0 @@
// pub struct DynState {
//
// }

View File

@@ -1,35 +0,0 @@
use std::ops::{Deref, DerefMut};
pub struct DynBorrower<'a, T: ?Sized> {
data: &'a mut T,
borrowed: &'a mut bool,
}
impl<'a, T: ?Sized> DynBorrower<'a, T> {
pub fn new(data: &'a mut T, borrowed: &'a mut bool) -> Self {
if *borrowed {
panic!("tried to mutably borrow the same thing twice");
}
Self { data, borrowed }
}
}
impl<T: ?Sized> Drop for DynBorrower<'_, T> {
fn drop(&mut self) {
*self.borrowed = false;
}
}
impl<T: ?Sized> Deref for DynBorrower<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data
}
}
impl<T: ?Sized> DerefMut for DynBorrower<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.data
}
}

60
core/src/util/handle.rs Normal file
View File

@@ -0,0 +1,60 @@
use std::{
cell::{Ref, RefCell, RefMut},
marker::Unsize,
ops::CoerceUnsized,
rc::{Rc, Weak},
};
pub struct Handle<T: ?Sized>(Rc<RefCell<T>>);
pub struct WeakHandle<T: ?Sized>(Weak<RefCell<T>>);
impl<T: ?Sized> Handle<T> {
pub fn get(&self) -> Ref<'_, T> {
self.0.borrow()
}
pub fn get_mut(&self) -> RefMut<'_, T> {
self.0.borrow_mut()
}
pub fn refs(&self) -> usize {
Rc::strong_count(&self.0)
}
pub fn weak(&self) -> WeakHandle<T> {
WeakHandle(Rc::downgrade(&self.0))
}
}
impl<T: ?Sized> WeakHandle<T> {
pub fn strong(&self) -> Option<Handle<T>> {
Some(Handle(self.0.upgrade()?))
}
}
impl<T: ?Sized> Clone for Handle<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: ?Sized> Clone for WeakHandle<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Default> Default for Handle<T> {
fn default() -> Self {
Self(Default::default())
}
}
impl<T> From<T> for Handle<T> {
fn from(value: T) -> Self {
Self(Rc::new(RefCell::new(value)))
}
}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Handle<U>> for Handle<T> {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WeakHandle<U>> for WeakHandle<T> {}

View File

@@ -1,23 +1,17 @@
mod arena; mod arena;
mod borrow;
mod change; mod change;
mod handle;
mod id; mod id;
mod math; mod math;
mod refcount; mod refcount;
mod slot;
mod trust;
mod typemap;
mod vec2; mod vec2;
pub use arena::*; pub use arena::*;
pub use borrow::*;
pub use change::*; pub use change::*;
pub use handle::*;
pub use id::*; pub use id::*;
pub use math::*; pub use math::*;
pub use refcount::*; pub use refcount::*;
pub use slot::*;
pub use trust::*;
pub use typemap::*;
pub use vec2::*; pub use vec2::*;
pub type HashMap<K, V> = fxhash::FxHashMap<K, V>; pub type HashMap<K, V> = fxhash::FxHashMap<K, V>;

View File

@@ -10,6 +10,7 @@ impl RefCounter {
pub fn new() -> Self { pub fn new() -> Self {
Self(Arc::new(0.into())) Self(Arc::new(0.into()))
} }
#[allow(unused)]
pub fn refs(&self) -> u32 { pub fn refs(&self) -> u32 {
self.0.load(Ordering::Acquire) self.0.load(Ordering::Acquire)
} }
@@ -17,17 +18,12 @@ impl RefCounter {
let refs = self.0.fetch_sub(1, Ordering::Release); let refs = self.0.fetch_sub(1, Ordering::Release);
refs == 0 refs == 0
} }
#[allow(unused)]
pub fn quiet_clone(&self) -> Self { pub fn quiet_clone(&self) -> Self {
Self(self.0.clone()) Self(self.0.clone())
} }
} }
impl Default for RefCounter {
fn default() -> Self {
Self::new()
}
}
impl Clone for RefCounter { impl Clone for RefCounter {
fn clone(&self) -> Self { fn clone(&self) -> Self {
self.0.fetch_add(1, Ordering::Release); self.0.fetch_add(1, Ordering::Release);

View File

@@ -1,69 +0,0 @@
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct SlotId {
idx: u32,
genr: u32,
}
pub struct SlotVec<T> {
data: Vec<(u32, Option<T>)>,
free: Vec<u32>,
}
impl<T> SlotVec<T> {
pub fn new() -> Self {
Self {
data: Default::default(),
free: Default::default(),
}
}
pub fn add(&mut self, x: T) -> SlotId {
if let Some(idx) = self.free.pop() {
let (genr, data) = &mut self.data[idx as usize];
*data = Some(x);
SlotId { idx, genr: *genr }
} else {
let idx = self.data.len() as u32;
let genr = 0;
self.data.push((genr, Some(x)));
SlotId { idx, genr }
}
}
pub fn free(&mut self, id: SlotId) {
let (genr, data) = &mut self.data[id.idx as usize];
*genr += 1;
*data = None;
self.free.push(id.idx);
}
pub fn get(&self, id: SlotId) -> Option<&T> {
let slot = &self.data[id.idx as usize];
if slot.0 != id.genr {
return None;
}
slot.1.as_ref()
}
pub fn get_mut(&mut self, id: SlotId) -> Option<&mut T> {
let slot = &mut self.data[id.idx as usize];
if slot.0 != id.genr {
return None;
}
slot.1.as_mut()
}
pub fn len(&self) -> usize {
self.data.len() - self.free.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<T> Default for SlotVec<T> {
fn default() -> Self {
Self::new()
}
}

View File

@@ -1,17 +0,0 @@
#[allow(clippy::missing_safety_doc)]
pub unsafe fn forget_ref<'a, T>(x: &T) -> &'a T {
unsafe { std::mem::transmute::<&T, &T>(x) }
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn forget_mut<'a, T>(x: &mut T) -> &'a mut T {
unsafe { std::mem::transmute::<&mut T, &mut T>(x) }
}
#[allow(clippy::mut_from_ref, clippy::missing_safety_doc)]
pub unsafe fn to_mut<T>(x: &T) -> &mut T {
#[allow(mutable_transmutes)]
unsafe {
std::mem::transmute::<&T, &mut T>(x)
}
}

View File

@@ -1,56 +0,0 @@
use crate::util::HashMap;
use std::{
any::TypeId,
marker::Unsize,
ops::{Deref, DerefMut},
};
pub struct TypeMap<Trait: ?Sized> {
map: HashMap<TypeId, Box<Trait>>,
}
impl<Trait: ?Sized> TypeMap<Trait> {
pub fn set_type<T: Unsize<Trait> + 'static>(&mut self, val: T) {
self.map
.insert(TypeId::of::<T>(), Box::new(val) as Box<Trait>);
}
pub fn type_mut<T: Unsize<Trait> + 'static>(&mut self) -> Option<&mut T> {
Some(Self::convert_mut(self.map.get_mut(&TypeId::of::<T>())?))
}
pub fn type_or_default<T: Default + Unsize<Trait> + 'static>(&mut self) -> &mut T {
Self::convert_mut(
self.map
.entry(TypeId::of::<T>())
.or_insert(Box::new(T::default()) as Box<Trait>),
)
}
fn convert_mut<T: Unsize<Trait>>(entry: &mut Box<Trait>) -> &mut T {
// allegedly this is just what Any does...
unsafe { &mut *(entry.as_mut() as *mut Trait as *mut T) }
}
}
impl<T: ?Sized> Deref for TypeMap<T> {
type Target = HashMap<TypeId, Box<T>>;
fn deref(&self) -> &Self::Target {
&self.map
}
}
impl<T: ?Sized> DerefMut for TypeMap<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.map
}
}
impl<T: ?Sized> Default for TypeMap<T> {
fn default() -> Self {
Self {
map: Default::default(),
}
}
}

View File

@@ -1,22 +0,0 @@
use crate::Widget;
pub struct WidgetData {
pub widget: Box<dyn Widget>,
pub label: String,
/// dynamic borrow checking
pub borrowed: bool,
}
impl WidgetData {
pub fn new<W: Widget>(widget: W) -> Self {
let mut label = std::any::type_name::<W>().to_string();
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
}
Self {
widget: Box::new(widget),
label,
borrowed: false,
}
}
}

View File

@@ -1,174 +0,0 @@
use std::{marker::Unsize, ops::CoerceUnsized, sync::mpsc::Sender};
use crate::{
HasUi, Widget,
util::{RefCounter, SlotId},
};
pub type WidgetId = SlotId;
/// An identifier for a widget that can index a UI or event ctx to get it.
/// This is a strong handle that does not impl Clone, and when it is dropped,
/// a signal is sent to the owning UI to clean up the resources.
///
/// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs?
pub struct WidgetHandle<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId,
counter: RefCounter,
send: Sender<WidgetId>,
ty: *const W,
}
/// A weak handle to a widget.
/// Will not keep it alive, but can still be used for indexing like WidgetHandle.
pub struct WidgetRef<W: ?Sized = dyn Widget> {
pub(super) id: WidgetId,
#[allow(unused)]
ty: *const W,
}
pub struct WidgetHandles<W: ?Sized = dyn Widget> {
pub h: WidgetHandle<W>,
pub r: WidgetRef<W>,
}
impl<W: Widget + ?Sized + Unsize<dyn Widget>> WidgetHandle<W> {
pub fn any(self) -> WidgetHandle<dyn Widget> {
self
}
}
impl<W: ?Sized> WidgetHandle<W> {
pub(crate) fn new(id: WidgetId, send: Sender<WidgetId>) -> Self {
Self {
id,
counter: RefCounter::new(),
send,
ty: null_ptr(),
}
}
pub fn id(&self) -> WidgetId {
self.id
}
pub fn refs(&self) -> u32 {
self.counter.refs()
}
pub fn weak(&self) -> WidgetRef<W> {
let Self { ty, id, .. } = *self;
WidgetRef { ty, id }
}
pub fn handles(self) -> WidgetHandles<W> {
let r = self.weak();
WidgetHandles { h: self, r }
}
}
impl<W: ?Sized> WidgetRef<W> {
pub(crate) fn new(id: WidgetId) -> Self {
Self { id, ty: null_ptr() }
}
pub fn id(&self) -> WidgetId {
self.id
}
#[track_caller]
pub fn upgrade(self, ui: &mut impl HasUi) -> WidgetHandle<W> {
ui.ui_mut().widgets.upgrade(self)
}
}
impl<W: ?Sized> Drop for WidgetHandle<W> {
fn drop(&mut self) {
if self.counter.drop() {
let _ = self.send.send(self.id);
}
}
}
pub trait WidgetIdFn<Rsc, W: ?Sized = dyn Widget>: FnOnce(&mut Rsc) -> WidgetRef<W> {}
impl<Rsc, W: ?Sized, F: FnOnce(&mut Rsc) -> WidgetRef<W>> WidgetIdFn<Rsc, W> for F {}
pub trait IdLike {
type Widget: ?Sized;
fn id(&self) -> WidgetId;
}
impl<W: ?Sized> IdLike for &WidgetHandle<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: ?Sized> IdLike for WidgetHandle<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl<W: ?Sized> IdLike for WidgetRef<W> {
type Widget = W;
fn id(&self) -> WidgetId {
self.id
}
}
impl IdLike for WidgetId {
type Widget = dyn Widget;
fn id(&self) -> WidgetId {
*self
}
}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetHandle<U>> for WidgetHandle<T> {}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<WidgetRef<U>> for WidgetRef<T> {}
impl<W: ?Sized> Clone for WidgetRef<W> {
fn clone(&self) -> Self {
*self
}
}
impl<W: ?Sized> Copy for WidgetRef<W> {}
impl<W: ?Sized> PartialEq for WidgetRef<W> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<W> PartialEq for WidgetHandle<W> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<W> std::fmt::Debug for WidgetHandle<W> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.id.fmt(f)
}
}
impl<'a, W: Widget + 'a, State: HasUi> FnOnce<(&'a mut State,)> for WidgetRef<W> {
type Output = &'a mut W;
extern "rust-call" fn call_once(self, args: (&'a mut State,)) -> Self::Output {
&mut args.0.ui_mut()[self]
}
}
fn null_ptr<W: ?Sized>() -> *const W {
if size_of::<&W>() == size_of::<*const dyn Widget>() {
let w: *const dyn Widget = &();
unsafe { std::mem::transmute_copy(&w) }
} else {
unsafe { std::mem::transmute_copy(&[0usize; 1]) }
}
}
unsafe impl<W: ?Sized> Send for WidgetRef<W> {}
unsafe impl<W: ?Sized> Sync for WidgetRef<W> {}

View File

@@ -1,76 +0,0 @@
use crate::HasUi;
use super::*;
use std::marker::Unsize;
pub trait WidgetLike<Rsc: HasUi, Tag>: Sized {
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
fn add(self, rsc: &mut Rsc) -> WidgetRef<Self::Widget>;
fn add_strong(self, rsc: &mut Rsc) -> WidgetHandle<Self::Widget> {
self.add(rsc).upgrade(rsc.ui_mut())
}
fn with_id<W2>(
self,
f: impl FnOnce(&mut Rsc, WidgetRef<Self::Widget>) -> WidgetRef<W2>,
) -> impl WidgetIdFn<Rsc, W2> {
move |state| {
let id = self.add(state);
f(state, id)
}
}
fn set_root(self, state: &mut Rsc) {
let id = self.add(state);
let ui = state.ui_mut();
ui.root = Some(id.upgrade(ui));
}
fn handles(self, state: &mut Rsc) -> WidgetHandles<Self::Widget> {
self.add(state).upgrade(state.ui_mut()).handles()
}
}
pub trait WidgetArrLike<Rsc, const LEN: usize, Tag> {
#[track_caller]
fn add(self, state: &mut Rsc) -> WidgetArr<LEN>;
}
impl<Rsc, const LEN: usize> WidgetArrLike<Rsc, LEN, ArrTag> for WidgetArr<LEN> {
fn add(self, _: &mut Rsc) -> WidgetArr<LEN> {
self
}
}
// variadic generics please save us
macro_rules! impl_widget_arr {
($n:expr;$($W:ident)*) => {
impl_widget_arr!($n;$($W)*;$(${concat($W,Tag)})*);
};
($n:expr;$($W:ident)*;$($Tag:ident)*) => {
impl<Rsc: HasUi, $($W: WidgetLike<Rsc, $Tag>,$Tag,)*> WidgetArrLike<Rsc, $n, ($($Tag,)*)> for ($($W,)*) {
fn add(self, rsc: &mut Rsc) -> WidgetArr<$n> {
#[allow(non_snake_case)]
let ($($W,)*) = self;
WidgetArr::new(
[$($W.add(rsc).upgrade(rsc.ui_mut()),)*],
)
}
}
};
}
impl_widget_arr!(1;A);
impl_widget_arr!(2;A B);
impl_widget_arr!(3;A B C);
impl_widget_arr!(4;A B C D);
impl_widget_arr!(5;A B C D E);
impl_widget_arr!(6;A B C D E F);
impl_widget_arr!(7;A B C D E F G);
impl_widget_arr!(8;A B C D E F G H);
impl_widget_arr!(9;A B C D E F G H I);
impl_widget_arr!(10;A B C D E F G H I J);
impl_widget_arr!(11;A B C D E F G H I J K);
impl_widget_arr!(12;A B C D E F G H I J K L);

View File

@@ -1,87 +0,0 @@
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
use std::any::Any;
mod data;
mod handle;
mod like;
mod tag;
mod view;
mod widgets;
pub use data::*;
pub use handle::*;
pub use like::*;
pub use tag::*;
pub use view::*;
pub use widgets::*;
pub trait Widget: Any {
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
}
pub trait WidgetAxisFns {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len;
}
impl<W: Widget + ?Sized> WidgetAxisFns for W {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len {
match A::get() {
Axis::X => self.desired_width(ctx),
Axis::Y => self.desired_height(ctx),
}
}
}
impl Widget for () {
fn draw(&mut self, _: &mut Painter) {}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
}
}
impl dyn Widget {
pub fn as_any(&self) -> &dyn Any {
self
}
pub fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
/// A function that returns a widget given a UI.
/// Useful for defining trait functions on widgets that create a parent widget so that the children
/// don't need to be IDs yet
pub trait WidgetFn<State, W: Widget + ?Sized>: FnOnce(&mut State) -> W {}
impl<State, W: Widget + ?Sized, F: FnOnce(&mut State) -> W> WidgetFn<State, W> for F {}
pub struct WidgetArr<const LEN: usize> {
pub arr: [WidgetHandle; LEN],
}
impl<const LEN: usize> WidgetArr<LEN> {
pub fn new(arr: [WidgetHandle; LEN]) -> Self {
Self { arr }
}
}
pub trait WidgetOption<State> {
fn get(self, state: &mut State) -> Option<WidgetHandle>;
}
impl<State> WidgetOption<State> for () {
fn get(self, _: &mut State) -> Option<WidgetHandle> {
None
}
}
impl<State, F: FnOnce(&mut State) -> Option<WidgetHandle>> WidgetOption<State> for F {
fn get(self, state: &mut State) -> Option<WidgetHandle> {
self(state)
}
}

View File

@@ -1,60 +0,0 @@
use super::*;
use crate::HasUi;
use std::marker::Unsize;
pub struct WidgetTag;
impl<Rsc: HasUi, W: Widget> WidgetLike<Rsc, WidgetTag> for W {
type Widget = W;
fn add(self, rsc: &mut Rsc) -> WidgetRef<W> {
rsc.ui_mut().widgets.add_weak(self)
}
}
pub struct FnTag;
impl<Rsc: HasUi, W: Widget, F: FnOnce(&mut Rsc) -> W> WidgetLike<Rsc, FnTag> for F {
type Widget = W;
fn add(self, rsc: &mut Rsc) -> WidgetRef<W> {
self(rsc).add(rsc)
}
}
pub trait WidgetFnTrait<Rsc> {
type Widget: Widget;
fn run(self, rsc: &mut Rsc) -> Self::Widget;
}
pub struct FnTraitTag;
impl<Rsc: HasUi, T: WidgetFnTrait<Rsc>> WidgetLike<Rsc, FnTraitTag> for T {
type Widget = T::Widget;
#[track_caller]
fn add(self, rsc: &mut Rsc) -> WidgetRef<T::Widget> {
self.run(rsc).add(rsc)
}
}
pub struct RefTag;
impl<Rsc: HasUi, W: ?Sized + Widget + Unsize<dyn Widget>> WidgetLike<Rsc, RefTag> for WidgetRef<W> {
type Widget = W;
fn add(self, _: &mut Rsc) -> WidgetRef<W> {
self
}
}
pub struct RefFnTag;
impl<Rsc: HasUi, W: ?Sized + Widget + Unsize<dyn Widget>, F: FnOnce(&mut Rsc) -> WidgetRef<W>>
WidgetLike<Rsc, RefFnTag> for F
{
type Widget = W;
fn add(self, rsc: &mut Rsc) -> WidgetRef<W> {
self(rsc)
}
}
pub struct ViewTag;
impl<Rsc: HasUi, V: WidgetView> WidgetLike<Rsc, ViewTag> for V {
type Widget = V::Widget;
fn add(self, _: &mut Rsc) -> WidgetRef<Self::Widget> {
self.root()
}
}
pub struct ArrTag;

View File

@@ -1,16 +0,0 @@
use std::marker::Unsize;
use crate::{Widget, WidgetRef};
pub trait WidgetView {
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
fn root(&self) -> WidgetRef<Self::Widget>;
}
pub trait HasWidget {
type Widget: Widget + ?Sized + Unsize<dyn Widget>;
}
impl<W: Widget + Unsize<dyn Widget> + ?Sized> HasWidget for WidgetRef<W> {
type Widget = W;
}

View File

@@ -1,109 +0,0 @@
use std::sync::mpsc::Sender;
use crate::{
IdLike, Widget, WidgetData, WidgetHandle, WidgetId, WidgetRef,
util::{DynBorrower, HashSet, SlotVec, forget_mut, to_mut},
};
pub struct Widgets {
pub needs_redraw: HashSet<WidgetId>,
vec: SlotVec<WidgetData>,
send: Sender<WidgetId>,
pub(crate) waiting: HashSet<WidgetId>,
}
impl Widgets {
pub fn new(send: Sender<WidgetId>) -> Self {
Self {
needs_redraw: Default::default(),
vec: Default::default(),
waiting: Default::default(),
send,
}
}
pub fn has_updates(&self) -> bool {
!self.needs_redraw.is_empty()
}
pub fn get_dyn(&self, id: WidgetId) -> Option<&dyn Widget> {
Some(self.vec.get(id)?.widget.as_ref())
}
pub fn get_dyn_mut(&mut self, id: WidgetId) -> Option<&mut dyn Widget> {
self.needs_redraw.insert(id);
Some(self.vec.get_mut(id)?.widget.as_mut())
}
/// get_dyn but dynamic borrow checking of widgets
/// lets you do recursive (tree) operations, like the painter does
pub(crate) fn get_dyn_dynamic<'a>(&self, id: WidgetId) -> WidgetWrapper<'a> {
// SAFETY: must guarantee no other mutable references to this widget exist
// done through the borrow variable
let data = unsafe { forget_mut(to_mut(self.vec.get(id).unwrap())) };
if data.borrowed {
panic!("tried to mutably borrow the same widget twice");
}
WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed)
}
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
where
I::Widget: Sized + Widget,
{
self.get_dyn(id.id())?.as_any().downcast_ref()
}
pub fn get_mut<I: IdLike>(&mut self, id: &I) -> Option<&mut I::Widget>
where
I::Widget: Sized + Widget,
{
self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut()
}
pub fn add_strong<W: Widget>(&mut self, widget: W) -> WidgetHandle<W> {
let id = self.vec.add(WidgetData::new(widget));
WidgetHandle::new(id, self.send.clone())
}
pub fn add_weak<W: Widget>(&mut self, widget: W) -> WidgetRef<W> {
let id = self.vec.add(WidgetData::new(widget));
self.waiting.insert(id);
WidgetRef::new(id)
}
#[track_caller]
pub fn upgrade<W: ?Sized>(&mut self, rf: WidgetRef<W>) -> WidgetHandle<W> {
if !self.waiting.remove(&rf.id()) {
let label = self.label(rf);
let id = rf.id();
panic!("widget '{label}' ({id:?}) was already added\ncannot add a widget twice; consider creating two")
}
WidgetHandle::new(rf.id(), self.send.clone())
}
pub fn data(&self, id: impl IdLike) -> Option<&WidgetData> {
self.vec.get(id.id())
}
pub fn label(&self, id: impl IdLike) -> &String {
&self.data(id.id()).unwrap().label
}
pub fn data_mut(&mut self, id: impl IdLike) -> Option<&mut WidgetData> {
self.vec.get_mut(id.id())
}
pub fn delete(&mut self, id: impl IdLike) {
self.vec.free(id.id());
// not sure if there's any point in this
// self.updates.remove(&id);
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.vec.len()
}
}
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;

View File

@@ -1,17 +1,14 @@
use iris::prelude::*; use iris::{prelude::*};
fn main() { fn main() {
DefaultApp::<State>::run(); DefaultApp::<State>::run();
} }
#[derive(DefaultUiState)] struct State;
struct State {
ui_state: DefaultUiState,
}
impl DefaultAppState for State { impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self { fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
rect(Color::RED).set_root(rsc); rect(Color::RED).set_root(ui);
Self { ui_state } Self
} }
} }

View File

@@ -1,30 +0,0 @@
use iris::prelude::*;
use std::time::Duration;
fn main() {
DefaultApp::<State>::run();
}
#[derive(DefaultUiState)]
struct State {
ui_state: DefaultUiState,
}
impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self {
let rect = rect(Color::RED).add(rsc);
rect.task_on(CursorSense::click(), async move |mut ctx| {
tokio::time::sleep(Duration::from_secs(1)).await;
ctx.task.update(move |_, rsc| {
let rect = rect(rsc);
if rect.color == Color::RED {
rect.color = Color::BLUE;
} else {
rect.color = Color::RED;
}
});
})
.set_root(rsc);
Self { ui_state }
}
}

View File

@@ -1,46 +0,0 @@
use iris::prelude::*;
fn main() {
DefaultApp::<State>::run();
}
#[derive(DefaultUiState)]
struct State {
ui_state: DefaultUiState,
}
type Rsc = DefaultRsc<State>;
#[derive(Clone, Copy, WidgetView)]
struct Test {
#[root]
root: WidgetRef<Rect>,
}
impl Test {
pub fn new(rsc: &mut Rsc) -> Self {
let root = rect(Color::RED).add(rsc);
Self { root }
}
pub fn toggle(&self, rsc: &mut Rsc) {
let rect = (self.root)(rsc);
if rect.color == Color::RED {
rect.color = Color::BLUE;
} else {
rect.color = Color::RED;
}
}
}
impl DefaultAppState for State {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self {
let test = Test::new(rsc);
test.on(CursorSense::click(), move |_, rsc| {
test.toggle(rsc);
})
.set_root(rsc);
Self { ui_state }
}
}

View File

@@ -4,7 +4,6 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
proc-macro2 = "1.0.103"
quote = "1.0.42" quote = "1.0.42"
syn = { version = "2.0.111", features = ["full"] } syn = { version = "2.0.111", features = ["full"] }

View File

@@ -2,18 +2,14 @@ extern crate proc_macro;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ use syn::{
Attribute, Block, Error, GenericParam, Generics, Ident, ItemStruct, ItemTrait, Signature, Block, Ident, Signature, Token, Visibility,
Token, Type, Visibility,
parse::{Parse, ParseStream, Result}, parse::{Parse, ParseStream, Result},
parse_macro_input, parse_quote, parse_macro_input,
spanned::Spanned,
}; };
struct Input { struct Input {
attrs: Vec<Attribute>,
vis: Visibility, vis: Visibility,
name: Ident, name: Ident,
generics: Generics,
fns: Vec<InputFn>, fns: Vec<InputFn>,
} }
@@ -24,11 +20,9 @@ struct InputFn {
impl Parse for Input { impl Parse for Input {
fn parse(input: ParseStream) -> Result<Self> { fn parse(input: ParseStream) -> Result<Self> {
let attrs = input.call(Attribute::parse_outer)?;
let vis = input.parse()?; let vis = input.parse()?;
input.parse::<Token![trait]>()?; input.parse::<Token![trait]>()?;
let name = input.parse()?; let name = input.parse()?;
let generics = input.parse::<Generics>()?;
input.parse::<Token![;]>()?; input.parse::<Token![;]>()?;
let mut fns = Vec::new(); let mut fns = Vec::new();
while !input.is_empty() { while !input.is_empty() {
@@ -39,25 +33,27 @@ impl Parse for Input {
if !input.is_empty() { if !input.is_empty() {
input.error("function expected"); input.error("function expected");
} }
Ok(Input { Ok(Input { vis, name, fns })
attrs,
vis,
name,
generics,
fns,
})
} }
} }
// pub trait $name<W: WidgetLike<Tag>, Tag> {
// $(
// fn $fn $(<$($T $(: $TT $(+ $TL)?)?,)*>)?($self $(, $arg: $ty)*) -> $ret;
// )*
// }
//
// impl<W: WidgetLike<Tag>, Tag> $name<W, Tag> for W {
// $(
// fn $fn $(<$($T $(: $TT $(+ $TL)?)?,)*>)?($self $(, $arg: $ty)*) -> $ret {
// $code
// }
// )*
// }
#[proc_macro] #[proc_macro]
pub fn widget_trait(input: TokenStream) -> TokenStream { pub fn widget_trait(input: TokenStream) -> TokenStream {
let Input { let Input { vis, name, fns } = parse_macro_input!(input as Input);
attrs,
vis,
name,
mut generics,
fns,
} = parse_macro_input!(input as Input);
let sigs: Vec<_> = fns.iter().map(|f| f.sig.clone()).collect(); let sigs: Vec<_> = fns.iter().map(|f| f.sig.clone()).collect();
let impls: Vec<_> = fns let impls: Vec<_> = fns
@@ -65,132 +61,13 @@ pub fn widget_trait(input: TokenStream) -> TokenStream {
.map(|InputFn { sig, body }| quote! { #sig #body }) .map(|InputFn { sig, body }| quote! { #sig #body })
.collect(); .collect();
let Some(GenericParam::Type(state)) = generics.params.first() else { TokenStream::from(quote! {
return Error::new(name.span(), "expected state generic parameter") #vis trait #name<WL: WidgetLike<Tag>, Tag> {
.into_compile_error()
.into();
};
let state = &state.ident;
generics
.params
.push(parse_quote!(WL: WidgetLike<#state, Tag>));
generics.params.push(parse_quote!(Tag));
let mut trai: ItemTrait = parse_quote!(
#vis trait #name #generics {
#(#sigs;)* #(#sigs;)*
} }
);
trai.attrs = attrs; impl<WL: WidgetLike<Tag>, Tag> #name<WL, Tag> for WL {
quote! {
#trai
impl #generics #name<Rsc, WL, Tag> for WL {
#(#impls)* #(#impls)*
} }
} })
.into()
}
#[proc_macro_derive(DefaultUiState, attributes(default_ui_state))]
pub fn derive_default_ui_state(input: TokenStream) -> TokenStream {
let mut output = proc_macro2::TokenStream::new();
let state: ItemStruct = parse_macro_input!(input);
let mut found_attr = false;
let mut state_field = None;
for field in &state.fields {
if !found_attr
&& let Type::Path(path) = &field.ty
&& path.path.is_ident("DefaultUiState")
{
state_field = Some(field);
}
let Some(attr) = field
.attrs
.iter()
.find(|a| a.path().is_ident("default_ui_state"))
else {
continue;
};
if found_attr {
output.extend(
Error::new(
attr.span(),
"cannot have more than one default_ui_state attribute",
)
.into_compile_error(),
);
continue;
}
found_attr = true;
state_field = Some(field);
}
let Some(field) = state_field else {
output.extend(
Error::new(state.ident.span(), "no DefaultUiState field found").into_compile_error(),
);
return output.into();
};
let sname = &state.ident;
let fname = field.ident.as_ref().unwrap();
output.extend(quote! {
impl iris::default::HasDefaultUiState for #sname {
fn default_state(&self) -> &iris::default::DefaultUiState {
&self.#fname
}
fn default_state_mut(&mut self) -> &mut iris::default::DefaultUiState {
&mut self.#fname
}
}
});
output.into()
}
#[proc_macro_derive(WidgetView, attributes(root))]
pub fn derive_widget_view(input: TokenStream) -> TokenStream {
let mut output = proc_macro2::TokenStream::new();
let state: ItemStruct = parse_macro_input!(input);
let mut found_attr = false;
let mut state_field = None;
for field in &state.fields {
let Some(attr) = field.attrs.iter().find(|a| a.path().is_ident("root")) else {
continue;
};
if found_attr {
output.extend(
Error::new(attr.span(), "cannot have more than one root widget")
.into_compile_error(),
);
continue;
}
found_attr = true;
state_field = Some(field);
}
let Some(field) = state_field else {
output.extend(
Error::new(state.ident.span(), "no root widget field found (#[root])")
.into_compile_error(),
);
return output.into();
};
let sname = &state.ident;
let fname = field.ident.as_ref().unwrap();
let fty = &field.ty;
output.extend(quote! {
impl iris::core::WidgetView for #sname {
type Widget = <#fty as iris::core::HasWidget>::Widget;
fn root(&self) -> #fty {
self.#fname
}
}
});
output.into()
} }

View File

@@ -1,25 +1,22 @@
# iris # iris
My experimental attempt at a rust ui library (also my first ui library). my fisrt attempt at a rust ui library
It's currently designed around using retained data structures (widgets), rather than diffing generated trees from data like xilem or iced. This is an experiment and I'm not sure if it's a good idea or not. it's called iris because it's the structure around what you actually want to display and colorful
There's a `main.rs` that runs a testing window, so you can just `cargo run` to see it working. there's a `main.rs` that runs a testing window, so can just `cargo run` to see it working
Goals, in general order: goals, in general order:
1. does what I want it to (text, images, video, animations) 1. does what I want it to (text, images, video, animations)
2. very easy to use ignoring ergonomic ref counting 2. very easy to use ignoring ergonomic ref counting
3. reasonably fast / efficient (a lot faster than electron, save battery life) 3. reasonably fast / efficient (a lot faster than electron, save battery life)
## dev details not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps
not targeting web rn cause wanna use actual nice gpu features & entire point of this is to make desktop apps / not need a web browser
general ideas trynna use rn / experiment with: general ideas trynna use rn / experiment with:
- retained mode - retained mode
- specifically designed around wgpu so there's no translation - specifically designed around wgpu
- postfix functions for most things to prevent unreadable indentation (going very well) - postfix functions for most things to prevent unreadable indentation (going very well)
- events can be done directly where you draw the widgets
- almost no macros in user code & actual LSP typechecking (variadic generics if you can hear me please save us) - almost no macros in user code & actual LSP typechecking (variadic generics if you can hear me please save us)
- relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout) - relative anchor + absolute offset coord system (+ "rest" / leftover during widget layout)
- single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this) - single threaded ui & pass context around to make non async usage straightforward (pretty unsure about this)
@@ -27,5 +24,3 @@ general ideas trynna use rn / experiment with:
under heavy initial development so not gonna try to explain status, maybe check TODO for that; under heavy initial development so not gonna try to explain status, maybe check TODO for that;
sizable chance it gets a rewrite once I know everything I need and what seems to work best sizable chance it gets a rewrite once I know everything I need and what seems to work best
it's called iris because it's the structure around what you actually want to display and colorful

View File

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -1,22 +1,20 @@
use cosmic_text::Family; use cosmic_text::Family;
use std::{cell::RefCell, rc::Rc};
use winit::event::WindowEvent;
use iris::prelude::*; use iris::prelude::*;
type ClientRsc = DefaultRsc<Client>; use len_fns::*;
use winit::event::WindowEvent;
fn main() { fn main() {
DefaultApp::<Client>::run(); DefaultApp::<Client>::run();
} }
#[derive(DefaultUiState)]
pub struct Client { pub struct Client {
ui_state: DefaultUiState,
info: WidgetRef<Text>, info: WidgetRef<Text>,
} }
event_ctx!(Client);
impl DefaultAppState for Client { impl DefaultAppState for Client {
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, _: Proxy<Self::Event>) -> Self { fn new(ui: &mut Ui, _state: &UiState, _proxy: Proxy<Self::Event>) -> Self {
let rrect = rect(Color::WHITE).radius(20); let rrect = rect(Color::WHITE).radius(20);
let pad_test = ( let pad_test = (
rrect.color(Color::BLUE), rrect.color(Color::BLUE),
@@ -39,7 +37,7 @@ impl DefaultAppState for Client {
.width(rest(3)), .width(rest(3)),
) )
.span(Dir::RIGHT) .span(Dir::RIGHT)
.add(rsc); .add(ui);
let span_test = ( let span_test = (
rrect.color(Color::GREEN).width(100), rrect.color(Color::GREEN).width(100),
@@ -50,30 +48,32 @@ impl DefaultAppState for Client {
rrect.color(Color::RED).width(100), rrect.color(Color::RED).width(100),
) )
.span(Dir::LEFT) .span(Dir::LEFT)
.add(rsc); .add(ui);
let span_add = Span::empty(Dir::RIGHT).add(rsc); let span_add = Span::empty(Dir::RIGHT).add(ui);
let span_add_ = span_add.clone();
let add_button = rect(Color::LIME) let add_button = rect(Color::LIME)
.radius(30) .radius(30)
.on(CursorSense::click(), move |_, rsc| { .on(CursorSense::click(), move |ctx| {
let child = image(include_bytes!("assets/sungals.png")) let child = image(include_bytes!("assets/sungals.png"))
.center() .center()
.add_strong(rsc); .add(ctx.ui);
span_add(rsc).push(child); span_add_.get_mut().children.push(child);
}) })
.sized((150, 150)) .sized((150, 150))
.align(Align::BOT_RIGHT); .align(Align::BOT_RIGHT);
let span_add_ = span_add.clone();
let del_button = rect(Color::RED) let del_button = rect(Color::RED)
.radius(30) .radius(30)
.on(CursorSense::click(), move |_, rsc| { .on(CursorSense::click(), move |_| {
span_add(rsc).pop(); span_add_.get_mut().children.pop();
}) })
.sized((150, 150)) .sized((150, 150))
.align(Align::BOT_LEFT); .align(Align::BOT_LEFT);
let span_add_test = (span_add, add_button, del_button).stack().add(rsc); let span_add_test = (span_add, add_button, del_button).stack().add(ui);
let btext = |content| wtext(content).size(30); let btext = |content| wtext(content).size(30);
@@ -96,40 +96,36 @@ impl DefaultAppState for Client {
wtext("pretty cool right?").size(50), wtext("pretty cool right?").size(50),
) )
.span(Dir::DOWN) .span(Dir::DOWN)
.add(rsc); .add(ui);
let texts = Span::empty(Dir::DOWN).gap(10).add(rsc); let texts = Span::empty(Dir::DOWN).gap(10).add(ui);
let msg_area = texts.scrollable().masked().background(rect(Color::SKY)); let msg_area = texts.clone().scroll().masked().background(rect(Color::SKY));
let add_text = wtext("add") let add_text = wtext("add")
.editable(EditMode::MultiLine) .editable(false)
.text_align(Align::LEFT) .text_align(Align::LEFT)
.size(30) .size(30)
.attr::<Selectable>(()) .attr::<Selectable>(())
.on(Submit, move |ctx, rsc| { .on(Submit, move |ctx| {
let w = ctx.widget; let content = ctx.widget.get_mut().take();
let content = w.edit(rsc).take();
let text = wtext(content) let text = wtext(content)
.editable(EditMode::MultiLine) .editable(false)
.size(30) .size(30)
.text_align(Align::LEFT) .text_align(Align::LEFT)
.wrap(true) .wrap(true)
.attr::<Selectable>(()); .attr::<Selectable>(());
let msg_box = text let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui);
.background(rect(Color::WHITE.darker(0.5))) texts.get_mut().children.push(msg_box);
.add_strong(rsc);
texts(rsc).push(msg_box);
}) })
.add(rsc); .add(ui);
let text_edit_scroll = ( let text_edit_scroll = (
msg_area.height(rest(1)), msg_area.height(rest(1)),
( (
Rect::new(Color::WHITE.darker(0.9)), Rect::new(Color::WHITE.darker(0.9)),
( (
add_text.width(rest(1)), add_text.clone().width(rest(1)),
Rect::new(Color::GREEN) Rect::new(Color::GREEN)
.on(CursorSense::click(), move |ctx, rsc: &mut ClientRsc| { .on(CursorSense::click(), move |ctx| {
rsc.run_event::<Submit>(add_text, (), ctx.state); ctx.ui.run_event(ctx.state, &add_text, Submit, ());
}) })
.sized((40, 40)), .sized((40, 40)),
) )
@@ -142,39 +138,25 @@ impl DefaultAppState for Client {
.align(Align::BOT), .align(Align::BOT),
) )
.span(Dir::DOWN) .span(Dir::DOWN)
.add(rsc); .add(ui);
let main = WidgetPtr::new().add(rsc); let main = pad_test.clone().pad(10).add(ui);
let vals = Rc::new(RefCell::new((0, Vec::new()))); let switch_button = |color, to: WidgetRef, label| {
let mut switch_button = |color, to: WidgetRef, label| { let main_ = main.clone();
let to = to.upgrade(rsc);
let vec = &mut vals.borrow_mut().1;
let i = vec.len();
if vec.is_empty() {
vec.push(None);
main(rsc).set(to);
} else {
vec.push(Some(to));
}
let vals = vals.clone();
let rect = rect(color) let rect = rect(color)
.on(CursorSense::click(), move |ctx, rsc| { .on(CursorSense::click(), move |ctx| {
let (prev, vec) = &mut *vals.borrow_mut(); main_.get_mut().inner = to.clone();
if let Some(h) = vec[i].take() { ctx.widget.get_mut().color = color.darker(0.3);
vec[*prev] = main(rsc).replace(h);
*prev = i;
}
ctx.widget(rsc).color = color.darker(0.3);
}) })
.on( .on(
CursorSense::HoverStart | CursorSense::unclick(), CursorSense::HoverStart | CursorSense::unclick(),
move |ctx, rsc| { move |ctx| {
ctx.widget(rsc).color = color.brighter(0.2); ctx.widget.get_mut().color = color.brighter(0.2);
}, },
) )
.on(CursorSense::HoverEnd, move |ctx, rsc| { .on(CursorSense::HoverEnd, move |ctx| {
ctx.widget(rsc).color = color; ctx.widget.get_mut().color = color;
}); });
(rect, wtext(label).size(30).text_align(Align::CENTER)).stack() (rect, wtext(label).size(30).text_align(Align::CENTER)).stack()
}; };
@@ -192,25 +174,25 @@ impl DefaultAppState for Client {
) )
.span(Dir::RIGHT); .span(Dir::RIGHT);
let info = wtext("").add(rsc); let info = wtext("").add(ui);
let info_sect = info.pad(10).align(Align::RIGHT); let info_sect = info.clone().pad(10).align(Align::RIGHT);
((tabs.height(40), main.pad(10)).span(Dir::DOWN), info_sect) ((tabs.height(40), main).span(Dir::DOWN), info_sect)
.stack() .stack()
.set_root(rsc); .set_root(ui);
Self { ui_state, info } Self { info }
} }
fn window_event(&mut self, _: WindowEvent, rsc: &mut DefaultRsc<Self>) { fn window_event(&mut self, _: WindowEvent, ui: &mut Ui, state: &UiState) {
let new = format!( let new = format!(
"widgets: {}\nactive: {}\nviews: {}", "widgets: {}\nactive:{}\nviews: {}",
rsc.ui.num_widgets(), ui.num_widgets(),
rsc.ui.active_widgets(), ui.active_widgets(),
self.ui_state.renderer.ui.view_count() state.renderer.ui.view_count()
); );
if new != *rsc.ui[self.info].content { if new != *self.info.get().content {
*rsc.ui[self.info].content = new; *self.info.get_mut().content = new;
} }
} }
} }

View File

@@ -11,13 +11,6 @@ pub trait AppState {
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop); fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop);
fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop); fn event(&mut self, event: Self::Event, event_loop: &ActiveEventLoop);
fn exit(&mut self); fn exit(&mut self);
fn run()
where
Self: Sized,
{
App::<Self>::run();
}
} }
pub struct App<State: AppState> { pub struct App<State: AppState> {

View File

@@ -1,63 +1,50 @@
use crate::prelude::*; use crate::{prelude::*, default::UiState};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use winit::dpi::{LogicalPosition, LogicalSize}; use winit::dpi::{LogicalPosition, LogicalSize};
pub struct Selector; pub struct Selector;
impl<Rsc: HasEvents, W: Widget + 'static> WidgetAttr<Rsc, W> for Selector impl<W: 'static> WidgetAttr<W> for Selector {
where
Rsc::State: HasDefaultUiState,
{
type Input = WidgetRef<TextEdit>; type Input = WidgetRef<TextEdit>;
fn run(rsc: &mut Rsc, container: WidgetRef<W>, id: Self::Input) { fn run(ui: &mut Ui, container: &WidgetRef<W>, id: Self::Input) {
rsc.register_event(container, CursorSense::click_or_drag(), move |ctx, rsc| { let container = container.clone();
let region = rsc.ui().window_region(&id).unwrap(); ui.register_event(
&container.clone(),
CursorSense::click_or_drag(),
move |mut ctx| {
let ui = ctx.ui;
let region = ui.window_region(&id).unwrap();
let id_pos = region.top_left; let id_pos = region.top_left;
let container_pos = rsc.ui().window_region(&container).unwrap().top_left; let container_pos = ui.window_region(&container).unwrap().top_left;
let pos = ctx.data.pos + container_pos - id_pos; ctx.data.cursor += container_pos - id_pos;
let size = region.size(); ctx.data.size = region.size();
select(rsc, ctx.state, id, pos, size, ctx.data.sense.is_dragging()); select(ui, id.clone(), ctx.state, ctx.data);
}); },
);
} }
} }
pub struct Selectable; pub struct Selectable;
impl<Rsc: HasEvents> WidgetAttr<Rsc, TextEdit> for Selectable impl WidgetAttr<TextEdit> for Selectable {
where
Rsc::State: HasDefaultUiState,
{
type Input = (); type Input = ();
fn run(rsc: &mut Rsc, id: WidgetRef<TextEdit>, _: Self::Input) { fn run(ui: &mut Ui, id: &WidgetRef<TextEdit>, _: Self::Input) {
rsc.register_event(id, CursorSense::click_or_drag(), move |ctx, rsc| { let id = id.clone();
select( ui.register_event(&id.clone(), CursorSense::click_or_drag(), move |ctx| {
rsc, select(ctx.ui, id.clone(), ctx.state, ctx.data);
ctx.state,
id,
ctx.data.pos,
ctx.data.size,
ctx.data.sense.is_dragging(),
);
}); });
} }
} }
fn select( fn select(ui: &mut Ui, id: WidgetRef<TextEdit>, state: &mut UiState, data: CursorData) {
rsc: &mut impl HasUi,
state: &mut impl HasDefaultUiState,
id: WidgetRef<TextEdit>,
pos: Vec2,
size: Vec2,
dragging: bool,
) {
let state = state.default_state_mut();
let now = Instant::now(); let now = Instant::now();
let recent = (now - state.last_click) < Duration::from_millis(300); let recent = (now - state.last_click) < Duration::from_millis(300);
state.last_click = now; state.last_click = now;
id.edit(rsc).select(pos, size, dragging, recent); id.get_mut()
if let Some(region) = rsc.ui().window_region(&id) { .select(data.cursor, data.size, data.sense.is_dragging(), recent);
if let Some(region) = ui.window_region(&id) {
state.window.set_ime_allowed(true); state.window.set_ime_allowed(true);
state.window.set_ime_cursor_area( state.window.set_ime_cursor_area(
LogicalPosition::<f32>::from(region.top_left.tuple()), LogicalPosition::<f32>::from(region.top_left.tuple()),

View File

@@ -1,9 +1,15 @@
use iris_core::Event; use crate::layout::DefaultEvent;
#[derive(Eq, PartialEq, Hash, Clone)] #[derive(Eq, PartialEq, Hash, Clone)]
pub struct Submit; pub struct Submit;
impl Event for Submit {}
#[derive(Eq, PartialEq, Hash, Clone)] #[derive(Eq, PartialEq, Hash, Clone)]
pub struct Edited; pub struct Edited;
impl Event for Edited {}
impl DefaultEvent for Submit {
type Data = ();
}
impl DefaultEvent for Edited {
type Data = ();
}

View File

@@ -1,4 +1,8 @@
use crate::prelude::*; use crate::{
widget::{CursorState, Modifiers},
layout::Vec2,
default::UiState,
};
use winit::{ use winit::{
event::{MouseButton, MouseScrollDelta, WindowEvent}, event::{MouseButton, MouseScrollDelta, WindowEvent},
keyboard::{Key, NamedKey}, keyboard::{Key, NamedKey},
@@ -66,7 +70,7 @@ impl Input {
} }
} }
impl DefaultUiState { impl UiState {
pub fn window_size(&self) -> Vec2 { pub fn window_size(&self) -> Vec2 {
let size = self.renderer.window().inner_size(); let size = self.renderer.window().inner_size();
(size.width, size.height).into() (size.width, size.height).into()

View File

@@ -1,35 +1,33 @@
use crate::prelude::*; use crate::prelude::*;
use arboard::Clipboard; use arboard::Clipboard;
use std::{ use std::sync::Arc;
marker::{PhantomData, Sized}, use std::time::Instant;
sync::Arc, use winit::event::{Ime, WindowEvent};
time::Instant, use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
}; use winit::window::{Window, WindowAttributes};
use winit::{
event::{Ime, WindowEvent},
event_loop::{ActiveEventLoop, EventLoopProxy},
window::{Window, WindowAttributes},
};
mod app; mod app;
mod attr; mod attr;
mod event; mod event;
mod input; mod input;
mod render; mod render;
mod sense;
mod task;
pub use app::*; pub use app::*;
pub use attr::*; pub use attr::*;
pub use event::*; pub use event::*;
pub use input::*; pub use input::*;
pub use render::*; pub use render::*;
pub use sense::*;
pub use task::*;
pub type Proxy<Event> = EventLoopProxy<Event>; pub type Proxy<Event> = EventLoopProxy<Event>;
pub type DefaultApp<Data> = App<DefaultState<Data>>;
pub struct DefaultUiState { pub struct DefaultState<AppState> {
ui: Ui,
ui_state: UiState,
app_state: AppState,
}
pub struct UiState {
pub renderer: UiRenderer, pub renderer: UiRenderer,
pub input: Input, pub input: Input,
pub focus: Option<WidgetRef<TextEdit>>, pub focus: Option<WidgetRef<TextEdit>>,
@@ -39,10 +37,31 @@ pub struct DefaultUiState {
pub last_click: Instant, pub last_click: Instant,
} }
impl DefaultUiState { pub trait DefaultAppState: 'static {
pub fn new(window: impl Into<Arc<Window>>) -> Self { type Event: 'static = ();
let window = window.into(); fn new(ui: &mut Ui, state: &UiState, proxy: Proxy<Self::Event>) -> Self;
Self { #[allow(unused_variables)]
fn event(&mut self, event: Self::Event, ui: &mut Ui, state: &UiState) {}
#[allow(unused_variables)]
fn exit(&mut self, ui: &mut Ui, state: &UiState) {}
#[allow(unused_variables)]
fn window_event(&mut self, event: WindowEvent, ui: &mut Ui, state: &UiState) {}
fn window_attrs() -> WindowAttributes {
WindowAttributes::default()
}
}
impl<State: DefaultAppState> AppState for DefaultState<State> {
type Event = State::Event;
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
let window = Arc::new(
event_loop
.create_window(State::window_attrs())
.expect("failed to create window "),
);
let mut ui = Ui::new();
let ui_state = UiState {
renderer: UiRenderer::new(window.clone()), renderer: UiRenderer::new(window.clone()),
window, window,
input: Input::default(), input: Input::default(),
@@ -50,137 +69,49 @@ impl DefaultUiState {
ime: 0, ime: 0,
last_click: Instant::now(), last_click: Instant::now(),
focus: None, focus: None,
} };
} let app_state = State::new(&mut ui, &ui_state, proxy);
}
pub trait HasDefaultUiState: Sized + 'static {
fn default_state(&self) -> &DefaultUiState;
fn default_state_mut(&mut self) -> &mut DefaultUiState;
}
pub trait DefaultAppState: HasDefaultUiState {
type Event = ();
fn new(ui_state: DefaultUiState, rsc: &mut DefaultRsc<Self>, proxy: Proxy<Self::Event>)
-> Self;
#[allow(unused_variables)]
fn event(&mut self, event: Self::Event, rsc: &mut DefaultRsc<Self>) {}
#[allow(unused_variables)]
fn exit(&mut self, rsc: &mut DefaultRsc<Self>) {}
#[allow(unused_variables)]
fn window_event(&mut self, event: WindowEvent, rsc: &mut DefaultRsc<Self>) {}
fn window_attributes() -> WindowAttributes {
Default::default()
}
}
pub struct DefaultRsc<State: 'static> {
pub ui: Ui,
pub events: EventManager<Self>,
pub tasks: Tasks<Self>,
_state: PhantomData<State>,
}
impl<State> DefaultRsc<State> {
fn init(window: Arc<Window>) -> (Self, TaskMsgReceiver<Self>) {
let (tasks, recv) = Tasks::init(window);
(
Self { Self {
ui: Default::default(), ui,
events: Default::default(), ui_state,
tasks, app_state,
_state: Default::default(),
},
recv,
)
}
}
impl<State> HasUi for DefaultRsc<State> {
fn ui(&self) -> &Ui {
&self.ui
}
fn ui_mut(&mut self) -> &mut Ui {
&mut self.ui
}
}
impl<State: 'static> HasState for DefaultRsc<State> {
type State = State;
}
impl<State: 'static> HasEvents for DefaultRsc<State> {
fn events(&self) -> &EventManager<Self> {
&self.events
}
fn events_mut(&mut self) -> &mut EventManager<Self> {
&mut self.events
}
}
impl<State: 'static> HasTasks for DefaultRsc<State> {
fn tasks_mut(&mut self) -> &mut Tasks<Self> {
&mut self.tasks
}
}
pub struct DefaultApp<State: DefaultAppState> {
rsc: DefaultRsc<State>,
state: State,
task_recv: TaskMsgReceiver<DefaultRsc<State>>,
}
impl<State: DefaultAppState> AppState for DefaultApp<State> {
type Event = State::Event;
fn new(event_loop: &ActiveEventLoop, proxy: EventLoopProxy<Self::Event>) -> Self {
let window = event_loop
.create_window(State::window_attributes())
.unwrap();
let default_state = DefaultUiState::new(window);
let (mut rsc, task_recv) = DefaultRsc::init(default_state.window.clone());
let state = State::new(default_state, &mut rsc, proxy);
Self {
rsc,
state,
task_recv,
} }
} }
fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) { fn event(&mut self, event: Self::Event, _: &ActiveEventLoop) {
self.state.event(event, &mut self.rsc); self.app_state.event(event, &mut self.ui, &self.ui_state);
} }
fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) {
for update in self.task_recv.try_iter() { let Self {
update(&mut self.state, &mut self.rsc); ui,
} ui_state,
app_state,
} = self;
let ui_state = self.state.default_state_mut();
let input_changed = ui_state.input.event(&event); let input_changed = ui_state.input.event(&event);
let cursor_state = ui_state.cursor_state().clone(); let cursor_state = ui_state.cursor_state().clone();
let old = ui_state.focus; let old = ui_state.focus.clone();
if cursor_state.buttons.left.is_start() { if cursor_state.buttons.left.is_start() {
ui_state.focus = None; ui_state.focus = None;
} }
if input_changed { if input_changed {
let window_size = ui_state.window_size(); let window_size = ui_state.window_size();
self.rsc // call sensors with all 3 important contexts
.run_sensors(&mut self.state, cursor_state, window_size); // TODO: allow user to specify custom contexts?
// and give them both states in case they need both
ui.run_sensors(&mut (), &cursor_state, window_size);
ui.run_sensors(ui_state, &cursor_state, window_size);
ui.run_sensors(app_state, &cursor_state, window_size);
} }
let ui = &mut self.rsc.ui;
let ui_state = self.state.default_state_mut();
if old != ui_state.focus if old != ui_state.focus
&& let Some(old) = old && let Some(old) = old
{ {
old.edit(ui).deselect(); old.get_mut().deselect();
} }
match &event { match &event {
WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
ui.update(&mut self.rsc.events);
ui_state.renderer.update(ui); ui_state.renderer.update(ui);
ui_state.renderer.draw(); ui_state.renderer.draw();
} }
@@ -189,23 +120,24 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
ui_state.renderer.resize(size) ui_state.renderer.resize(size)
} }
WindowEvent::KeyboardInput { event, .. } => { WindowEvent::KeyboardInput { event, .. } => {
if let Some(sel) = ui_state.focus if let Some(sel) = &ui_state.focus
&& event.state.is_pressed() && event.state.is_pressed()
{ {
let mut text = sel.edit(ui); let sel = &sel.clone();
match text.apply_event(event, &ui_state.input.modifiers) { let res = sel.get_mut().apply_event(event, &ui_state.input.modifiers);
match res {
TextInputResult::Unfocus => { TextInputResult::Unfocus => {
ui_state.focus = None; ui_state.focus = None;
ui_state.window.set_ime_allowed(false); ui_state.window.set_ime_allowed(false);
} }
TextInputResult::Submit => { TextInputResult::Submit => {
self.rsc.run_event::<Submit>(sel, (), &mut self.state); ui.run_event(app_state, sel, Submit, ());
} }
TextInputResult::Paste => { TextInputResult::Paste => {
if let Ok(t) = ui_state.clipboard.get_text() { if let Ok(t) = ui_state.clipboard.get_text() {
text.insert(&t); sel.get_mut().insert(&t);
} }
self.rsc.run_event::<Edited>(sel, (), &mut self.state); ui.run_event(app_state, sel, Edited, ());
} }
TextInputResult::Copy(text) => { TextInputResult::Copy(text) => {
if let Err(err) = ui_state.clipboard.set_text(text) { if let Err(err) = ui_state.clipboard.set_text(text) {
@@ -213,39 +145,37 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
} }
} }
TextInputResult::Used => { TextInputResult::Used => {
self.rsc.run_event::<Edited>(sel, (), &mut self.state); ui.run_event(app_state, sel, Edited, ());
} }
TextInputResult::Unused => {} TextInputResult::Unused => {}
} }
} }
} }
WindowEvent::Ime(ime) => { WindowEvent::Ime(ime) => {
if let Some(sel) = ui_state.focus { if let Some(sel) = &ui_state.focus {
let mut text = sel.edit(ui);
match ime { match ime {
Ime::Enabled | Ime::Disabled => (), Ime::Enabled | Ime::Disabled => (),
Ime::Preedit(content, _pos) => { Ime::Preedit(content, _pos) => {
// TODO: highlight once that's real // TODO: highlight once that's real
text.replace(ui_state.ime, content); sel.get_mut().replace(ui_state.ime, content);
ui_state.ime = content.chars().count(); ui_state.ime = content.chars().count();
} }
Ime::Commit(content) => { Ime::Commit(content) => {
text.insert(content); sel.get_mut().insert(content);
} }
} }
} }
} }
_ => (), _ => (),
} }
self.state.window_event(event, &mut self.rsc); app_state.window_event(event, ui, ui_state);
let ui_state = self.state.default_state_mut(); if ui.update() {
if self.rsc.ui.needs_redraw() {
ui_state.renderer.window().request_redraw(); ui_state.renderer.window().request_redraw();
} }
ui_state.input.end_frame(); ui_state.input.end_frame();
} }
fn exit(&mut self) { fn exit(&mut self) {
self.state.exit(&mut self.rsc); self.app_state.exit(&mut self.ui, &self.ui_state);
} }
} }

View File

@@ -1,7 +1,10 @@
use iris_core::{Ui, UiLimits, UiRenderNode}; use crate::{
layout::Ui,
render::{UiLimits, UiRenderNode},
};
use pollster::FutureExt; use pollster::FutureExt;
use std::sync::Arc; use std::sync::Arc;
use wgpu::*; use wgpu::{util::StagingBelt, *};
use winit::{dpi::PhysicalSize, window::Window}; use winit::{dpi::PhysicalSize, window::Window};
pub const CLEAR_COLOR: Color = Color::BLACK; pub const CLEAR_COLOR: Color = Color::BLACK;
@@ -13,12 +16,13 @@ pub struct UiRenderer {
queue: Queue, queue: Queue,
config: SurfaceConfiguration, config: SurfaceConfiguration,
encoder: CommandEncoder, encoder: CommandEncoder,
staging_belt: StagingBelt,
pub ui: UiRenderNode, pub ui: UiRenderNode,
} }
impl UiRenderer { impl UiRenderer {
pub fn update(&mut self, ui: &mut Ui) { pub fn update(&mut self, updates: &mut Ui) {
self.ui.update(&self.device, &self.queue, ui); self.ui.update(&self.device, &self.queue, updates);
} }
pub fn draw(&mut self) { pub fn draw(&mut self) {
@@ -45,7 +49,9 @@ impl UiRenderer {
} }
self.queue.submit(std::iter::once(encoder.finish())); self.queue.submit(std::iter::once(encoder.finish()));
self.staging_belt.finish();
output.present(); output.present();
self.staging_belt.recall();
} }
pub fn resize(&mut self, size: &PhysicalSize<u32>) { pub fn resize(&mut self, size: &PhysicalSize<u32>) {
@@ -66,6 +72,7 @@ impl UiRenderer {
let instance = Instance::new(&InstanceDescriptor { let instance = Instance::new(&InstanceDescriptor {
backends: Backends::PRIMARY, backends: Backends::PRIMARY,
flags: InstanceFlags::empty(),
..Default::default() ..Default::default()
}); });
@@ -115,7 +122,7 @@ impl UiRenderer {
format: surface_format, format: surface_format,
width: size.width, width: size.width,
height: size.height, height: size.height,
present_mode: PresentMode::AutoNoVsync, present_mode: PresentMode::AutoVsync,
alpha_mode: surface_caps.alpha_modes[0], alpha_mode: surface_caps.alpha_modes[0],
desired_maximum_frame_latency: 2, desired_maximum_frame_latency: 2,
view_formats: vec![], view_formats: vec![],
@@ -123,9 +130,10 @@ impl UiRenderer {
surface.configure(&device, &config); surface.configure(&device, &config);
let staging_belt = StagingBelt::new(4096 * 4);
let encoder = Self::create_encoder(&device); let encoder = Self::create_encoder(&device);
let ui = UiRenderNode::new(&device, &queue, &config, ui_limits); let shape_pipeline = UiRenderNode::new(&device, &queue, &config, ui_limits);
Self { Self {
surface, surface,
@@ -133,7 +141,8 @@ impl UiRenderer {
queue, queue,
config, config,
encoder, encoder,
ui, staging_belt,
ui: shape_pipeline,
window, window,
} }
} }

View File

@@ -1,82 +0,0 @@
use iris_core::HasState;
use std::{
pin::Pin,
sync::{
Arc,
mpsc::{Receiver as SyncReceiver, Sender as SyncSender, channel as sync_channel},
},
};
use tokio::{
runtime::Runtime,
sync::mpsc::{
UnboundedReceiver as AsyncReceiver, UnboundedSender as AsyncSender,
unbounded_channel as async_channel,
},
};
use winit::window::Window;
pub type TaskMsgSender<Rsc> = SyncSender<Box<dyn TaskUpdate<Rsc>>>;
pub type TaskMsgReceiver<Rsc> = SyncReceiver<Box<dyn TaskUpdate<Rsc>>>;
pub trait TaskUpdate<Rsc: HasState>: FnOnce(&mut Rsc::State, &mut Rsc) + Send {}
impl<F: FnOnce(&mut Rsc::State, &mut Rsc) + Send, Rsc: HasState> TaskUpdate<Rsc> for F {}
pub struct Tasks<Rsc: HasState> {
start: AsyncSender<BoxTask>,
window: Arc<Window>,
msg_send: SyncSender<Box<dyn TaskUpdate<Rsc>>>,
}
pub struct TaskCtx<Rsc: HasState> {
send: TaskMsgSender<Rsc>,
}
impl<Rsc: HasState> TaskCtx<Rsc> {
pub fn update(&mut self, f: impl TaskUpdate<Rsc> + 'static) {
let _ = self.send.send(Box::new(f));
}
}
impl<Rsc: HasState + 'static> TaskCtx<Rsc> {
fn new(send: TaskMsgSender<Rsc>) -> Self {
Self { send }
}
}
type BoxTask = Pin<Box<dyn Future<Output = ()> + Send>>;
impl<Rsc: HasState> Tasks<Rsc> {
pub fn init(window: Arc<Window>) -> (Self, TaskMsgReceiver<Rsc>) {
let (start, start_recv) = async_channel();
let (msgs, msgs_recv) = sync_channel();
std::thread::spawn(|| {
let rt = Runtime::new().unwrap();
rt.block_on(listen(start_recv))
});
(
Self {
start,
msg_send: msgs,
window,
},
msgs_recv,
)
}
pub fn spawn<F: AsyncFnOnce(TaskCtx<Rsc>) + 'static + std::marker::Send>(&mut self, task: F)
where
F::CallOnceFuture: Send,
{
let send = self.msg_send.clone();
let window = self.window.clone();
let _ = self.start.send(Box::pin(async move {
task(TaskCtx::new(send)).await;
window.request_redraw();
}));
}
}
async fn listen(mut recv: AsyncReceiver<BoxTask>) {
while let Some(task) = recv.recv().await {
tokio::spawn(task);
}
}

View File

@@ -1,81 +1,82 @@
use iris_core::*; use crate::prelude::*;
use iris_macro::*; use iris_macro::widget_trait;
use std::sync::Arc;
use crate::default::{TaskCtx, Tasks}; pub mod eventable {
use super::*;
pub trait Eventable<Rsc: HasEvents, Tag>: WidgetLike<Rsc, Tag> { widget_trait! {
fn on<E: EventLike>( pub trait Eventable;
fn on<E: Event, Ctx: 'static>(
self, self,
event: E, event: E,
f: impl WidgetEventFn<Rsc, <E::Event as Event>::Data, Self::Widget>, f: impl WidgetEventFn<Ctx, E::Data, WL::Widget>,
) -> impl WidgetIdFn<Rsc, Self::Widget> { ) -> impl WidgetIdFn<WL::Widget> {
move |rsc| { move |ui| {
let id = self.add(rsc); let id = self.add(ui);
rsc.register_event(id, event.into_event(), move |ctx, rsc| { ui.register_widget_event(&id, event, f);
f(
EventIdCtx {
widget: id,
state: ctx.state,
data: ctx.data,
},
rsc,
);
});
id id
} }
} }
}
} }
impl<WL: WidgetLike<Rsc, Tag>, Rsc: HasEvents, Tag> Eventable<Rsc, Tag> for WL {}
widget_trait! { // TODO: naming in here is a bit weird like eventable
pub trait TaskEventable<Rsc: HasEvents + HasTasks>; #[macro_export]
fn task_on<E: EventLike, F: AsyncWidgetEventFn<Rsc, <E::Event as Event>::Data, WL::Widget>>( macro_rules! event_ctx {
($ty: ty) => {
mod local_event_trait {
use super::*;
use std::marker::Sized;
#[allow(unused_imports)]
use $crate::prelude::*;
#[allow(unused)]
pub trait EventableCtx<W: ?Sized, Tag, Ctx: 'static> {
fn on<E: Event>(
self, self,
event: E, event: E,
f: F, f: impl WidgetEventFn<Ctx, E::Data, W>,
) -> impl WidgetIdFn<Rsc, WL::Widget> ) -> impl WidgetIdFn<W> + EventableCtx<W, IdFnTag, Ctx>;
where <E::Event as Event>::Data: Send, }
for<'a> F::CallRefFuture<'a>: Send,
impl<WL: WidgetLike<Tag>, Tag> EventableCtx<WL::Widget, Tag, $ty> for WL {
fn on<E: Event>(
self,
event: E,
f: impl WidgetEventFn<$ty, E::Data, WL::Widget>,
) -> impl WidgetIdFn<WL::Widget> + EventableCtx<WL::Widget, IdFnTag, $ty> {
eventable::Eventable::on(self, event, f)
}
}
#[allow(unused)]
pub trait EventableCtxUi<W: ?Sized, Tag, Ctx: 'static>
where
WidgetRef<W>: EventableCtx<W, Tag, Ctx>,
{ {
let f = Arc::new(f); fn on<E: Event>(
move |rsc| { &mut self,
let id = self.add(rsc); widget: &WidgetRef<W>,
rsc.register_event(id, event.into_event(), move |ctx, rsc| { event: E,
let data = ctx.data; f: impl WidgetEventFn<Ctx, E::Data, W>,
let f = f.clone(); );
rsc.tasks_mut().spawn(async move |task| { }
f(AsyncEventIdCtx {
widget: id, impl<W: ?Sized + 'static, Tag> EventableCtxUi<W, Tag, $ty> for Ui
data, where
task, WidgetRef<W>: EventableCtx<W, Tag, $ty>,
}).await; {
}); fn on<E: Event>(
}); &mut self,
id widget: &WidgetRef<W>,
event: E,
f: impl WidgetEventFn<$ty, E::Data, W>,
) {
self.register_widget_event(&widget, event, f);
} }
} }
}
use local_event_trait::*;
};
} }
pub use event_ctx;
pub trait HasTasks: Sized + HasState + HasEvents {
fn tasks_mut(&mut self) -> &mut Tasks<Self>;
}
pub trait AsyncWidgetEventFn<Rsc: HasEvents, Data, W: ?Sized>:
AsyncFn(AsyncEventIdCtx<Rsc, Data, W>) + Send + Sync + 'static
{
}
impl<
Rsc: HasEvents,
F: AsyncFn(AsyncEventIdCtx<Rsc, Data, W>) + Send + Sync + 'static,
Data,
W: ?Sized,
> AsyncWidgetEventFn<Rsc, Data, W> for F
{
}
pub struct AsyncEventIdCtx<Rsc: HasEvents, Data, W: ?Sized> {
pub widget: WidgetRef<W>,
pub data: Data,
pub task: TaskCtx<Rsc>,
}

View File

@@ -2,25 +2,22 @@
#![feature(fn_traits)] #![feature(fn_traits)]
#![feature(gen_blocks)] #![feature(gen_blocks)]
#![feature(associated_type_defaults)] #![feature(associated_type_defaults)]
#![feature(unsize)]
#![feature(option_into_flat_iter)]
#![feature(async_fn_traits)]
pub mod default; mod default;
pub mod event; mod event;
pub mod widget; mod widget;
pub use iris_core as core; pub use iris_core::*;
pub use iris_macro as macros; pub use iris_macro::*;
pub mod prelude { pub mod prelude {
use super::*; pub use super::default::*;
pub use default::*; pub use super::event::*;
pub use event::*; pub use super::widget::*;
pub use iris_core::*;
pub use iris_macro::*;
pub use widget::*;
pub use iris_core::util::Vec2; pub use iris_core::layout::*;
pub use len_fns::*; pub use iris_core::render::*;
pub use iris_core::util::Handle;
pub use iris_macro::*;
} }

View File

@@ -1,5 +1,5 @@
use crate::prelude::*;
use image::DynamicImage; use image::DynamicImage;
use crate::prelude::*;
pub struct Image { pub struct Image {
handle: TextureHandle, handle: TextureHandle,
@@ -19,10 +19,10 @@ impl Widget for Image {
} }
} }
pub fn image<State: HasUi>(image: impl LoadableImage) -> impl WidgetFn<State, Image> { pub fn image(image: impl LoadableImage) -> impl WidgetFn<Image> {
let image = image.get_image().expect("Failed to load image"); let image = image.get_image().expect("Failed to load image");
move |state| Image { move |ui| Image {
handle: state.ui_mut().add_texture(image), handle: ui.add_texture(image),
} }
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Masked { pub struct Masked {
pub inner: WidgetHandle, pub inner: WidgetRef,
} }
impl Widget for Masked { impl Widget for Masked {

View File

@@ -3,6 +3,7 @@ mod mask;
mod position; mod position;
mod ptr; mod ptr;
mod rect; mod rect;
mod sense;
mod text; mod text;
mod trait_fns; mod trait_fns;
@@ -11,5 +12,6 @@ pub use mask::*;
pub use position::*; pub use position::*;
pub use ptr::*; pub use ptr::*;
pub use rect::*; pub use rect::*;
pub use sense::*;
pub use text::*; pub use text::*;
pub use trait_fns::*; pub use trait_fns::*;

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Aligned { pub struct Aligned {
pub inner: WidgetHandle, pub inner: WidgetRef,
pub align: Align, pub align: Align,
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct LayerOffset { pub struct LayerOffset {
pub inner: WidgetHandle, pub inner: WidgetRef,
pub offset: usize, pub offset: usize,
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct MaxSize { pub struct MaxSize {
pub inner: WidgetHandle, pub inner: WidgetRef,
pub x: Option<Len>, pub x: Option<Len>,
pub y: Option<Len>, pub y: Option<Len>,
} }

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Offset { pub struct Offset {
pub inner: WidgetHandle, pub inner: WidgetRef,
pub amt: UiVec2, pub amt: UiVec2,
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
pub struct Pad { pub struct Pad {
pub padding: Padding, pub padding: Padding,
pub inner: WidgetHandle, pub inner: WidgetRef,
} }
impl Widget for Pad { impl Widget for Pad {

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Scroll { pub struct Scroll {
inner: WidgetHandle, inner: WidgetRef,
axis: Axis, axis: Axis,
amt: f32, amt: f32,
snap_end: bool, snap_end: bool,
@@ -41,7 +41,7 @@ impl Widget for Scroll {
} }
impl Scroll { impl Scroll {
pub fn new(inner: WidgetHandle, axis: Axis) -> Self { pub fn new(inner: WidgetRef, axis: Axis) -> Self {
Self { Self {
inner, inner,
axis, axis,

View File

@@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
pub struct Sized { pub struct Sized {
pub inner: WidgetHandle, pub inner: WidgetRef,
pub x: Option<Len>, pub x: Option<Len>,
pub y: Option<Len>, pub y: Option<Len>,
} }

View File

@@ -2,7 +2,7 @@ use crate::prelude::*;
use std::marker::PhantomData; use std::marker::PhantomData;
pub struct Span { pub struct Span {
pub children: Vec<WidgetHandle>, pub children: Vec<WidgetRef>,
pub dir: Dir, pub dir: Dir,
pub gap: f32, pub gap: f32,
} }
@@ -62,14 +62,6 @@ impl Span {
self self
} }
pub fn push(&mut self, w: WidgetHandle) {
self.children.push(w);
}
pub fn pop(&mut self) -> Option<WidgetHandle> {
self.children.pop()
}
fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len { fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len {
let gap = self.gap * self.children.len().saturating_sub(1) as f32; let gap = self.gap * self.children.len().saturating_sub(1) as f32;
self.children.iter().fold(Len::abs(gap), |mut s, id| { self.children.iter().fold(Len::abs(gap), |mut s, id| {
@@ -152,31 +144,28 @@ impl Span {
} }
} }
pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> { pub struct SpanBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
pub children: Wa, pub children: Wa,
pub dir: Dir, pub dir: Dir,
pub gap: f32, pub gap: f32,
_pd: PhantomData<(State, Tag)>, _pd: PhantomData<Tag>,
} }
impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc> impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
for SpanBuilder<Rsc, LEN, Wa, Tag> for SpanBuilder<LEN, Wa, Tag>
{ {
type Widget = Span; type Output = Span;
#[track_caller] extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
fn run(self, rsc: &mut Rsc) -> Self::Widget {
Span { Span {
children: self.children.add(rsc).arr.into_iter().collect(), children: self.children.ui(args.0).arr.to_vec(),
dir: self.dir, dir: self.dir,
gap: self.gap, gap: self.gap,
} }
} }
} }
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> SpanBuilder<LEN, Wa, Tag> {
SpanBuilder<State, LEN, Wa, Tag>
{
pub fn new(children: Wa, dir: Dir) -> Self { pub fn new(children: Wa, dir: Dir) -> Self {
Self { Self {
children, children,
@@ -193,7 +182,7 @@ impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
} }
impl std::ops::Deref for Span { impl std::ops::Deref for Span {
type Target = Vec<WidgetHandle>; type Target = Vec<WidgetRef>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.children &self.children

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use crate::prelude::*; use crate::prelude::*;
pub struct Stack { pub struct Stack {
pub children: Vec<WidgetHandle>, pub children: Vec<WidgetRef>,
pub size: StackSize, pub size: StackSize,
} }
@@ -42,29 +42,26 @@ pub enum StackSize {
Child(usize), Child(usize),
} }
pub struct StackBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> { pub struct StackBuilder<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
pub children: Wa, pub children: Wa,
pub size: StackSize, pub size: StackSize,
_pd: PhantomData<(State, Tag)>, _pd: PhantomData<Tag>,
} }
impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc> impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> FnOnce<(&mut Ui,)>
for StackBuilder<Rsc, LEN, Wa, Tag> for StackBuilder<LEN, Wa, Tag>
{ {
type Widget = Stack; type Output = Stack;
#[track_caller] extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
fn run(self, rsc: &mut Rsc) -> Self::Widget {
Stack { Stack {
children: self.children.add(rsc).arr.into_iter().collect(), children: self.children.ui(args.0).arr.to_vec(),
size: self.size, size: self.size,
} }
} }
} }
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> StackBuilder<LEN, Wa, Tag> {
StackBuilder<State, LEN, Wa, Tag>
{
pub fn new(children: Wa) -> Self { pub fn new(children: Wa) -> Self {
Self { Self {
children, children,

View File

@@ -1,8 +1,8 @@
use crate::prelude::*; use crate::prelude::*;
use std::marker::{Sized, Unsize};
#[derive(Default)]
pub struct WidgetPtr { pub struct WidgetPtr {
pub inner: Option<WidgetHandle>, pub inner: Option<WidgetRef>,
} }
impl Widget for WidgetPtr { impl Widget for WidgetPtr {
@@ -28,30 +28,3 @@ impl Widget for WidgetPtr {
} }
} }
} }
impl WidgetPtr {
pub fn new() -> Self {
Self::default()
}
pub fn empty() -> Self {
Self {
inner: Default::default(),
}
}
pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: WidgetHandle<W>) {
self.inner = Some(to)
}
pub fn replace<W: ?Sized + Unsize<dyn Widget>>(
&mut self,
to: WidgetHandle<W>,
) -> Option<WidgetHandle> {
self.inner.replace(to)
}
}
impl Default for WidgetPtr {
fn default() -> Self {
Self::empty()
}
}

View File

@@ -1,9 +1,16 @@
use crate::prelude::*; use crate::prelude::*;
use crate::util::Id;
use std::{ use std::{
ops::{BitOr, Deref, DerefMut}, ops::{BitOr, Deref, DerefMut},
rc::Rc, rc::Rc,
}; };
use crate::{
layout::{UiModule, UiRegion, Vec2},
util::HashMap,
};
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum CursorButton { pub enum CursorButton {
Left, Left,
@@ -22,23 +29,8 @@ pub enum CursorSense {
Scroll, Scroll,
} }
#[derive(Clone)]
pub struct CursorSenses(Vec<CursorSense>); pub struct CursorSenses(Vec<CursorSense>);
impl Event for CursorSenses {
type Data = CursorData;
type State = SensorState;
fn should_run(&self, data: &Self::Data) -> Option<Self::Data> {
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
let mut data = data.clone();
data.sense = sense;
Some(data)
} else {
None
}
}
}
impl CursorSense { impl CursorSense {
pub fn click() -> Self { pub fn click() -> Self {
Self::PressStart(CursorButton::Left) Self::PressStart(CursorButton::Left)
@@ -83,16 +75,6 @@ impl CursorButtons {
self.middle.end_frame(); self.middle.end_frame();
self.right.end_frame(); self.right.end_frame();
} }
pub fn iter(&self) -> impl Iterator<Item = (CursorButton, &ActivationState)> {
[
CursorButton::Left,
CursorButton::Middle,
CursorButton::Right,
]
.into_iter()
.map(|b| (b, self.select(&b)))
}
} }
impl CursorState { impl CursorState {
@@ -116,70 +98,135 @@ pub enum ActivationState {
/// or basically have some way to have custom senses /// or basically have some way to have custom senses
/// that depend on active widget positions /// that depend on active widget positions
/// but I'm not sure how or if worth it /// but I'm not sure how or if worth it
pub struct Sensor<Ctx: HasEvents, Data> { pub struct Sensor<Ctx, Data> {
pub senses: CursorSenses, pub senses: CursorSenses,
pub f: Rc<dyn EventFn<Ctx, Data>>, pub f: Rc<dyn EventFn<Ctx, Data>>,
} }
pub type SensorMap<Ctx, Data> = HashMap<Id, SensorGroup<Ctx, Data>>;
pub type SenseShape = UiRegion; pub type SenseShape = UiRegion;
pub struct SensorGroup<Ctx, Data> {
#[derive(Default, Debug)]
pub struct SensorState {
pub hover: ActivationState, pub hover: ActivationState,
pub sensors: Vec<Sensor<Ctx, Data>>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct CursorData { pub struct CursorData {
/// where this widget was hit pub cursor: Vec2,
pub pos: Vec2,
pub size: Vec2, pub size: Vec2,
pub scroll_delta: Vec2, pub scroll_delta: Vec2,
pub hover: ActivationState, /// the (first) sense that triggered this event
pub cursor: CursorState, /// the senses are checked in order
/// the first sense that triggered this
pub sense: CursorSense, pub sense: CursorSense,
} }
pub trait SensorUi<Rsc: HasEvents> { pub struct CursorModule<Ctx> {
fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2); map: SensorMap<Ctx, CursorData>,
active: HashMap<usize, HashMap<Id, SenseShape>>,
} }
impl<Rsc: HasEvents> SensorUi<Rsc> for Rsc { impl<Ctx: 'static> UiModule for CursorModule<Ctx> {
fn run_sensors(&mut self, state: &mut Rsc::State, cursor: CursorState, window_size: Vec2) { fn on_draw(&mut self, inst: &WidgetInstance) {
let layers = std::mem::take(&mut self.ui_mut().layers); if self.map.contains_key(&inst.id) {
let mut active = std::mem::take(&mut self.events_mut().get_type::<CursorSense>().active); self.active
for layer in layers.indices().rev() { .entry(inst.layer)
.or_default()
.insert(inst.id, inst.region);
}
}
fn on_undraw(&mut self, inst: &WidgetInstance) {
if let Some(layer) = self.active.get_mut(&inst.layer) {
layer.remove(&inst.id);
}
}
fn on_remove(&mut self, id: &Id) {
self.map.remove(id);
for layer in self.active.values_mut() {
layer.remove(id);
}
}
fn on_move(&mut self, inst: &WidgetInstance) {
if let Some(map) = self.active.get_mut(&inst.layer)
&& let Some(region) = map.get_mut(&inst.id)
{
*region = inst.region;
}
}
}
impl<Ctx> CursorModule<Ctx> {
pub fn merge(&mut self, other: Self) {
for (id, group) in other.map {
for sensor in group.sensors {
self.map.entry(id).or_default().sensors.push(sensor);
}
}
}
}
pub trait SensorUi {
fn run_sensors<Ctx: 'static>(&mut self, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2);
}
impl SensorUi for Ui {
fn run_sensors<Ctx: 'static>(
&mut self,
ctx: &mut Ctx,
cursor: &CursorState,
window_size: Vec2,
) {
CursorModule::<Ctx>::run(self, ctx, cursor, window_size);
}
}
impl<Ctx: 'static> CursorModule<Ctx> {
pub fn run(ui: &mut Ui, ctx: &mut Ctx, cursor: &CursorState, window_size: Vec2) {
let layers = std::mem::take(&mut ui.data_mut().layers);
let mut module = std::mem::take(ui.data_mut().modules.get_mut::<Self>());
for i in layers.indices().rev() {
let Some(list) = module.active.get_mut(&i) else {
continue;
};
let mut sensed = false; let mut sensed = false;
for (id, sensor) in active.get_mut(&layer).into_flat_iter() { for (id, shape) in list.iter() {
let shape = self.ui().active.get(id).unwrap().region; let group = module.map.get_mut(id).unwrap();
let region = shape.to_px(window_size); let region = shape.to_px(window_size);
let in_shape = cursor.exists && region.contains(cursor.pos); let in_shape = cursor.exists && region.contains(cursor.pos);
sensor.hover.update(in_shape); group.hover.update(in_shape);
if sensor.hover == ActivationState::Off { if group.hover == ActivationState::Off {
continue; continue;
} }
sensed = true; sensed = true;
let cursor = cursor.clone(); for sensor in &mut group.sensors {
if let Some(sense) = should_run(&sensor.senses, cursor, group.hover) {
let data = CursorData { let data = CursorData {
pos: cursor.pos - region.top_left, cursor: 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: sensor.hover, sense,
cursor,
// this does not have any meaning;
// might wanna set up Event to have a prepare stage
sense: CursorSense::Hovering,
}; };
self.run_event::<CursorSense>(*id, data, state); (sensor.f)(EventCtx {
ui,
state: ctx,
data,
});
}
}
} }
if sensed { if sensed {
break; break;
} }
} }
self.events_mut().get_type::<CursorSense>().active = active;
self.ui_mut().layers = layers; let ui_mod = ui.data_mut().modules.get_mut::<Self>();
std::mem::swap(ui_mod, &mut module);
ui_mod.merge(module);
ui.data_mut().layers = layers;
} }
} }
@@ -247,10 +294,66 @@ impl ActivationState {
} }
} }
impl EventLike for CursorSense { impl Event for CursorSenses {
type Event = CursorSenses; type Module<Ctx: 'static> = CursorModule<Ctx>;
fn into_event(self) -> Self::Event { type Data = CursorData;
self.into() }
impl Event for CursorSense {
type Module<Ctx: 'static> = CursorModule<Ctx>;
type Data = CursorData;
}
impl<E: Event<Data = <CursorSenses as Event>::Data> + Into<CursorSenses>, Ctx: 'static>
EventModule<E, Ctx> for CursorModule<Ctx>
{
fn register(&mut self, id: Id, senses: E, f: impl EventFn<Ctx, <E as Event>::Data>) {
// TODO: does not add to active if currently active
self.map.entry(id).or_default().sensors.push(Sensor {
senses: senses.into(),
f: Rc::new(f),
});
}
fn run<'a>(
&self,
id: &Id,
event: E,
) -> Option<impl Fn(EventCtx<Ctx, <E as Event>::Data>) + use<'a, E, Ctx>> {
let senses = event.into();
if let Some(group) = self.map.get(id) {
let fs: Vec<_> = group
.sensors
.iter()
.filter_map(|sensor| {
if sensor.senses.iter().any(|s| senses.contains(s)) {
Some(sensor.f.clone())
} else {
None
}
})
.collect();
Some(move |ctx: EventCtx<Ctx, CursorData>| {
for f in &fs {
f(EventCtx {
state: ctx.state,
ui: ctx.ui,
data: ctx.data.clone(),
});
}
})
} else {
None
}
}
}
impl<Ctx, Data> Default for SensorGroup<Ctx, Data> {
fn default() -> Self {
Self {
hover: Default::default(),
sensors: Default::default(),
}
} }
} }
@@ -290,3 +393,12 @@ impl BitOr<CursorSense> for CursorSenses {
self self
} }
} }
impl<Ctx> Default for CursorModule<Ctx> {
fn default() -> Self {
Self {
map: Default::default(),
active: Default::default(),
}
}
}

View File

@@ -1,16 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use cosmic_text::{Attrs, Family, Metrics}; use cosmic_text::{Attrs, Family, Metrics};
use std::marker::{PhantomData, Sized}; use std::marker::Sized;
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> { pub struct TextBuilder<O = TextOutput, H: WidgetOption = ()> {
pub content: String, pub content: String,
pub attrs: TextAttrs, pub attrs: TextAttrs,
pub hint: H, pub hint: H,
pub output: O, pub output: O,
state: PhantomData<State>,
} }
impl<State, O, H: WidgetOption<State>> TextBuilder<State, O, H> { impl<O, H: WidgetOption> TextBuilder<O, H> {
pub fn size(mut self, size: impl UiNum) -> Self { pub fn size(mut self, size: impl UiNum) -> Self {
self.attrs.font_size = size.to_f32(); self.attrs.font_size = size.to_f32();
self.attrs.line_height = self.attrs.font_size * LINE_HEIGHT_MULT; self.attrs.line_height = self.attrs.font_size * LINE_HEIGHT_MULT;
@@ -40,54 +39,43 @@ impl<State, O, H: WidgetOption<State>> TextBuilder<State, O, H> {
self.attrs.wrap = wrap; self.attrs.wrap = wrap;
self self
} }
pub fn editable(self, mode: EditMode) -> TextBuilder<State, TextEditOutput, H> { pub fn editable(self, single_line: bool) -> TextBuilder<TextEditOutput, H> {
TextBuilder { TextBuilder {
content: self.content, content: self.content,
attrs: self.attrs, attrs: self.attrs,
hint: self.hint, hint: self.hint,
output: TextEditOutput { mode }, output: TextEditOutput { single_line },
state: PhantomData,
} }
} }
} }
impl<Rsc: HasUi, O> TextBuilder<Rsc, O> { impl<O> TextBuilder<O> {
pub fn hint<W: WidgetLike<Rsc, Tag>, Tag>( pub fn hint<W: WidgetLike<Tag>, Tag>(self, hint: W) -> TextBuilder<O, impl WidgetOption> {
self,
hint: W,
) -> TextBuilder<Rsc, O, impl WidgetOption<Rsc>> {
TextBuilder { TextBuilder {
content: self.content, content: self.content,
attrs: self.attrs, attrs: self.attrs,
hint: move |rsc: &mut Rsc| Some(hint.add_strong(rsc).any()), hint: move |ui: &mut Ui| Some(hint.add(ui).any()),
output: self.output, output: self.output,
state: PhantomData,
} }
} }
} }
pub trait TextBuilderOutput<State>: Sized { pub trait TextBuilderOutput: Sized {
type Output; type Output;
fn run<H: WidgetOption<State>>( fn run<H: WidgetOption>(ui: &mut Ui, builder: TextBuilder<Self, H>) -> Self::Output;
state: &mut State,
builder: TextBuilder<State, Self, H>,
) -> Self::Output;
} }
pub struct TextOutput; pub struct TextOutput;
impl<Rsc: HasUi> TextBuilderOutput<Rsc> for TextOutput { impl TextBuilderOutput for TextOutput {
type Output = Text; type Output = Text;
fn run<H: WidgetOption<Rsc>>( fn run<H: WidgetOption>(ui: &mut Ui, builder: TextBuilder<Self, H>) -> Self::Output {
state: &mut Rsc,
builder: TextBuilder<Rsc, Self, H>,
) -> Self::Output {
let mut buf = TextBuffer::new_empty(Metrics::new( let mut buf = TextBuffer::new_empty(Metrics::new(
builder.attrs.font_size, builder.attrs.font_size,
builder.attrs.line_height, builder.attrs.line_height,
)); ));
let hint = builder.hint.get(state); let hint = builder.hint.get(ui);
let font_system = &mut state.ui_mut().text.font_system; let font_system = &mut ui.data().text.get_mut().font_system;
buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
let mut text = Text { let mut text = Text {
content: builder.content.into(), content: builder.content.into(),
@@ -100,25 +88,22 @@ impl<Rsc: HasUi> TextBuilderOutput<Rsc> for TextOutput {
} }
pub struct TextEditOutput { pub struct TextEditOutput {
mode: EditMode, single_line: bool,
} }
impl TextBuilderOutput for TextEditOutput {
impl<State: HasUi> TextBuilderOutput<State> for TextEditOutput {
type Output = TextEdit; type Output = TextEdit;
fn run<H: WidgetOption<State>>( fn run<H: WidgetOption>(ui: &mut Ui, builder: TextBuilder<Self, H>) -> Self::Output {
state: &mut State,
builder: TextBuilder<State, Self, H>,
) -> Self::Output {
let buf = TextBuffer::new_empty(Metrics::new( let buf = TextBuffer::new_empty(Metrics::new(
builder.attrs.font_size, builder.attrs.font_size,
builder.attrs.line_height, builder.attrs.line_height,
)); ));
let mut text = TextEdit::new( let mut text = TextEdit::new(
TextView::new(buf, builder.attrs, builder.hint.get(state)), TextView::new(buf, builder.attrs, builder.hint.get(ui)),
builder.output.mode, builder.output.single_line,
ui.data().text.clone(),
); );
let font_system = &mut state.ui_mut().text.font_system; let font_system = &mut ui.data().text.get_mut().font_system;
text.buf text.buf
.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); .set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None);
builder.attrs.apply(font_system, &mut text.buf, None); builder.attrs.apply(font_system, &mut text.buf, None);
@@ -126,22 +111,19 @@ impl<State: HasUi> TextBuilderOutput<State> for TextEditOutput {
} }
} }
impl<State, O: TextBuilderOutput<State>, H: WidgetOption<State>> FnOnce<(&mut State,)> impl<O: TextBuilderOutput, H: WidgetOption> FnOnce<(&mut Ui,)> for TextBuilder<O, H> {
for TextBuilder<State, O, H>
{
type Output = O::Output; type Output = O::Output;
extern "rust-call" fn call_once(self, args: (&mut State,)) -> Self::Output { extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
O::run(args.0, self) O::run(args.0, self)
} }
} }
pub fn wtext<State>(content: impl Into<String>) -> TextBuilder<State> { pub fn wtext(content: impl Into<String>) -> TextBuilder {
TextBuilder { TextBuilder {
content: content.into(), content: content.into(),
attrs: TextAttrs::default(), attrs: TextAttrs::default(),
hint: (), hint: (),
output: TextOutput, output: TextOutput,
state: PhantomData,
} }
} }

View File

@@ -1,6 +1,7 @@
use crate::prelude::*;
use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, LayoutRun, Motion};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use crate::prelude::*;
use cosmic_text::{Affinity, Attrs, Cursor, LayoutRun, Motion};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use winit::{ use winit::{
event::KeyEvent, event::KeyEvent,
@@ -12,23 +13,19 @@ pub struct TextEdit {
selection: TextSelection, selection: TextSelection,
history: Vec<(String, TextSelection)>, history: Vec<(String, TextSelection)>,
double_hit: Option<Cursor>, double_hit: Option<Cursor>,
pub mode: EditMode, data: TextData,
} pub single_line: bool,
#[derive(Clone, Copy, PartialEq)]
pub enum EditMode {
SingleLine,
MultiLine,
} }
impl TextEdit { impl TextEdit {
pub fn new(view: TextView, mode: EditMode) -> Self { pub fn new(view: TextView, single_line: bool, data: TextData) -> Self {
Self { Self {
view, view,
selection: Default::default(), selection: Default::default(),
history: Default::default(), history: Default::default(),
double_hit: None, double_hit: None,
mode, single_line,
data,
} }
} }
pub fn select_content(&self, start: Cursor, end: Cursor) -> String { pub fn select_content(&self, start: Cursor, end: Cursor) -> String {
@@ -47,6 +44,369 @@ impl TextEdit {
str str
} }
} }
pub fn take(&mut self) -> String {
let text = self
.buf
.lines
.drain(..)
.map(|l| l.into_text())
.collect::<Vec<_>>()
.join("\n");
self.set("");
text
}
pub fn set(&mut self, text: &str) {
let text = self.string(text);
self.view.buf.set_text(
&mut self.data.get_mut().font_system,
&text,
&Attrs::new(),
SHAPING,
None,
);
self.selection.clear();
}
pub fn motion(&mut self, motion: Motion, select: bool) {
if let TextSelection::Pos(cursor) = self.selection
&& let Some(new) = self.buf_motion(cursor, motion)
{
self.selection = if select {
TextSelection::Span {
start: cursor,
end: new,
}
} else {
TextSelection::Pos(new)
};
} else if let TextSelection::Span { start, end } = self.selection {
if select {
if let Some(cursor) = self.buf_motion(end, motion) {
self.selection = TextSelection::Span { start, end: cursor };
}
} else {
let (start, end) = sort_cursors(start, end);
match motion {
Motion::Left | Motion::LeftWord => self.selection = TextSelection::Pos(start),
Motion::Right | Motion::RightWord => self.selection = TextSelection::Pos(end),
_ => {
if let Some(cursor) = self.buf_motion(end, motion) {
self.selection = TextSelection::Pos(cursor);
}
}
}
}
}
}
pub fn replace(&mut self, len: usize, text: &str) {
let text = self.string(text);
for _ in 0..len {
self.delete(false);
}
self.insert_inner(&text, false);
}
fn string(&self, text: &str) -> String {
if self.single_line {
text.replace('\n', "")
} else {
text.to_string()
}
}
pub fn insert(&mut self, text: &str) {
let text = self.string(text);
let mut lines = text.split('\n');
let Some(first) = lines.next() else {
return;
};
self.insert_inner(first, true);
for line in lines {
self.newline();
self.insert_inner(line, true);
}
}
pub fn clear_span(&mut self) -> bool {
if let TextSelection::Span { start, end } = self.selection {
self.delete_between(start, end);
let (start, _) = sort_cursors(start, end);
self.selection = TextSelection::Pos(start);
true
} else {
false
}
}
pub fn delete_between(&mut self, start: Cursor, end: Cursor) {
let lines = &mut self.view.buf.lines;
let (start, end) = sort_cursors(start, end);
if start.line == end.line {
let line = &mut lines[start.line];
let text = line.text();
let text = text[..start.index].to_string() + &text[end.index..];
edit_line(line, text);
} else {
// start
let start_text = lines[start.line].text()[..start.index].to_string();
let end_text = &lines[end.line].text()[end.index..];
let text = start_text + end_text;
edit_line(&mut lines[start.line], text);
}
// between
let range = (start.line + 1)..=end.line;
if !range.is_empty() {
lines.splice(range, None);
}
}
fn insert_inner(&mut self, text: &str, mov: bool) {
self.clear_span();
if let TextSelection::Pos(cursor) = self.selection {
let line = &mut self.view.buf.lines[cursor.line];
let mut line_text = line.text().to_string();
line_text.insert_str(cursor.index, text);
edit_line(line, line_text);
if mov {
for _ in 0..text.chars().count() {
self.motion(Motion::Right, false);
}
}
}
}
pub fn newline(&mut self) {
if self.single_line {
return;
}
self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.selection {
let lines = &mut self.view.buf.lines;
let line = &mut lines[cursor.line];
let new = line.split_off(cursor.index);
cursor.line += 1;
lines.insert(cursor.line, new);
cursor.index = 0;
}
}
pub fn backspace(&mut self, word: bool) {
if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.selection
&& (cursor.index != 0 || cursor.line != 0)
{
self.motion(if word { Motion::LeftWord } else { Motion::Left }, false);
self.delete(word);
}
}
pub fn delete(&mut self, word: bool) {
if self.clear_span() {
return;
}
if let TextSelection::Pos(cursor) = &mut self.selection {
if word {
let start = *cursor;
if let Some(end) = self.buf_motion(start, Motion::RightWord) {
self.delete_between(start, end);
}
} else {
let lines = &mut self.view.buf.lines;
let line = &mut lines[cursor.line];
if cursor.index == line.text().len() {
if cursor.line == lines.len() - 1 {
return;
}
let add = lines.remove(cursor.line + 1).into_text();
let line = &mut lines[cursor.line];
let mut cur = line.text().to_string();
cur.push_str(&add);
edit_line(line, cur);
} else {
let mut text = line.text().to_string();
text.remove(cursor.index);
edit_line(line, text);
}
}
}
}
fn buf_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
self.view
.buf
.cursor_motion(
&mut self.data.get_mut().font_system,
cursor,
None,
motion,
)
.map(|r| r.0)
}
pub fn select_word_at(&mut self, cursor: Cursor) {
if let (Some(start), Some(end)) = (
self.buf_motion(cursor, Motion::LeftWord),
self.buf_motion(cursor, Motion::RightWord),
) {
self.selection = TextSelection::Span { start, end };
}
}
pub fn select_line_at(&mut self, cursor: Cursor) {
let end = self.buf.lines[cursor.line].text().len();
self.selection = TextSelection::Span {
start: Cursor::new(cursor.line, 0),
end: Cursor::new(cursor.line, end),
}
}
pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool, recent: bool) {
let pos = pos - self.region().top_left().to_abs(size);
let hit = self.buf.hit(pos.x, pos.y);
let sel = &mut self.selection;
match sel {
TextSelection::None => {
if !drag && let Some(hit) = hit {
*sel = TextSelection::Pos(hit)
}
}
TextSelection::Pos(pos) => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => (),
(Some(hit), false) => {
if recent && hit == *pos {
self.double_hit = Some(hit);
return self.select_word_at(hit);
} else {
*pos = hit
}
}
(Some(end), true) => *sel = TextSelection::Span { start: *pos, end },
},
TextSelection::Span { start, end } => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => *sel = TextSelection::Pos(*start),
(Some(hit), false) => {
if recent
&& let Some(double) = self.double_hit
&& double == hit
{
return self.select_line_at(hit);
} else {
*sel = TextSelection::Pos(hit)
}
}
(Some(hit), true) => *end = hit,
},
}
if let TextSelection::Span { start, end } = sel
&& start == end
{
*sel = TextSelection::Pos(*start);
}
}
pub fn deselect(&mut self) {
self.selection = TextSelection::None;
}
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
let old = (self.content(), self.selection);
let mut undo = false;
let res = self.apply_event_inner(event, modifiers, &mut undo);
if undo && let Some((old, selection)) = self.history.pop() {
self.set(&old);
self.selection = selection;
} else if self.content() != old.0 {
self.history.push(old);
}
res
}
fn apply_event_inner(
&mut self,
event: &KeyEvent,
modifiers: &Modifiers,
undo: &mut bool,
) -> TextInputResult {
match &event.logical_key {
Key::Named(named) => match named {
NamedKey::Backspace => self.backspace(modifiers.control),
NamedKey::Delete => self.delete(modifiers.control),
NamedKey::Space => self.insert(" "),
NamedKey::Enter => {
if modifiers.shift {
self.newline();
} else {
return TextInputResult::Submit;
}
}
NamedKey::ArrowRight => {
if modifiers.control {
self.motion(Motion::RightWord, modifiers.shift)
} else {
self.motion(Motion::Right, modifiers.shift)
}
}
NamedKey::ArrowLeft => {
if modifiers.control {
self.motion(Motion::LeftWord, modifiers.shift)
} else {
self.motion(Motion::Left, modifiers.shift)
}
}
NamedKey::ArrowUp => self.motion(Motion::Up, modifiers.shift),
NamedKey::ArrowDown => self.motion(Motion::Down, modifiers.shift),
NamedKey::Escape => {
self.deselect();
return TextInputResult::Unfocus;
}
_ => return TextInputResult::Unused,
},
Key::Character(text) => {
if modifiers.control {
match text.as_str() {
"v" => return TextInputResult::Paste,
"c" => {
if let TextSelection::Span { start, end } = self.selection {
let content = self.select_content(start, end);
return TextInputResult::Copy(content);
}
}
"x" => {
if let TextSelection::Span { start, end } = self.selection {
let content = self.select_content(start, end);
self.clear_span();
return TextInputResult::Copy(content);
}
}
"a" => {
if !self.buf.lines[0].text().is_empty() || self.buf.lines.len() > 1 {
let lines = &self.buf.lines;
let last_line = lines.len() - 1;
let last_idx = lines[last_line].text().len();
self.selection = TextSelection::Span {
start: Cursor::new(0, 0),
end: Cursor::new(last_line, last_idx),
};
}
}
"z" => {
*undo = true;
}
_ => self.insert(text),
}
} else {
self.insert(text);
}
}
_ => return TextInputResult::Unused,
}
TextInputResult::Used
}
} }
impl Widget for TextEdit { impl Widget for TextEdit {
@@ -180,373 +540,6 @@ fn cursor_pos(cursor: Cursor, buf: &TextBuffer) -> Option<Vec2> {
prev prev
} }
pub struct TextEditCtx<'a> {
pub text: &'a mut TextEdit,
pub font_system: &'a mut FontSystem,
}
impl<'a> TextEditCtx<'a> {
pub fn take(&mut self) -> String {
let text = self
.text
.buf
.lines
.drain(..)
.map(|l| l.into_text())
.collect::<Vec<_>>()
.join("\n");
self.text
.buf
.set_text(self.font_system, "", &Attrs::new(), SHAPING, None);
self.text.selection.clear();
text
}
pub fn set(&mut self, text: &str) {
let text = self.string(text);
self.text
.buf
.set_text(self.font_system, &text, &Attrs::new(), SHAPING, None);
self.text.selection.clear();
}
pub fn motion(&mut self, motion: Motion, select: bool) {
if let TextSelection::Pos(cursor) = self.text.selection
&& let Some(new) = self.buf_motion(cursor, motion)
{
if select {
self.text.selection = TextSelection::Span {
start: cursor,
end: new,
};
} else {
self.text.selection = TextSelection::Pos(new);
}
} else if let TextSelection::Span { start, end } = self.text.selection {
if select {
if let Some(cursor) = self.buf_motion(end, motion) {
self.text.selection = TextSelection::Span { start, end: cursor };
}
} else {
let (start, end) = sort_cursors(start, end);
let sel = &mut self.text.selection;
match motion {
Motion::Left | Motion::LeftWord => *sel = TextSelection::Pos(start),
Motion::Right | Motion::RightWord => *sel = TextSelection::Pos(end),
_ => {
if let Some(cursor) = self.buf_motion(end, motion) {
self.text.selection = TextSelection::Pos(cursor);
}
}
}
}
}
}
pub fn replace(&mut self, len: usize, text: &str) {
let text = self.string(text);
for _ in 0..len {
self.delete(false);
}
self.insert_inner(&text, false);
}
fn string(&self, text: &str) -> String {
if self.text.mode == EditMode::SingleLine {
text.replace('\n', "")
} else {
text.to_string()
}
}
pub fn insert(&mut self, text: &str) {
let text = self.string(text);
let mut lines = text.split('\n');
let Some(first) = lines.next() else {
return;
};
self.insert_inner(first, true);
for line in lines {
self.newline();
self.insert_inner(line, true);
}
}
pub fn clear_span(&mut self) -> bool {
if let TextSelection::Span { start, end } = self.text.selection {
self.delete_between(start, end);
let (start, _) = sort_cursors(start, end);
self.text.selection = TextSelection::Pos(start);
true
} else {
false
}
}
pub fn delete_between(&mut self, start: Cursor, end: Cursor) {
let lines = &mut self.text.view.buf.lines;
let (start, end) = sort_cursors(start, end);
if start.line == end.line {
let line = &mut lines[start.line];
let text = line.text();
let text = text[..start.index].to_string() + &text[end.index..];
edit_line(line, text);
} else {
// start
let start_text = lines[start.line].text()[..start.index].to_string();
let end_text = &lines[end.line].text()[end.index..];
let text = start_text + end_text;
edit_line(&mut lines[start.line], text);
}
// between
let range = (start.line + 1)..=end.line;
if !range.is_empty() {
lines.splice(range, None);
}
}
fn insert_inner(&mut self, text: &str, mov: bool) {
self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection {
let line = &mut self.text.view.buf.lines[cursor.line];
let mut line_text = line.text().to_string();
line_text.insert_str(cursor.index, text);
edit_line(line, line_text);
if mov {
for _ in 0..text.chars().count() {
self.motion(Motion::Right, false);
}
}
}
}
pub fn newline(&mut self) {
if self.text.mode == EditMode::SingleLine {
return;
}
self.clear_span();
if let TextSelection::Pos(cursor) = &mut self.text.selection {
let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line];
let new = line.split_off(cursor.index);
cursor.line += 1;
lines.insert(cursor.line, new);
cursor.index = 0;
}
}
pub fn backspace(&mut self, word: bool) {
if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.text.selection
&& (cursor.index != 0 || cursor.line != 0)
{
self.motion(if word { Motion::LeftWord } else { Motion::Left }, false);
self.delete(word);
}
}
pub fn delete(&mut self, word: bool) {
if !self.clear_span()
&& let TextSelection::Pos(cursor) = &mut self.text.selection
{
if word {
let start = *cursor;
if let Some(end) = self.buf_motion(start, Motion::RightWord) {
self.delete_between(start, end);
}
} else {
let lines = &mut self.text.view.buf.lines;
let line = &mut lines[cursor.line];
if cursor.index == line.text().len() {
if cursor.line == lines.len() - 1 {
return;
}
let add = lines.remove(cursor.line + 1).into_text();
let line = &mut lines[cursor.line];
let mut cur = line.text().to_string();
cur.push_str(&add);
edit_line(line, cur);
} else {
let mut text = line.text().to_string();
text.remove(cursor.index);
edit_line(line, text);
}
}
}
}
fn buf_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
self.text
.buf
.cursor_motion(self.font_system, cursor, None, motion)
.map(|r| r.0)
}
pub fn select_word_at(&mut self, cursor: Cursor) {
if let (Some(start), Some(end)) = (
self.buf_motion(cursor, Motion::LeftWord),
self.buf_motion(cursor, Motion::RightWord),
) {
self.text.selection = TextSelection::Span { start, end };
}
}
pub fn select_line_at(&mut self, cursor: Cursor) {
let end = self.text.buf.lines[cursor.line].text().len();
self.text.selection = TextSelection::Span {
start: Cursor::new(cursor.line, 0),
end: Cursor::new(cursor.line, end),
}
}
pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool, recent: bool) {
let pos = pos - self.text.region().top_left().to_abs(size);
let hit = self.text.buf.hit(pos.x, pos.y);
let sel = &mut self.text.selection;
match sel {
TextSelection::None => {
if !drag && let Some(hit) = hit {
*sel = TextSelection::Pos(hit)
}
}
TextSelection::Pos(pos) => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => (),
(Some(hit), false) => {
if recent && hit == *pos {
self.text.double_hit = Some(hit);
return self.select_word_at(hit);
} else {
*pos = hit
}
}
(Some(end), true) => *sel = TextSelection::Span { start: *pos, end },
},
TextSelection::Span { start, end } => match (hit, drag) {
(None, false) => *sel = TextSelection::None,
(None, true) => *sel = TextSelection::Pos(*start),
(Some(hit), false) => {
if recent
&& let Some(double) = self.text.double_hit
&& double == hit
{
return self.select_line_at(hit);
} else {
*sel = TextSelection::Pos(hit)
}
}
(Some(hit), true) => *end = hit,
},
}
if let TextSelection::Span { start, end } = sel
&& start == end
{
*sel = TextSelection::Pos(*start);
}
}
pub fn deselect(&mut self) {
self.text.selection = TextSelection::None;
}
pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult {
let old = (self.text.content(), self.text.selection);
let mut undo = false;
let res = self.apply_event_inner(event, modifiers, &mut undo);
if undo && let Some((old, selection)) = self.text.history.pop() {
self.set(&old);
self.text.selection = selection;
} else if self.text.content() != old.0 {
self.text.history.push(old);
}
res
}
fn apply_event_inner(
&mut self,
event: &KeyEvent,
modifiers: &Modifiers,
undo: &mut bool,
) -> TextInputResult {
match &event.logical_key {
Key::Named(named) => match named {
NamedKey::Backspace => self.backspace(modifiers.control),
NamedKey::Delete => self.delete(modifiers.control),
NamedKey::Space => self.insert(" "),
NamedKey::Enter => {
if modifiers.shift {
self.newline();
} else {
return TextInputResult::Submit;
}
}
NamedKey::ArrowRight => {
if modifiers.control {
self.motion(Motion::RightWord, modifiers.shift)
} else {
self.motion(Motion::Right, modifiers.shift)
}
}
NamedKey::ArrowLeft => {
if modifiers.control {
self.motion(Motion::LeftWord, modifiers.shift)
} else {
self.motion(Motion::Left, modifiers.shift)
}
}
NamedKey::ArrowUp => self.motion(Motion::Up, modifiers.shift),
NamedKey::ArrowDown => self.motion(Motion::Down, modifiers.shift),
NamedKey::Escape => {
self.deselect();
return TextInputResult::Unfocus;
}
_ => return TextInputResult::Unused,
},
Key::Character(text) => {
if modifiers.control {
match text.as_str() {
"v" => return TextInputResult::Paste,
"c" => {
if let TextSelection::Span { start, end } = self.text.selection {
let content = self.text.select_content(start, end);
return TextInputResult::Copy(content);
}
}
"x" => {
if let TextSelection::Span { start, end } = self.text.selection {
let content = self.text.select_content(start, end);
self.clear_span();
return TextInputResult::Copy(content);
}
}
"a" => {
if !self.text.buf.lines[0].text().is_empty()
|| self.text.buf.lines.len() > 1
{
let lines = &self.text.buf.lines;
let last_line = lines.len() - 1;
let last_idx = lines[last_line].text().len();
self.text.selection = TextSelection::Span {
start: Cursor::new(0, 0),
end: Cursor::new(last_line, last_idx),
};
}
}
"z" => {
*undo = true;
}
_ => self.insert(text),
}
} else {
self.insert(text);
}
}
_ => return TextInputResult::Unused,
}
TextInputResult::Used
}
}
#[derive(Default)] #[derive(Default)]
pub struct Modifiers { pub struct Modifiers {
pub shift: bool, pub shift: bool,
@@ -615,17 +608,3 @@ impl DerefMut for TextEdit {
&mut self.view &mut self.view
} }
} }
pub trait TextEditable {
fn edit<'a>(&self, ui: &'a mut impl HasUi) -> TextEditCtx<'a>;
}
impl<I: IdLike<Widget = TextEdit>> TextEditable for I {
fn edit<'a>(&self, ui: &'a mut impl HasUi) -> TextEditCtx<'a> {
let ui = ui.ui_mut();
TextEditCtx {
text: ui.widgets.get_mut(self).unwrap(),
font_system: &mut ui.text.font_system,
}
}
}

View File

@@ -3,9 +3,8 @@ mod edit;
pub use build::*; pub use build::*;
pub use edit::*; pub use edit::*;
use iris_core::util::MutDetect;
use crate::prelude::*; use crate::{prelude::*, util::MutDetect};
use cosmic_text::{Attrs, BufferLine, Cursor, Metrics, Shaping}; use cosmic_text::{Attrs, BufferLine, Cursor, Metrics, Shaping};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@@ -22,11 +21,11 @@ pub struct TextView {
// cache // cache
tex: Option<RenderedText>, tex: Option<RenderedText>,
width: Option<f32>, width: Option<f32>,
pub hint: Option<WidgetHandle>, pub hint: Option<WidgetRef>,
} }
impl TextView { impl TextView {
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetHandle>) -> Self { pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<WidgetRef>) -> Self {
Self { Self {
attrs: attrs.into(), attrs: attrs.into(),
buf: buf.into(), buf: buf.into(),
@@ -68,9 +67,12 @@ impl TextView {
return tex.clone(); return tex.clone();
} }
self.width = width; self.width = width;
let font_system = &mut ctx.text.font_system; let mut text_data = ctx.text.get_mut();
self.attrs.apply(font_system, &mut self.buf, width); self.attrs
self.buf.shape_until_scroll(font_system, false); .apply(&mut text_data.font_system, &mut self.buf, width);
self.buf
.shape_until_scroll(&mut text_data.font_system, false);
drop(text_data);
let tex = ctx.draw_text(&mut self.buf, &self.attrs); let tex = ctx.draw_text(&mut self.buf, &self.attrs);
self.tex = Some(tex.clone()); self.tex = Some(tex.clone());
self.attrs.changed = false; self.attrs.changed = false;
@@ -137,7 +139,7 @@ impl Text {
if self.content.changed { if self.content.changed {
self.content.changed = false; self.content.changed = false;
self.view.buf.set_text( self.view.buf.set_text(
&mut ctx.text.font_system, &mut ctx.text.get_mut().font_system,
&self.content, &self.content,
&Attrs::new().family(self.view.attrs.family), &Attrs::new().family(self.view.attrs.family),
SHAPING, SHAPING,

View File

@@ -1,148 +1,147 @@
use super::*;
use crate::prelude::*; use crate::prelude::*;
use iris_macro::widget_trait;
// these methods should "not require any context" (require unit) because they're in core // these methods should "not require any context" (require unit) because they're in core
event_ctx!(());
widget_trait! { widget_trait! {
pub trait CoreWidget<Rsc: HasUi + 'static>; pub trait CoreWidget;
fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Rsc, Pad> { fn pad(self, padding: impl Into<Padding>) -> impl WidgetFn<Pad> {
|state| Pad { |ui| Pad {
padding: padding.into(), padding: padding.into(),
inner: self.add_strong(state), inner: self.add(ui),
} }
} }
fn align(self, align: impl Into<Align>) -> impl WidgetFn<Rsc, Aligned> { fn align(self, align: impl Into<Align>) -> impl WidgetFn<Aligned> {
move |state| Aligned { move |ui| Aligned {
inner: self.add_strong(state), inner: self.add(ui),
align: align.into(), align: align.into(),
} }
} }
fn center(self) -> impl WidgetFn<Rsc, Aligned> { fn center(self) -> impl WidgetFn<Aligned> {
self.align(Align::CENTER) self.align(Align::CENTER)
} }
fn label(self, label: impl Into<String>) -> impl WidgetIdFn<Rsc, WL::Widget> { fn label(self, label: impl Into<String>) -> impl WidgetIdFn<WL::Widget> {
|state| { |ui| {
let id = self.add(state); let id = self.add(ui);
state.ui_mut().set_label(id, label.into()); id.set_label(label);
id id
} }
} }
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, Sized> { fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Sized> {
let size = size.into(); let size = size.into();
move |state| Sized { move |ui| Sized {
inner: self.add_strong(state), inner: self.add(ui),
x: Some(size.x), x: Some(size.x),
y: Some(size.y), y: Some(size.y),
} }
} }
fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> { fn max_width(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
let len = len.into(); let len = len.into();
move |state| MaxSize { move |ui| MaxSize {
inner: self.add_strong(state), inner: self.add(ui),
x: Some(len), x: Some(len),
y: None, y: None,
} }
} }
fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, MaxSize> { fn max_height(self, len: impl Into<Len>) -> impl WidgetFn<MaxSize> {
let len = len.into(); let len = len.into();
move |state| MaxSize { move |ui| MaxSize {
inner: self.add_strong(state), inner: self.add(ui),
x: None, x: None,
y: Some(len), y: Some(len),
} }
} }
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> { fn width(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
let len = len.into(); let len = len.into();
move |state| Sized { move |ui| Sized {
inner: self.add_strong(state), inner: self.add(ui),
x: Some(len), x: Some(len),
y: None, y: None,
} }
} }
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> { fn height(self, len: impl Into<Len>) -> impl WidgetFn<Sized> {
let len = len.into(); let len = len.into();
move |state| Sized { move |ui| Sized {
inner: self.add_strong(state), inner: self.add(ui),
x: None, x: None,
y: Some(len), y: Some(len),
} }
} }
fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Rsc, Offset> { fn offset(self, amt: impl Into<UiVec2>) -> impl WidgetFn<Offset> {
move |state| Offset { move |ui| Offset {
inner: self.add_strong(state), inner: self.add(ui),
amt: amt.into(), amt: amt.into(),
} }
} }
fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents { fn scroll(self) -> impl WidgetIdFn<Scroll> {
move |state| { move |ui| {
Scroll::new(self.add_strong(state), Axis::Y) Scroll::new(self.add(ui), Axis::Y)
.on(CursorSense::Scroll, |ctx, rsc| { .on(CursorSense::Scroll, |ctx| {
let delta = ctx.data.scroll_delta.y * 50.0; let s = &mut *ctx.widget.get_mut();
ctx.widget(rsc).scroll(delta); s.scroll(ctx.data.scroll_delta.y * 50.0);
}) })
.add(state) .add(ui)
} }
} }
fn masked(self) -> impl WidgetFn<Rsc, Masked> { fn masked(self) -> impl WidgetFn<Masked> {
move |state| Masked { move |ui| Masked {
inner: self.add_strong(state), inner: self.add(ui),
} }
} }
fn background<T>(self, w: impl WidgetLike<Rsc, T>) -> impl WidgetFn<Rsc, Stack> { fn background<T,>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
move |state| Stack { move |ui| Stack {
children: vec![w.add_strong(state), self.add_strong(state)], children: vec![w.add(ui), self.add(ui)],
size: StackSize::Child(1), size: StackSize::Child(1),
} }
} }
fn foreground<T>(self, w: impl WidgetLike<Rsc, T>) -> impl WidgetFn<Rsc, Stack> { fn foreground<T,>(self, w: impl WidgetLike<T>) -> impl WidgetFn<Stack> {
move |state| Stack { move |ui| Stack {
children: vec![self.add_strong(state), w.add_strong(state)], children: vec![self.add(ui), w.add(ui)],
size: StackSize::Child(0), size: StackSize::Child(0),
} }
} }
fn layer_offset(self, offset: usize) -> impl WidgetFn<Rsc, LayerOffset> { fn layer_offset(self, offset: usize) -> impl WidgetFn<LayerOffset> {
move |state| LayerOffset { move |ui| LayerOffset {
inner: self.add_strong(state), inner: self.add(ui),
offset, offset,
} }
} }
fn to_any(self) -> impl WidgetIdFn<Rsc> { fn to_any(self) -> impl WidgetRet {
|state| self.add(state) |ui| self.add(ui)
} }
fn set_ptr(self, ptr: WidgetRef<WidgetPtr>, state: &mut Rsc) { fn set_ptr(self, ptr: &WidgetRef<WidgetPtr>, ui: &mut Ui) {
let id = self.add_strong(state); ptr.get_mut().inner = Some(self.add(ui));
state.ui_mut()[ptr].inner = Some(id);
} }
} }
pub trait CoreWidgetArr<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> { pub trait CoreWidgetArr<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> {
fn span(self, dir: Dir) -> SpanBuilder<Rsc, LEN, Wa, Tag>; fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag>;
fn stack(self) -> StackBuilder<Rsc, LEN, Wa, Tag>; fn stack(self) -> StackBuilder<LEN, Wa, Tag>;
} }
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> impl<const LEN: usize, Wa: WidgetArrLike<LEN, Tag>, Tag> CoreWidgetArr<LEN, Wa, Tag> for Wa {
CoreWidgetArr<State, LEN, Wa, Tag> for Wa fn span(self, dir: Dir) -> SpanBuilder<LEN, Wa, Tag> {
{
fn span(self, dir: Dir) -> SpanBuilder<State, LEN, Wa, Tag> {
SpanBuilder::new(self, dir) SpanBuilder::new(self, dir)
} }
fn stack(self) -> StackBuilder<State, LEN, Wa, Tag> { fn stack(self) -> StackBuilder<LEN, Wa, Tag> {
StackBuilder::new(self) StackBuilder::new(self)
} }
} }