diff --git a/Cargo.lock b/Cargo.lock index 664605c..9b291e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,81 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +[[package]] +name = "accesskit" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a4b14f3d99c1255dcba8f45621ab1a2e7540a0009652d33989005a4d0bfc6b" +dependencies = [ + "enumn", + "serde", +] + +[[package]] +name = "accesskit_consumer" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6" +dependencies = [ + "accesskit", +] + +[[package]] +name = "accesskit_macos" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7" +dependencies = [ + "accesskit", + "accesskit_consumer", + "objc2 0.3.0-beta.3.patch-leaks.3", + "once_cell", +] + +[[package]] +name = "accesskit_unix" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f46c18d99ba61ad7123dd13eeb0c104436ab6af1df6a1cd8c11054ed394a08" +dependencies = [ + "accesskit", + "accesskit_consumer", + "async-channel", + "async-once-cell", + "atspi", + "futures-lite 1.13.0", + "once_cell", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b" +dependencies = [ + "accesskit", + "accesskit_consumer", + "once_cell", + "paste", + "static_assertions", + "windows 0.48.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5284218aca17d9e150164428a0ebc7b955f70e3a9a78b4c20894513aabf98a67" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "winit", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -42,16 +117,32 @@ dependencies = [ "cfg-if", "getrandom", "once_cell", + "serde", "version_check", "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aligned-vec" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-activity" version = "0.5.2" @@ -130,7 +221,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -166,6 +257,183 @@ dependencies = [ "libloading 0.7.4", ] +[[package]] +name = "async-broadcast" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" +dependencies = [ + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +dependencies = [ + "concurrent-queue", + "event-listener 5.3.0", + "event-listener-strategy 0.5.1", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316" +dependencies = [ + "async-lock 3.3.0", + "async-task", + "concurrent-queue", + "fastrand 2.0.2", + "futures-lite 2.3.0", + "slab", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.27", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" +dependencies = [ + "async-lock 3.3.0", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.3.0", + "parking", + "polling 3.6.0", + "rustix 0.38.32", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-once-cell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" + +[[package]] +name = "async-process" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" +dependencies = [ + "async-io 1.13.0", + "async-lock 2.8.0", + "async-signal", + "blocking", + "cfg-if", + "event-listener 3.1.0", + "futures-lite 1.13.0", + "rustix 0.38.32", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-recursion" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "async-signal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +dependencies = [ + "async-io 2.3.2", + "async-lock 2.8.0", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 0.38.32", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-task" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" + +[[package]] +name = "async-trait" +version = "0.1.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -178,6 +446,54 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" +[[package]] +name = "atspi" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6059f350ab6f593ea00727b334265c4dfc7fd442ee32d264794bd9bdc68e87ca" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92af95f966d2431f962bc632c2e68eda7777330158bf640c4af4249349b2cdf5" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c65e7d70f86d4c0e3b2d585d9bf3f979f0b19d635a336725a88d279f76b939" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite 1.13.0", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6495661273703e7a229356dcbe8c8f38223d697aacfaf0e13590a9ac9977bb52" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + [[package]] name = "autocfg" version = "1.2.0" @@ -222,6 +538,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bincode" version = "1.3.3" @@ -231,6 +553,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit_field" version = "0.10.2" @@ -248,6 +585,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bitmaps" @@ -270,13 +610,41 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-sys" +version = "0.1.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" +dependencies = [ + "objc-sys 0.2.0-beta.2", +] + [[package]] name = "block-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" dependencies = [ - "objc-sys", + "objc-sys 0.3.2", +] + +[[package]] +name = "block2" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" +dependencies = [ + "block-sys 0.1.0-beta.1", + "objc2-encode 2.0.0-pre.2", ] [[package]] @@ -285,8 +653,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" dependencies = [ - "block-sys", - "objc2", + "block-sys 0.2.1", + "objc2 0.4.1", +] + +[[package]] +name = "blocking" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +dependencies = [ + "async-channel", + "async-lock 3.3.0", + "async-task", + "fastrand 2.0.2", + "futures-io", + "futures-lite 2.3.0", + "piper", + "tracing", ] [[package]] @@ -318,7 +702,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -341,8 +725,8 @@ checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ "bitflags 2.5.0", "log", - "polling", - "rustix", + "polling 3.6.0", + "rustix 0.38.32", "slab", "thiserror", ] @@ -354,7 +738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop", - "rustix", + "rustix 0.38.32", "wayland-backend", "wayland-client", ] @@ -397,6 +781,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "chrono" version = "0.4.37" @@ -427,6 +820,36 @@ dependencies = [ "cc", ] +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + [[package]] name = "codegen" version = "0.2.0" @@ -452,6 +875,37 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "combine" version = "4.6.6" @@ -471,6 +925,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -511,6 +974,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -569,6 +1041,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cursor-icon" version = "1.1.0" @@ -588,6 +1070,43 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "5.0.1" @@ -609,6 +1128,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -624,12 +1154,31 @@ dependencies = [ "libloading 0.8.3", ] +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "ecolor" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfe80b1890e1a8cdbffc6044d6872e814aaf6011835a2a5e2db0e5c5c4ef4e" +dependencies = [ + "bytemuck", + "serde", +] + [[package]] name = "ecolor" version = "0.27.1" @@ -637,6 +1186,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb152797942f72b84496eb2ebeff0060240e0bf55096c4525ffa22dd54722d86" dependencies = [ "bytemuck", + "serde", +] + +[[package]] +name = "eframe" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c456c1bb6d13bf68b780257484703d750c70a23ff891ba35f4d6e23a4dbdf26f" +dependencies = [ + "bytemuck", + "cocoa", + "directories-next", + "document-features", + "egui 0.26.2", + "egui-wgpu", + "egui-winit 0.26.2", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image 0.24.9", + "js-sys", + "log", + "objc", + "parking_lot", + "percent-encoding", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", + "ron", + "serde", + "static_assertions", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "winit", +] + +[[package]] +name = "egui" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180f595432a5b615fc6b74afef3955249b86cfea72607b40740a4cd60d5297d0" +dependencies = [ + "accesskit", + "ahash", + "epaint 0.26.2", + "log", + "nohash-hasher", + "ron", + "serde", ] [[package]] @@ -645,10 +1247,51 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d1b8cc14b0b260aa6bd124ef12c8a94f57ffe8e40aa970f3db710c21bb945f3" dependencies = [ + "accesskit", "ahash", - "epaint", + "epaint 0.27.1", "log", "nohash-hasher", + "serde", +] + +[[package]] +name = "egui-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2451324b79718fa3ed7e550a0d18c75dde02e667990a5fa1bc28d53e22ddc041" +dependencies = [ + "egui 0.26.2", + "egui-probe-proc", +] + +[[package]] +name = "egui-probe-proc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbd3e42a3d9f512f23ea73279bc3a31865677d6d9ff273e3f8f5a5a70f1c884" +dependencies = [ + "convert_case", + "proc-easy", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "egui-snarl" +version = "0.2.1" +dependencies = [ + "eframe", + "egui 0.27.1", + "egui-probe", + "egui_extras", + "serde", + "serde_json", + "slab", + "syn 2.0.58", + "tiny-fn", + "wasm-bindgen-futures", ] [[package]] @@ -656,14 +1299,50 @@ name = "egui-vulkano" version = "0.1.0" dependencies = [ "ahash", - "egui", - "egui-winit", - "image", + "egui 0.27.1", + "egui-winit 0.27.1", + "image 0.25.1", "vulkano", "vulkano-shaders", "winit", ] +[[package]] +name = "egui-wgpu" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f2d75e1e70228e7126f828bac05f9fe0e7ea88e9660c8cebe609bb114c61d4" +dependencies = [ + "bytemuck", + "document-features", + "egui 0.26.2", + "epaint 0.26.2", + "log", + "thiserror", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4d44f8d89f70d4480545eb2346b76ea88c3022e9f4706cebc799dbe8b004a2" +dependencies = [ + "accesskit_winit", + "arboard", + "egui 0.26.2", + "log", + "raw-window-handle 0.6.0", + "serde", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + [[package]] name = "egui-winit" version = "0.27.1" @@ -671,7 +1350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3733435d6788c760bb98ce4cb1b8b7a2d953a3a7b421656ba8b3e014019be3d0" dependencies = [ "arboard", - "egui", + "egui 0.27.1", "log", "raw-window-handle 0.6.0", "smithay-clipboard", @@ -680,12 +1359,68 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_extras" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4a6962241a76da5be5e64e41b851ee1c95fda11f76635522a3c82b119b5475" +dependencies = [ + "egui 0.26.2", + "ehttp", + "enum-map", + "image 0.24.9", + "log", + "mime_guess2", + "resvg", + "serde", +] + +[[package]] +name = "egui_glow" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08e3be8728b4c59493dbfec041c657e6725bdeafdbd49aef3f1dbb9e551fa01" +dependencies = [ + "bytemuck", + "egui 0.26.2", + "glow", + "log", + "memoffset 0.9.1", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "ehttp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e598cc2bfc28612f26426259ed99a978270e9433d63ae6d2843e30fb0974cd02" +dependencies = [ + "document-features", + "js-sys", + "ureq", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "either" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "emath" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6916301ecf80448f786cdf3eb51d9dbdd831538732229d49119e2d4312eaaf09" +dependencies = [ + "bytemuck", + "serde", +] + [[package]] name = "emath" version = "0.27.1" @@ -693,6 +1428,77 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555a7cbfcc52c81eb5f8f898190c840fa1c435f67f30b7ef77ce7cf6b7dcd987" dependencies = [ "bytemuck", + "serde", +] + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "enumflags2" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3278c9d5fb675e0a51dabcf4c0d355f692b064171535ba72361be1528a9d8e8d" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "enumn" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "epaint" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b9fdf617dd7f58b0c8e6e9e4a1281f730cde0831d40547da446b2bb76a47af" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor 0.26.2", + "emath 0.26.2", + "log", + "nohash-hasher", + "parking_lot", + "serde", ] [[package]] @@ -704,11 +1510,12 @@ dependencies = [ "ab_glyph", "ahash", "bytemuck", - "ecolor", - "emath", + "ecolor 0.27.1", + "emath 0.27.1", "log", "nohash-hasher", "parking_lot", + "serde", ] [[package]] @@ -742,6 +1549,65 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +dependencies = [ + "event-listener 5.3.0", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.72.0" @@ -758,6 +1624,21 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + [[package]] name = "fdeflate" version = "0.3.4" @@ -818,12 +1699,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d141b9bb793ce817c1ec513e93b72e4e2a6d9494b18d5c1e454fa08905586d6" dependencies = [ "itertools 0.11.0", - "proc-macro-crate", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.11.0" @@ -854,7 +1741,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -898,6 +1785,40 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand 2.0.2", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -906,7 +1827,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -928,8 +1849,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -944,6 +1868,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -983,12 +1917,152 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + [[package]] name = "glam" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fcd4ae4e86d991ad1300b8f57166e5be0c95ef1f63f3f5b827f8a164548746" +dependencies = [ + "bitflags 2.5.0", + "cfg_aliases", + "cgl", + "core-foundation", + "dispatch", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "icrate", + "libloading 0.8.3", + "objc2 0.4.1", + "once_cell", + "raw-window-handle 0.5.2", + "wayland-sys", + "windows-sys 0.48.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle 0.5.2", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" +dependencies = [ + "gl_generator", + "windows-sys 0.48.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.5.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.5.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror", + "winapi", + "windows 0.52.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.5.0", + "gpu-descriptor-types", + "hashbrown 0.14.3", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "half" version = "2.4.0" @@ -1021,6 +2095,25 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hassle-rs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.5.0", + "com", + "libc", + "libloading 0.8.3", + "thiserror", + "widestring", + "winapi", +] [[package]] name = "heck" @@ -1040,6 +2133,18 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "home" version = "0.5.9" @@ -1078,9 +2183,9 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" dependencies = [ - "block2", + "block2 0.3.0", "dispatch", - "objc2", + "objc2 0.4.1", ] [[package]] @@ -1116,6 +2221,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + [[package]] name = "image" version = "0.25.1" @@ -1149,6 +2267,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "imgref" version = "1.10.1" @@ -1195,6 +2319,15 @@ dependencies = [ "libc", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -1203,7 +2336,18 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", ] [[package]] @@ -1286,7 +2430,7 @@ version = "0.1.0" dependencies = [ "anyhow", "downcast-rs", - "egui", + "egui 0.27.1", "flax", "flume", "glam", @@ -1335,7 +2479,8 @@ version = "0.1.0" dependencies = [ "anyhow", "downcast-rs", - "egui", + "egui 0.27.1", + "egui-snarl", "egui-vulkano", "flax", "flume", @@ -1344,6 +2489,8 @@ dependencies = [ "parking_lot", "serde", "serde-lexpr", + "syn 2.0.58", + "thiserror", "vulkano", "vulkano-shaders", "vulkano-util", @@ -1389,6 +2536,23 @@ dependencies = [ "khors-core", ] +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.3", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "kqueue" version = "1.0.8" @@ -1409,6 +2573,15 @@ dependencies = [ "libc", ] +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec 0.7.4", +] + [[package]] name = "lasso" version = "0.7.2" @@ -1505,12 +2678,24 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.11" @@ -1570,6 +2755,55 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.5.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess2" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a3333bb1609500601edc766a39b4c1772874a4ce26022f4d866854dc020c41" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1598,6 +2832,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "naga" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +dependencies = [ + "bit-set", + "bitflags 2.5.0", + "codespan-reporting", + "hexf-parse", + "indexmap 2.2.6", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -1644,6 +2898,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.7.1", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -1740,7 +3006,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -1810,10 +3076,10 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -1823,6 +3089,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -1836,20 +3103,46 @@ dependencies = [ "objc_id", ] +[[package]] +name = "objc-sys" +version = "0.2.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" + [[package]] name = "objc-sys" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" +[[package]] +name = "objc2" +version = "0.3.0-beta.3.patch-leaks.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" +dependencies = [ + "block2 0.2.0-alpha.6", + "objc-sys 0.2.0-beta.2", + "objc2-encode 2.0.0-pre.2", +] + [[package]] name = "objc2" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "objc-sys", - "objc2-encode", + "objc-sys 0.3.2", + "objc2-encode 3.0.0", +] + +[[package]] +name = "objc2-encode" +version = "2.0.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" +dependencies = [ + "objc-sys 0.2.0-beta.2", ] [[package]] @@ -1858,6 +3151,15 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -1897,6 +3199,16 @@ dependencies = [ "libredox 0.0.2", ] +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "owned_ttf_parser" version = "0.20.0" @@ -1906,6 +3218,12 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1941,6 +3259,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1953,6 +3277,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.2", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -1972,6 +3307,22 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + [[package]] name = "polling" version = "3.6.0" @@ -1982,7 +3333,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.32", "tracing", "windows-sys 0.52.0", ] @@ -1993,6 +3344,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "pretty" version = "0.12.3" @@ -2004,6 +3361,27 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-easy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea59c637cd0e6b71ae18e589854e9de9b7cb17fefdbf2047e42bd38e24285b19" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + [[package]] name = "proc-macro-crate" version = "2.0.2" @@ -2011,7 +3389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" dependencies = [ "toml_datetime", - "toml_edit", + "toml_edit 0.20.2", ] [[package]] @@ -2039,7 +3417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -2212,6 +3590,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + [[package]] name = "redox_syscall" version = "0.3.5" @@ -2241,6 +3625,55 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "resvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" +dependencies = [ + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + [[package]] name = "rgb" version = "0.8.37" @@ -2250,6 +3683,33 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags 2.5.0", + "serde", + "serde_derive", +] + [[package]] name = "roxmltree" version = "0.14.1" @@ -2259,12 +3719,38 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "0.38.32" @@ -2274,10 +3760,41 @@ dependencies = [ "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.17" @@ -2345,7 +3862,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -2359,6 +3876,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "serde_spanned" version = "0.6.5" @@ -2368,6 +3896,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shaderc" version = "0.8.3" @@ -2386,7 +3925,7 @@ checksum = "73120d240fe22196300f39ca8547ca2d014960f27b19b47b21288b396272f7f7" dependencies = [ "cmake", "libc", - "roxmltree", + "roxmltree 0.14.1", ] [[package]] @@ -2413,6 +3952,21 @@ dependencies = [ "quote", ] +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "sized-chunks" version = "0.6.5" @@ -2430,6 +3984,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", + "serde", ] [[package]] @@ -2438,6 +3993,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8305086044614627ed85432d27b87cf9fc047204eaa036a11de6cf0120f273" +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -2457,7 +4021,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix", + "rustix 0.38.32", "thiserror", "wayland-backend", "wayland-client", @@ -2489,6 +4053,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.6" @@ -2508,6 +4082,21 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "steel-core" version = "0.6.0" @@ -2550,7 +4139,7 @@ source = "git+https://github.com/mattwparas/steel.git?branch=master#0c35ffcb0ebc dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -2583,6 +4172,9 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] name = "strsim" @@ -2591,10 +4183,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] -name = "syn" -version = "2.0.57" +name = "subtle" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "svgtypes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" +dependencies = [ + "kurbo", + "siphasher", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -2620,6 +4239,18 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand 2.0.2", + "rustix 0.38.32", + "windows-sys 0.52.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -2646,7 +4277,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -2670,6 +4301,12 @@ dependencies = [ "weezl", ] +[[package]] +name = "tiny-fn" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219331d5e8cc8377a282141509c21457a8b87937230fa4b8152d8857bfee2942" + [[package]] name = "tiny-skia" version = "0.11.4" @@ -2681,6 +4318,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", + "png", "tiny-skia-path", ] @@ -2724,7 +4362,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.6", "tokio-macros", "windows-sys 0.48.0", ] @@ -2737,7 +4375,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -2749,7 +4387,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.20.2", ] [[package]] @@ -2761,6 +4399,17 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + [[package]] name = "toml_edit" version = "0.20.2" @@ -2793,7 +4442,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -2820,6 +4469,15 @@ dependencies = [ "nom", ] +[[package]] +name = "type-map" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f" +dependencies = [ + "rustc-hash", +] + [[package]] name = "typed-arena" version = "2.0.2" @@ -2832,6 +4490,26 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "winapi", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -2865,6 +4543,35 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "rustls-webpki", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.0" @@ -2876,6 +4583,50 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "usvg" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" +dependencies = [ + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "roxmltree 0.19.0", + "simplecss", + "siphasher", + "svgtypes", + "usvg-tree", +] + +[[package]] +name = "usvg-tree" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path", +] + [[package]] name = "v_frame" version = "0.3.8" @@ -2943,10 +4694,10 @@ name = "vulkano-macros" version = "0.34.0" source = "git+https://github.com/vulkano-rs/vulkano.git?branch=master#55556bb916dfb288f7cac5a048111954afd230d8" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 2.0.2", "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -2959,7 +4710,7 @@ dependencies = [ "proc-macro2", "quote", "shaderc", - "syn", + "syn 2.0.58", "vulkano", ] @@ -2973,6 +4724,12 @@ dependencies = [ "winit", ] +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "walkdir" version = "2.5.0" @@ -3010,7 +4767,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -3044,7 +4801,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3063,7 +4820,7 @@ checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.32", "scoped-tls", "smallvec", "wayland-sys", @@ -3076,7 +4833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ "bitflags 2.5.0", - "rustix", + "rustix 0.38.32", "wayland-backend", "wayland-scanner", ] @@ -3098,7 +4855,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" dependencies = [ - "rustix", + "rustix 0.38.32", "wayland-client", "xcursor", ] @@ -3207,12 +4964,123 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "wgpu" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1213b52478a7631d6e387543ed8f642bc02c578ef4e3b49aca2a29a7df0cb" +dependencies = [ + "arrayvec 0.7.4", + "cfg-if", + "cfg_aliases", + "js-sys", + "log", + "parking_lot", + "profiling", + "raw-window-handle 0.6.0", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f6b033c2f00ae0bc8ea872c5989777c60bc241aac4e58b24774faa8b391f78" +dependencies = [ + "arrayvec 0.7.4", + "bit-vec", + "bitflags 2.5.0", + "cfg_aliases", + "codespan-reporting", + "indexmap 2.2.6", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.0", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f972c280505ab52ffe17e94a7413d9d54b58af0114ab226b9fc4999a47082e" +dependencies = [ + "android_system_properties", + "arrayvec 0.7.4", + "ash", + "bitflags 2.5.0", + "cfg_aliases", + "core-graphics-types", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.3", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle 0.6.0", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.5.0", + "js-sys", + "web-sys", +] + [[package]] name = "which" version = "4.4.2" @@ -3222,9 +5090,15 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.32", ] +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -3256,6 +5130,27 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.4", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -3265,6 +5160,28 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "windows-implement" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ee588991b9e7e6c8338edf3333fbe4da35dc72092643958ebb43f0ab2c49c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "windows-interface" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6fb8df20c9bcaa8ad6ab513f7b40104840c8867d5751126e4df3b08388d0cc7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3486,14 +5403,14 @@ dependencies = [ "memmap2", "ndk", "ndk-sys", - "objc2", + "objc2 0.4.1", "once_cell", "orbclient", "percent-encoding", "raw-window-handle 0.5.2", "raw-window-handle 0.6.0", "redox_syscall 0.3.5", - "rustix", + "rustix 0.38.32", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -3543,7 +5460,7 @@ dependencies = [ "libc", "libloading 0.8.3", "once_cell", - "rustix", + "rustix 0.38.32", "x11rb-protocol", ] @@ -3559,6 +5476,16 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" +[[package]] +name = "xdg-home" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e5a325c3cb8398ad6cf859c1135b25dd29e186679cf2da7581d9679f63b38e" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "xkbcommon-dl" version = "0.4.2" @@ -3590,6 +5517,78 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "zbus" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "byteorder", + "derivative", + "enumflags2", + "event-listener 2.5.3", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "once_cell", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "winapi", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -3607,9 +5606,15 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + [[package]] name = "zune-core" version = "0.4.12" @@ -3633,3 +5638,41 @@ checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" dependencies = [ "zune-core", ] + +[[package]] +name = "zvariant" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +dependencies = [ + "byteorder", + "enumflags2", + "libc", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "3.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] diff --git a/Cargo.toml b/Cargo.toml index a92bc53..8a8bf99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" members = [ "khors-core", "vendor/egui-vulkano", + "vendor/egui-snarl", "modules/khors-app", "modules/khors-graphics", "modules/khors-window", diff --git a/khors-test/src/main.rs b/khors-test/src/main.rs index b0ce2d9..cb3f54f 100644 --- a/khors-test/src/main.rs +++ b/khors-test/src/main.rs @@ -1,8 +1,8 @@ use anyhow::Result; use khors_config::ConfigModule; -use khors_app::App; -use khors_graphics::RenderModule; +use khors_app::app::App; +use khors_graphics::renderer::RenderModule; use khors_window::WindowModule; use tokio::runtime::Builder; use winit::event_loop::{ControlFlow, EventLoopBuilder}; diff --git a/modules/khors-app/src/app.rs b/modules/khors-app/src/app.rs new file mode 100644 index 0000000..d88a1f8 --- /dev/null +++ b/modules/khors-app/src/app.rs @@ -0,0 +1,318 @@ +#![warn(dead_code)] +use khors_core::{ + events::Events, + module::{Module, ModulesStack}, +}; +use khors_graphics::{ + debug_gui::DebugGuiStack, + render_module::{RenderModule as ThreadLocalModule, RenderModulesStack}, +}; + +use anyhow::Result; +use flax::{component, Schedule, World}; +use vulkano::device::DeviceFeatures; +use vulkano_util::{ + context::{VulkanoConfig, VulkanoContext}, + window::VulkanoWindows, +}; +use winit::{ + event::{Event, WindowEvent}, + window::WindowId, +}; + +component! { + window_id: WindowId, + + resources, +} + +#[allow(dead_code)] +pub struct App { + name: String, + modules: ModulesStack, + thread_local_modules: RenderModulesStack, + world: World, + schedule: Schedule, + events: Events, + rx: flume::Receiver, + running: bool, + event_cleanup_time: std::time::Duration, + vk_context: VulkanoContext, + vk_windows: VulkanoWindows, + debug_gui_stack: DebugGuiStack, +} + +impl App { + pub fn new() -> Self { + let mut events = Events::new(); + let (tx, rx) = flume::unbounded(); + events.subscribe_custom(tx); + + let schedule = Schedule::builder().build(); + + let vk_config = VulkanoConfig { + device_features: DeviceFeatures { + dynamic_rendering: true, + ..Default::default() + }, + ..Default::default() + }; + let vk_context = VulkanoContext::new(vk_config); + let vk_windows = VulkanoWindows::default(); + + Self { + name: "Khors".into(), + modules: ModulesStack::new(), + thread_local_modules: RenderModulesStack::new(), + world: World::new(), + schedule, + events, + rx, + running: false, + event_cleanup_time: std::time::Duration::from_secs(60), + vk_context, + vk_windows, + debug_gui_stack: DebugGuiStack::default(), + } + } + + pub fn run(&mut self) -> Result<()> { + self.running = true; + + self.schedule.execute_par(&mut self.world).unwrap(); + + let vk_context = &mut self.vk_context; + let vk_windows = &mut self.vk_windows; + let world = &mut self.world; + let events = &mut self.events; + let frame_time = std::time::Duration::from_millis(16); + let gui_stack = &mut self.debug_gui_stack; + + for module in self.modules.iter_mut() { + module.on_update(world, events, frame_time)?; + } + + for module in self.thread_local_modules.iter_mut() { + module.on_update(gui_stack, vk_context, vk_windows, world, events, frame_time)?; + } + + self.handle_events(); + + Ok(()) + } + + pub fn create_window(&mut self, event_loop: &winit::event_loop::EventLoopWindowTarget) + where + T: Clone + Send + Sync, + { + let vk_window_id = self.vk_windows.create_window( + event_loop, + &self.vk_context, + &vulkano_util::window::WindowDescriptor { + title: self.name.clone(), + present_mode: vulkano::swapchain::PresentMode::Mailbox, + ..Default::default() + }, + |_| {}, + ); + + let renderer = self.vk_windows.get_renderer(vk_window_id).unwrap(); + + self.world + .set(resources(), window_id(), vk_window_id) + .unwrap(); + + self.debug_gui_stack + .add_gui(vk_window_id, event_loop, renderer, true, false); + } + + pub fn process_event_loop( + &mut self, + event: winit::event::Event, + _elwt: &winit::event_loop::EventLoopWindowTarget, + ) -> Result + where + T: Clone + Send + Sync, + { + match &event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + return Ok(true); + } + Event::WindowEvent { + event: WindowEvent::Focused(_), + .. + } => self.events().send(event.clone()), + Event::WindowEvent { + event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. }, + window_id, + } => self + .vk_windows + .get_renderer_mut(*window_id) + .unwrap() + .resize(), + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + window_id, + } => 'redraw: { + // Tasks for redrawing: + // 1. Update state based on events + // 2. Compute & Render + // 3. Reset input state + // 4. Update time & title + + // The rendering part goes here: + match self + .vk_windows + .get_renderer(*window_id) + .unwrap() + .window_size() + { + [w, h] => { + // Skip this frame when minimized. + if w == 0.0 || h == 0.0 { + break 'redraw; + } + } + } + + self.run()?; + } + Event::WindowEvent { window_id, event } => { + let window = self.vk_windows.get_window(*window_id).unwrap(); + let gui = self.debug_gui_stack.get_mut(*window_id).unwrap(); + gui.update(window, event); + } + Event::AboutToWait => { + self.vk_windows.iter().for_each(|(window_id, _)| { + self.vk_windows + .get_window(*window_id) + .unwrap() + .request_redraw() + }); + } + _ => (), + } + Ok(false) + } + + pub fn handle_events(&mut self) { + for event in self.rx.try_iter() { + match event { + AppEvent::Exit => self.running = false, + } + } + } + + #[allow(dead_code)] + pub fn set_schedule(&mut self, schedule: Schedule) { + self.schedule = schedule; + } + + #[allow(dead_code)] + pub fn world(&self) -> &World { + &self.world + } + + #[allow(dead_code)] + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } + + pub fn events(&self) -> &Events { + &self.events + } + + #[allow(dead_code)] + pub fn events_mut(&mut self) -> &mut Events { + &mut self.events + } + + /// Pushes a module from the provided init closure to to the top of the layer stack. The provided + /// closure to construct the layer takes in the world and events. + pub fn push_render_module(&mut self, func: F) + where + F: FnOnce( + &mut VulkanoContext, + &mut VulkanoWindows, + &mut Schedule, + &mut World, + &mut Events, + ) -> T, + T: 'static + ThreadLocalModule, + { + let module = func( + &mut self.vk_context, + &mut self.vk_windows, + &mut self.schedule, + &mut self.world, + &mut self.events, + ); + self.thread_local_modules.push(module); + } + + /// Pushes a layer from the provided init closure to to the top of the layer stack. The provided + /// closure to construct the layer takes in the world and events. + pub fn push_module(&mut self, func: F) + where + F: FnOnce(&mut Schedule, &mut World, &mut Events) -> T, + T: 'static + Module, + { + let module = func(&mut self.schedule, &mut self.world, &mut self.events); + self.modules.push(module); + } + + /// Pushes a module from the provided init closure to to the top of the module stack. The provided + /// closure to construct the module takes in the world and events, and may return an error which + /// is propagated to the callee. + #[allow(dead_code)] + pub fn try_push_module(&mut self, func: F) -> Result<(), E> + where + F: FnOnce(&mut World, &mut Events) -> Result, + T: 'static + Module, + { + let module = func(&mut self.world, &mut self.events)?; + self.modules.push(module); + Ok(()) + } + + /// Inserts a module from the provided init closure to to the top of the module stack. The provided + /// closure to construct the module takes in the world and events. + #[allow(dead_code)] + pub fn insert_module(&mut self, index: usize, func: F) + where + F: FnOnce(&mut World, &mut Events) -> T, + T: 'static + Module, + { + let module = func(&mut self.world, &mut self.events); + self.modules.insert(index, module); + } + + /// Pushes a module from the provided init closure to to the top of the module stack. The provided + /// closure to construct the module takes in the world and events, and may return an error which + /// is propagated to the callee. + #[allow(dead_code)] + pub fn try_insert_module(&mut self, index: usize, func: F) -> Result<(), E> + where + F: FnOnce(&mut World, &mut Events) -> Result, + T: 'static + Module, + { + let module = func(&mut self.world, &mut self.events)?; + self.modules.insert(index, module); + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(dead_code)] +pub enum AppEvent { + Exit, +} + +impl Default for App { + fn default() -> Self { + Self::new() + } +} diff --git a/modules/khors-app/src/lib.rs b/modules/khors-app/src/lib.rs index d88a1f8..309be62 100644 --- a/modules/khors-app/src/lib.rs +++ b/modules/khors-app/src/lib.rs @@ -1,318 +1 @@ -#![warn(dead_code)] -use khors_core::{ - events::Events, - module::{Module, ModulesStack}, -}; -use khors_graphics::{ - debug_gui::DebugGuiStack, - render_module::{RenderModule as ThreadLocalModule, RenderModulesStack}, -}; - -use anyhow::Result; -use flax::{component, Schedule, World}; -use vulkano::device::DeviceFeatures; -use vulkano_util::{ - context::{VulkanoConfig, VulkanoContext}, - window::VulkanoWindows, -}; -use winit::{ - event::{Event, WindowEvent}, - window::WindowId, -}; - -component! { - window_id: WindowId, - - resources, -} - -#[allow(dead_code)] -pub struct App { - name: String, - modules: ModulesStack, - thread_local_modules: RenderModulesStack, - world: World, - schedule: Schedule, - events: Events, - rx: flume::Receiver, - running: bool, - event_cleanup_time: std::time::Duration, - vk_context: VulkanoContext, - vk_windows: VulkanoWindows, - debug_gui_stack: DebugGuiStack, -} - -impl App { - pub fn new() -> Self { - let mut events = Events::new(); - let (tx, rx) = flume::unbounded(); - events.subscribe_custom(tx); - - let schedule = Schedule::builder().build(); - - let vk_config = VulkanoConfig { - device_features: DeviceFeatures { - dynamic_rendering: true, - ..Default::default() - }, - ..Default::default() - }; - let vk_context = VulkanoContext::new(vk_config); - let vk_windows = VulkanoWindows::default(); - - Self { - name: "Khors".into(), - modules: ModulesStack::new(), - thread_local_modules: RenderModulesStack::new(), - world: World::new(), - schedule, - events, - rx, - running: false, - event_cleanup_time: std::time::Duration::from_secs(60), - vk_context, - vk_windows, - debug_gui_stack: DebugGuiStack::default(), - } - } - - pub fn run(&mut self) -> Result<()> { - self.running = true; - - self.schedule.execute_par(&mut self.world).unwrap(); - - let vk_context = &mut self.vk_context; - let vk_windows = &mut self.vk_windows; - let world = &mut self.world; - let events = &mut self.events; - let frame_time = std::time::Duration::from_millis(16); - let gui_stack = &mut self.debug_gui_stack; - - for module in self.modules.iter_mut() { - module.on_update(world, events, frame_time)?; - } - - for module in self.thread_local_modules.iter_mut() { - module.on_update(gui_stack, vk_context, vk_windows, world, events, frame_time)?; - } - - self.handle_events(); - - Ok(()) - } - - pub fn create_window(&mut self, event_loop: &winit::event_loop::EventLoopWindowTarget) - where - T: Clone + Send + Sync, - { - let vk_window_id = self.vk_windows.create_window( - event_loop, - &self.vk_context, - &vulkano_util::window::WindowDescriptor { - title: self.name.clone(), - present_mode: vulkano::swapchain::PresentMode::Mailbox, - ..Default::default() - }, - |_| {}, - ); - - let renderer = self.vk_windows.get_renderer(vk_window_id).unwrap(); - - self.world - .set(resources(), window_id(), vk_window_id) - .unwrap(); - - self.debug_gui_stack - .add_gui(vk_window_id, event_loop, renderer, true, false); - } - - pub fn process_event_loop( - &mut self, - event: winit::event::Event, - _elwt: &winit::event_loop::EventLoopWindowTarget, - ) -> Result - where - T: Clone + Send + Sync, - { - match &event { - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - return Ok(true); - } - Event::WindowEvent { - event: WindowEvent::Focused(_), - .. - } => self.events().send(event.clone()), - Event::WindowEvent { - event: WindowEvent::Resized(..) | WindowEvent::ScaleFactorChanged { .. }, - window_id, - } => self - .vk_windows - .get_renderer_mut(*window_id) - .unwrap() - .resize(), - Event::WindowEvent { - event: WindowEvent::RedrawRequested, - window_id, - } => 'redraw: { - // Tasks for redrawing: - // 1. Update state based on events - // 2. Compute & Render - // 3. Reset input state - // 4. Update time & title - - // The rendering part goes here: - match self - .vk_windows - .get_renderer(*window_id) - .unwrap() - .window_size() - { - [w, h] => { - // Skip this frame when minimized. - if w == 0.0 || h == 0.0 { - break 'redraw; - } - } - } - - self.run()?; - } - Event::WindowEvent { window_id, event } => { - let window = self.vk_windows.get_window(*window_id).unwrap(); - let gui = self.debug_gui_stack.get_mut(*window_id).unwrap(); - gui.update(window, event); - } - Event::AboutToWait => { - self.vk_windows.iter().for_each(|(window_id, _)| { - self.vk_windows - .get_window(*window_id) - .unwrap() - .request_redraw() - }); - } - _ => (), - } - Ok(false) - } - - pub fn handle_events(&mut self) { - for event in self.rx.try_iter() { - match event { - AppEvent::Exit => self.running = false, - } - } - } - - #[allow(dead_code)] - pub fn set_schedule(&mut self, schedule: Schedule) { - self.schedule = schedule; - } - - #[allow(dead_code)] - pub fn world(&self) -> &World { - &self.world - } - - #[allow(dead_code)] - pub fn world_mut(&mut self) -> &mut World { - &mut self.world - } - - pub fn events(&self) -> &Events { - &self.events - } - - #[allow(dead_code)] - pub fn events_mut(&mut self) -> &mut Events { - &mut self.events - } - - /// Pushes a module from the provided init closure to to the top of the layer stack. The provided - /// closure to construct the layer takes in the world and events. - pub fn push_render_module(&mut self, func: F) - where - F: FnOnce( - &mut VulkanoContext, - &mut VulkanoWindows, - &mut Schedule, - &mut World, - &mut Events, - ) -> T, - T: 'static + ThreadLocalModule, - { - let module = func( - &mut self.vk_context, - &mut self.vk_windows, - &mut self.schedule, - &mut self.world, - &mut self.events, - ); - self.thread_local_modules.push(module); - } - - /// Pushes a layer from the provided init closure to to the top of the layer stack. The provided - /// closure to construct the layer takes in the world and events. - pub fn push_module(&mut self, func: F) - where - F: FnOnce(&mut Schedule, &mut World, &mut Events) -> T, - T: 'static + Module, - { - let module = func(&mut self.schedule, &mut self.world, &mut self.events); - self.modules.push(module); - } - - /// Pushes a module from the provided init closure to to the top of the module stack. The provided - /// closure to construct the module takes in the world and events, and may return an error which - /// is propagated to the callee. - #[allow(dead_code)] - pub fn try_push_module(&mut self, func: F) -> Result<(), E> - where - F: FnOnce(&mut World, &mut Events) -> Result, - T: 'static + Module, - { - let module = func(&mut self.world, &mut self.events)?; - self.modules.push(module); - Ok(()) - } - - /// Inserts a module from the provided init closure to to the top of the module stack. The provided - /// closure to construct the module takes in the world and events. - #[allow(dead_code)] - pub fn insert_module(&mut self, index: usize, func: F) - where - F: FnOnce(&mut World, &mut Events) -> T, - T: 'static + Module, - { - let module = func(&mut self.world, &mut self.events); - self.modules.insert(index, module); - } - - /// Pushes a module from the provided init closure to to the top of the module stack. The provided - /// closure to construct the module takes in the world and events, and may return an error which - /// is propagated to the callee. - #[allow(dead_code)] - pub fn try_insert_module(&mut self, index: usize, func: F) -> Result<(), E> - where - F: FnOnce(&mut World, &mut Events) -> Result, - T: 'static + Module, - { - let module = func(&mut self.world, &mut self.events)?; - self.modules.insert(index, module); - Ok(()) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -#[allow(dead_code)] -pub enum AppEvent { - Exit, -} - -impl Default for App { - fn default() -> Self { - Self::new() - } -} +pub mod app; diff --git a/modules/khors-graphics/Cargo.toml b/modules/khors-graphics/Cargo.toml index 1fa11e9..4eeddde 100644 --- a/modules/khors-graphics/Cargo.toml +++ b/modules/khors-graphics/Cargo.toml @@ -8,9 +8,12 @@ edition = "2021" [dependencies] khors-core = { path = "../../khors-core", version = "0.1.0" } egui-vulkano = { path = "../../vendor/egui-vulkano", version = "0.1.0" } +egui-snarl = { path = "../../vendor/egui-snarl", features = ["serde"] } anyhow = "1.0.80" +thiserror = "1.0.58" egui = "0.27.1" +syn = "2.0.58" glam = "0.27.0" winit = { version = "0.29.15",features = ["rwh_05"] } vulkano = { git = "https://github.com/vulkano-rs/vulkano.git", branch = "master" } diff --git a/modules/khors-graphics/src/lib.rs b/modules/khors-graphics/src/lib.rs index d41ce55..da7497a 100644 --- a/modules/khors-graphics/src/lib.rs +++ b/modules/khors-graphics/src/lib.rs @@ -1,555 +1,10 @@ pub mod debug_gui; pub mod render_module; -use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World}; -use glam::{ - f32::{Mat3, Vec3}, - Mat4, -}; -use std::sync::Arc; -use vulkano::{ - buffer::{ - allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, - Buffer, BufferCreateInfo, BufferUsage, - }, - command_buffer::{ - allocator::{CommandBufferAllocator, StandardCommandBufferAllocator}, - CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer, - RenderPassBeginInfo, - }, - descriptor_set::{ - allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet, - }, - device::DeviceOwned, - format::Format, - image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage}, - memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, - pipeline::{ - graphics::{ - color_blend::{ColorBlendAttachmentState, ColorBlendState}, - depth_stencil::{DepthState, DepthStencilState}, - input_assembly::InputAssemblyState, - multisample::MultisampleState, - rasterization::RasterizationState, - vertex_input::{Vertex, VertexDefinition}, - viewport::{Viewport, ViewportState}, - GraphicsPipelineCreateInfo, - }, - layout::PipelineDescriptorSetLayoutCreateInfo, - GraphicsPipeline, Pipeline, PipelineBindPoint, PipelineLayout, - PipelineShaderStageCreateInfo, - }, - render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, - shader::EntryPoint, - sync::GpuFuture, -}; -use vulkano_util::{ - context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows, -}; - -use egui_vulkano::Gui; -use crate::debug_gui::DebugGuiStack; -use crate::render_module::RenderModule as ThreadLocalModule; - -use self::{ - model::{INDICES, NORMALS, POSITIONS}, - vulkan::vertex::{Normal, Position}, -}; - pub mod events; -mod model; +pub mod renderer; +pub mod rendergraph; +pub mod node_editor; +mod temp; mod test_pipeline; mod vulkan; - -pub struct RenderModule { - schedule: Schedule, - memory_allocator: Arc, - descriptor_set_allocator: Arc, - command_buffer_allocator: Arc, - viewport: Viewport, - rotation_start: std::time::Instant, -} - -impl RenderModule { - pub fn new( - vk_context: &mut VulkanoContext, - _vk_windows: &mut VulkanoWindows, - _schedule: &mut Schedule, - _world: &mut World, - _events: &mut khors_core::events::Events, - ) -> Self { - let schedule = Schedule::builder() - .with_system(add_distance_system()) - .build(); - - let memory_allocator = Arc::new(StandardMemoryAllocator::new_default( - vk_context.device().clone(), - )); - - let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( - vk_context.device().clone(), - Default::default(), - )); - - let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( - vk_context.device().clone(), - Default::default(), - )); - - let viewport = Viewport { - offset: [0.0, 0.0], - extent: [0.0, 0.0], - depth_range: 0.0..=1.0, - }; - - let rotation_start = std::time::Instant::now(); - - Self { - schedule, - memory_allocator, - descriptor_set_allocator, - command_buffer_allocator, - viewport, - rotation_start, - } - } -} - -impl ThreadLocalModule for RenderModule { - fn on_update( - &mut self, - gui_stack: &mut DebugGuiStack, - vk_context: &mut VulkanoContext, - vk_windows: &mut vulkano_util::window::VulkanoWindows, - world: &mut World, - _events: &mut khors_core::events::Events, - _frame_time: std::time::Duration, - ) -> anyhow::Result<()> { - self.schedule.execute_seq(world).unwrap(); - - let viewport = &mut self.viewport; - - for (window_id, renderer) in vk_windows.iter_mut() { - let gui = gui_stack.get_mut(*window_id).unwrap(); - draw( - vk_context.device().clone(), - self.memory_allocator.clone(), - self.descriptor_set_allocator.clone(), - self.command_buffer_allocator.clone(), - viewport, - vk_context, - renderer, - gui, - self.rotation_start, - ); - } - Ok(()) - } -} - -pub fn add_distance_system() -> BoxedSystem { - let query = Query::new(entity_ids()); - - System::builder() - .with_query(query) - .build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| { - for _id in &mut query { - // println!("----------: {}", _id.index()); - } - }) - .boxed() -} - -fn draw( - device: Arc, - memory_allocator: Arc, - descriptor_set_allocator: Arc, - command_buffer_allocator: Arc, - _viewport: &mut Viewport, - context: &mut VulkanoContext, - renderer: &mut VulkanoWindowRenderer, - gui: &mut Gui, - rotation_start: std::time::Instant, -) { - let vertex_buffer = Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::VERTEX_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - POSITIONS, - ) - .unwrap(); - let normals_buffer = Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::VERTEX_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - NORMALS, - ) - .unwrap(); - let index_buffer = Buffer::from_iter( - memory_allocator.clone(), - BufferCreateInfo { - usage: BufferUsage::INDEX_BUFFER, - ..Default::default() - }, - AllocationCreateInfo { - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - INDICES, - ) - .unwrap(); - - let uniform_buffer = SubbufferAllocator::new( - memory_allocator.clone(), - SubbufferAllocatorCreateInfo { - buffer_usage: BufferUsage::UNIFORM_BUFFER, - memory_type_filter: MemoryTypeFilter::PREFER_DEVICE - | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, - ..Default::default() - }, - ); - - let render_pass = vulkano::single_pass_renderpass!( - device.clone(), - attachments: { - color: { - format: renderer.swapchain_format(), - samples: 1, - load_op: Clear, - store_op: Store, - }, - depth_stencil: { - format: Format::D16_UNORM, - samples: 1, - load_op: Clear, - store_op: DontCare, - }, - }, - pass: { - color: [color], - depth_stencil: {depth_stencil}, - }, - ) - .unwrap(); - - let vs = vs::load(device.clone()) - .unwrap() - .entry_point("main") - .unwrap(); - let fs = fs::load(device.clone()) - .unwrap() - .entry_point("main") - .unwrap(); - - let (mut pipeline, mut framebuffers) = window_size_dependent_setup( - memory_allocator.clone(), - vs.clone(), - fs.clone(), - renderer.swapchain_image_views(), - render_pass.clone(), - ); - - // Do not draw the frame when the screen size is zero. On Windows, this can - // occur when minimizing the application. - let image_extent: [u32; 2] = renderer.window().inner_size().into(); - - if image_extent.contains(&0) { - return; - } - - // Begin rendering by acquiring the gpu future from the window renderer. - let previous_frame_end = renderer - .acquire(None, |swapchain_images| { - // Whenever the window resizes we need to recreate everything dependent - // on the window size. In this example that - // includes the swapchain, the framebuffers - // and the dynamic state viewport. - let (new_pipeline, new_framebuffers) = window_size_dependent_setup( - memory_allocator.clone(), - vs.clone(), - fs.clone(), - swapchain_images, - render_pass.clone(), - ); - - pipeline = new_pipeline; - framebuffers = new_framebuffers; - }) - .unwrap(); - - let uniform_buffer_subbuffer = { - let elapsed = rotation_start.elapsed(); - let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0; - let rotation = Mat3::from_rotation_y(rotation as f32); - - // NOTE: This teapot was meant for OpenGL where the origin is at the lower left - // instead the origin is at the upper left in Vulkan, so we reverse the Y axis. - let aspect_ratio = renderer.aspect_ratio(); - - let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0); - let view = Mat4::look_at_rh( - Vec3::new(0.4, 0.3, 1.0), - Vec3::new(0.0, 0.0, 0.0), - Vec3::new(0.0, -1.0, 0.0), - ); - let scale = Mat4::from_scale(Vec3::splat(0.01)); - - let uniform_data = vs::Data { - world: Mat4::from_mat3(rotation).to_cols_array_2d(), - view: (view * scale).to_cols_array_2d(), - proj: proj.to_cols_array_2d(), - }; - - let subbuffer = uniform_buffer.allocate_sized().unwrap(); - *subbuffer.write().unwrap() = uniform_data; - - subbuffer - }; - - let mut builder = RecordingCommandBuffer::new( - command_buffer_allocator.clone(), - context.graphics_queue().queue_family_index(), - CommandBufferLevel::Primary, - CommandBufferBeginInfo { - usage: CommandBufferUsage::OneTimeSubmit, - ..Default::default() - }, - ) - .unwrap(); - - let layout = &pipeline.layout().set_layouts()[0]; - let set = DescriptorSet::new( - descriptor_set_allocator.clone(), - layout.clone(), - [WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)], - [], - ) - .unwrap(); - - builder - .begin_render_pass( - RenderPassBeginInfo { - clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into()), Some(1f32.into())], - ..RenderPassBeginInfo::framebuffer( - framebuffers[renderer.image_index() as usize].clone(), - ) - }, - Default::default(), - ) - .unwrap() - .bind_pipeline_graphics(pipeline.clone()) - .unwrap() - .bind_descriptor_sets( - PipelineBindPoint::Graphics, - pipeline.layout().clone(), - 0, - set, - ) - .unwrap() - .bind_vertex_buffers(0, (vertex_buffer.clone(), normals_buffer.clone())) - .unwrap() - .bind_index_buffer(index_buffer.clone()) - .unwrap(); - - unsafe { - builder - .draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0) - .unwrap(); - } - - builder.end_render_pass(Default::default()).unwrap(); - // Finish recording the command buffer by calling `end`. - let command_buffer = builder.end().unwrap(); - - draw_gui(gui); - - let before_future = previous_frame_end - .then_execute(context.graphics_queue().clone(), command_buffer) - .unwrap() - .boxed(); - - let after_future = gui - .draw_on_image(before_future, renderer.swapchain_image_view()) - .boxed(); - - // The color output is now expected to contain our triangle. But in order to - // show it on the screen, we have to *present* the image by calling - // `present` on the window renderer. - // - // This function does not actually present the image immediately. Instead it - // submits a present command at the end of the queue. This means that it will - // only be presented once the GPU has finished executing the command buffer - // that draws the triangle. - renderer.present(after_future, true); -} - -fn draw_gui(gui: &mut Gui) { - let mut code = CODE.to_owned(); - gui.immediate_ui(|gui| { - let ctx = gui.context(); - egui::Window::new("Colors").vscroll(true).show(&ctx, |ui| { - ui.vertical_centered(|ui| { - ui.add(egui::widgets::Label::new("Hi there!")); - sized_text(ui, "Rich Text", 32.0); - }); - ui.separator(); - ui.columns(2, |columns| { - egui::ScrollArea::vertical() - .id_source("source") - .show(&mut columns[0], |ui| { - ui.add( - egui::TextEdit::multiline(&mut code).font(egui::TextStyle::Monospace), - ); - }); - egui::ScrollArea::vertical() - .id_source("rendered") - .show(&mut columns[1], |ui| { - ui.add(egui::widgets::Label::new("Good day!")); - }); - }); - }); - }); -} - -fn sized_text(ui: &mut egui::Ui, text: impl Into, size: f32) { - ui.label( - egui::RichText::new(text) - .size(size) - .family(egui::FontFamily::Monospace), - ); -} - -const CODE: &str = r" -# Some markup -``` -let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1); -``` -"; - -fn window_size_dependent_setup( - memory_allocator: Arc, - vs: EntryPoint, - fs: EntryPoint, - image_views: &[Arc], - render_pass: Arc, -) -> (Arc, Vec>) { - let device = memory_allocator.device().clone(); - - let extent = image_views[0].image().extent(); - - let depth_buffer = ImageView::new_default( - Image::new( - memory_allocator, - ImageCreateInfo { - image_type: ImageType::Dim2d, - format: Format::D16_UNORM, - extent, - usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT, - ..Default::default() - }, - AllocationCreateInfo::default(), - ) - .unwrap(), - ) - .unwrap(); - - let framebuffers = image_views - .iter() - .map(|image_view| { - Framebuffer::new( - render_pass.clone(), - FramebufferCreateInfo { - attachments: vec![image_view.clone(), depth_buffer.clone()], - ..Default::default() - }, - ) - .unwrap() - }) - .collect::>(); - - // In the triangle example we use a dynamic viewport, as its a simple example. However in the - // teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the - // driver to optimize things, at the cost of slower window resizes. - // https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport - let pipeline = { - let vertex_input_state = [Position::per_vertex(), Normal::per_vertex()] - .definition(&vs) - .unwrap(); - let stages = [ - PipelineShaderStageCreateInfo::new(vs), - PipelineShaderStageCreateInfo::new(fs), - ]; - let layout = PipelineLayout::new( - device.clone(), - PipelineDescriptorSetLayoutCreateInfo::from_stages(&stages) - .into_pipeline_layout_create_info(device.clone()) - .unwrap(), - ) - .unwrap(); - let subpass = Subpass::from(render_pass, 0).unwrap(); - - GraphicsPipeline::new( - device, - None, - GraphicsPipelineCreateInfo { - stages: stages.into_iter().collect(), - vertex_input_state: Some(vertex_input_state), - input_assembly_state: Some(InputAssemblyState::default()), - viewport_state: Some(ViewportState { - viewports: [Viewport { - offset: [0.0, 0.0], - extent: [extent[0] as f32, extent[1] as f32], - depth_range: 0.0..=1.0, - }] - .into_iter() - .collect(), - ..Default::default() - }), - rasterization_state: Some(RasterizationState::default()), - depth_stencil_state: Some(DepthStencilState { - depth: Some(DepthState::simple()), - ..Default::default() - }), - multisample_state: Some(MultisampleState::default()), - color_blend_state: Some(ColorBlendState::with_attachment_states( - subpass.num_color_attachments(), - ColorBlendAttachmentState::default(), - )), - subpass: Some(subpass.into()), - ..GraphicsPipelineCreateInfo::layout(layout) - }, - ) - .unwrap() - }; - - (pipeline, framebuffers) -} - -mod vs { - vulkano_shaders::shader! { - ty: "vertex", - path: "src/vert.glsl", - } -} - -mod fs { - vulkano_shaders::shader! { - ty: "fragment", - path: "src/frag.glsl", - } -} diff --git a/modules/khors-graphics/src/node_editor/mod.rs b/modules/khors-graphics/src/node_editor/mod.rs new file mode 100644 index 0000000..19a286c --- /dev/null +++ b/modules/khors-graphics/src/node_editor/mod.rs @@ -0,0 +1,1025 @@ +use std::collections::HashMap; + +use egui::{Color32, Ui}; +use egui_snarl::{ + ui::{AnyPins, PinInfo, SnarlStyle, SnarlViewer}, + InPin, InPinId, NodeId, OutPin, OutPinId, Snarl, +}; + +const STRING_COLOR: Color32 = Color32::from_rgb(0x00, 0xb0, 0x00); +const NUMBER_COLOR: Color32 = Color32::from_rgb(0xb0, 0x00, 0x00); +const IMAGE_COLOR: Color32 = Color32::from_rgb(0xb0, 0x00, 0xb0); +const UNTYPED_COLOR: Color32 = Color32::from_rgb(0xb0, 0xb0, 0xb0); + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +enum DemoNode { + /// Node with single input. + /// Displays the value of the input. + Sink, + + /// Value node with a single output. + /// The value is editable in UI. + Number(f64), + + /// Value node with a single output. + String(String), + + /// Converts URI to Image + ShowImage(String), + + /// Expression node with a single output. + /// It has number of inputs equal to number of variables in the expression. + ExprNode(ExprNode), +} + +impl DemoNode { + fn number_out(&self) -> f64 { + match self { + DemoNode::Number(value) => *value, + DemoNode::ExprNode(expr_node) => expr_node.eval(), + _ => unreachable!(), + } + } + + fn number_in(&mut self, idx: usize) -> &mut f64 { + match self { + DemoNode::ExprNode(expr_node) => &mut expr_node.values[idx - 1], + _ => unreachable!(), + } + } + + fn label_in(&mut self, idx: usize) -> &str { + match self { + DemoNode::ShowImage(_) if idx == 0 => "URL", + DemoNode::ExprNode(expr_node) => &expr_node.bindings[idx - 1], + _ => unreachable!(), + } + } + + fn string_out(&self) -> &str { + match self { + DemoNode::String(value) => &value, + _ => unreachable!(), + } + } + + fn string_in(&mut self) -> &mut String { + match self { + DemoNode::ShowImage(uri) => uri, + DemoNode::ExprNode(expr_node) => &mut expr_node.text, + _ => unreachable!(), + } + } + + fn expr_node(&mut self) -> &mut ExprNode { + match self { + DemoNode::ExprNode(expr_node) => expr_node, + _ => unreachable!(), + } + } +} + +struct DemoViewer; + +impl SnarlViewer for DemoViewer { + #[inline] + fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl) { + // Validate connection + match (&snarl[from.id.node], &snarl[to.id.node]) { + (DemoNode::Sink, _) => { + unreachable!("Sink node has no outputs") + } + (_, DemoNode::Sink) => {} + (_, DemoNode::Number(_)) => { + unreachable!("Number node has no inputs") + } + (_, DemoNode::String(_)) => { + unreachable!("String node has no inputs") + } + (DemoNode::Number(_), DemoNode::ShowImage(_)) => { + return; + } + (DemoNode::ShowImage(_), DemoNode::ShowImage(_)) => { + return; + } + (DemoNode::String(_), DemoNode::ShowImage(_)) => {} + (DemoNode::ExprNode(_), DemoNode::ExprNode(_)) if to.id.input == 0 => { + return; + } + (DemoNode::ExprNode(_), DemoNode::ExprNode(_)) => {} + (DemoNode::Number(_), DemoNode::ExprNode(_)) if to.id.input == 0 => { + return; + } + (DemoNode::Number(_), DemoNode::ExprNode(_)) => {} + (DemoNode::String(_), DemoNode::ExprNode(_)) if to.id.input == 0 => {} + (DemoNode::String(_), DemoNode::ExprNode(_)) => { + return; + } + (DemoNode::ShowImage(_), DemoNode::ExprNode(_)) => { + return; + } + (DemoNode::ExprNode(_), DemoNode::ShowImage(_)) => { + return; + } + } + + for &remote in &to.remotes { + snarl.disconnect(remote, to.id); + } + + snarl.connect(from.id, to.id); + } + + fn title(&mut self, node: &DemoNode) -> String { + match node { + DemoNode::Sink => "Sink".to_owned(), + DemoNode::Number(_) => "Number".to_owned(), + DemoNode::String(_) => "String".to_owned(), + DemoNode::ShowImage(_) => "Show image".to_owned(), + DemoNode::ExprNode(_) => "Expr".to_owned(), + } + } + + fn inputs(&mut self, node: &DemoNode) -> usize { + match node { + DemoNode::Sink => 1, + DemoNode::Number(_) => 0, + DemoNode::String(_) => 0, + DemoNode::ShowImage(_) => 1, + DemoNode::ExprNode(expr_node) => 1 + expr_node.bindings.len(), + } + } + + fn outputs(&mut self, node: &DemoNode) -> usize { + match node { + DemoNode::Sink => 0, + DemoNode::Number(_) => 1, + DemoNode::String(_) => 1, + DemoNode::ShowImage(_) => 1, + DemoNode::ExprNode(_) => 1, + } + } + + fn show_input( + &mut self, + pin: &InPin, + ui: &mut egui::Ui, + scale: f32, + snarl: &mut Snarl, + ) -> PinInfo { + match snarl[pin.id.node] { + DemoNode::Sink => { + assert_eq!(pin.id.input, 0, "Sink node has only one input"); + + match &*pin.remotes { + [] => { + ui.label("None"); + PinInfo::circle().with_fill(UNTYPED_COLOR) + } + [remote] => match snarl[remote.node] { + DemoNode::Sink => unreachable!("Sink node has no outputs"), + DemoNode::Number(value) => { + assert_eq!(remote.output, 0, "Number node has only one output"); + ui.label(format_float(value)); + PinInfo::square().with_fill(NUMBER_COLOR) + } + DemoNode::String(ref value) => { + assert_eq!(remote.output, 0, "String node has only one output"); + ui.label(format!("{:?}", value)); + PinInfo::triangle().with_fill(STRING_COLOR) + } + DemoNode::ExprNode(ref expr) => { + assert_eq!(remote.output, 0, "Expr node has only one output"); + ui.label(format_float(expr.eval())); + PinInfo::square().with_fill(NUMBER_COLOR) + } + DemoNode::ShowImage(ref uri) => { + assert_eq!(remote.output, 0, "ShowImage node has only one output"); + + let image = egui::Image::new(uri) + .fit_to_original_size(scale) + .show_loading_spinner(true); + ui.add(image); + + PinInfo::circle().with_fill(IMAGE_COLOR) + } + }, + _ => unreachable!("Sink input has only one wire"), + } + } + DemoNode::Number(_) => { + unreachable!("Number node has no inputs") + } + DemoNode::String(_) => { + unreachable!("String node has no inputs") + } + DemoNode::ShowImage(_) => match &*pin.remotes { + [] => { + let input = snarl[pin.id.node].string_in(); + egui::TextEdit::singleline(input) + .clip_text(false) + .desired_width(0.0) + .margin(ui.spacing().item_spacing) + .show(ui); + PinInfo::triangle().with_fill(STRING_COLOR) + } + [remote] => { + let new_value = snarl[remote.node].string_out().to_owned(); + + egui::TextEdit::singleline(&mut &*new_value) + .clip_text(false) + .desired_width(0.0) + .margin(ui.spacing().item_spacing) + .show(ui); + + let input = snarl[pin.id.node].string_in(); + *input = new_value; + + PinInfo::triangle().with_fill(STRING_COLOR) + } + _ => unreachable!("Sink input has only one wire"), + }, + DemoNode::ExprNode(_) if pin.id.input == 0 => { + let changed = match &*pin.remotes { + [] => { + let input = snarl[pin.id.node].string_in(); + let r = egui::TextEdit::singleline(input) + .clip_text(false) + .desired_width(0.0) + .margin(ui.spacing().item_spacing) + .show(ui) + .response; + + r.changed() + } + [remote] => { + let new_string = snarl[remote.node].string_out().to_owned(); + + egui::TextEdit::singleline(&mut &*new_string) + .clip_text(false) + .desired_width(0.0) + .margin(ui.spacing().item_spacing) + .show(ui); + + let input = snarl[pin.id.node].string_in(); + if new_string != *input { + *input = new_string; + true + } else { + false + } + } + _ => unreachable!("Expr pins has only one wire"), + }; + + if changed { + let expr_node = snarl[pin.id.node].expr_node(); + + match syn::parse_str(&expr_node.text) { + Ok(expr) => { + expr_node.expr = expr; + + let values = Iterator::zip( + expr_node.bindings.iter().map(String::clone), + expr_node.values.iter().copied(), + ) + .collect::>(); + + let mut new_bindings = Vec::new(); + expr_node.expr.extend_bindings(&mut new_bindings); + + let old_bindings = + std::mem::replace(&mut expr_node.bindings, new_bindings.clone()); + + let new_values = new_bindings + .iter() + .map(|name| values.get(&**name).copied().unwrap_or(0.0)) + .collect::>(); + + expr_node.values = new_values; + + let old_inputs = (0..old_bindings.len()) + .map(|idx| { + snarl.in_pin(InPinId { + node: pin.id.node, + input: idx, + }) + }) + .collect::>(); + + for (idx, name) in old_bindings.iter().enumerate() { + let new_idx = + new_bindings.iter().position(|new_name| *new_name == *name); + + match new_idx { + None => { + snarl.drop_inputs(old_inputs[idx].id); + } + Some(new_idx) if new_idx != idx => { + let new_in_pin = InPinId { + node: pin.id.node, + input: new_idx, + }; + for &remote in &old_inputs[idx].remotes { + snarl.disconnect(remote, old_inputs[idx].id); + snarl.connect(remote, new_in_pin); + } + } + _ => {} + } + } + } + Err(_) => {} + } + } + PinInfo::triangle().with_fill(STRING_COLOR) + } + DemoNode::ExprNode(ref expr_node) => { + if pin.id.input <= expr_node.bindings.len() { + match &*pin.remotes { + [] => { + let node = &mut snarl[pin.id.node]; + ui.label(node.label_in(pin.id.input)); + ui.add(egui::DragValue::new(node.number_in(pin.id.input))); + PinInfo::square().with_fill(NUMBER_COLOR) + } + [remote] => { + let new_value = snarl[remote.node].number_out(); + let node = &mut snarl[pin.id.node]; + ui.label(node.label_in(pin.id.input)); + ui.label(format_float(new_value)); + *node.number_in(pin.id.input) = new_value; + PinInfo::square().with_fill(NUMBER_COLOR) + } + _ => unreachable!("Expr pins has only one wire"), + } + } else { + ui.label("Removed"); + PinInfo::circle().with_fill(Color32::BLACK) + } + } + } + } + + fn show_output( + &mut self, + pin: &OutPin, + ui: &mut Ui, + _scale: f32, + snarl: &mut Snarl, + ) -> PinInfo { + match snarl[pin.id.node] { + DemoNode::Sink => { + unreachable!("Sink node has no outputs") + } + DemoNode::Number(ref mut value) => { + assert_eq!(pin.id.output, 0, "Number node has only one output"); + ui.add(egui::DragValue::new(value)); + PinInfo::square().with_fill(NUMBER_COLOR) + } + DemoNode::String(ref mut value) => { + assert_eq!(pin.id.output, 0, "String node has only one output"); + let edit = egui::TextEdit::singleline(value) + .clip_text(false) + .desired_width(0.0) + .margin(ui.spacing().item_spacing); + ui.add(edit); + PinInfo::triangle().with_fill(STRING_COLOR) + } + DemoNode::ExprNode(ref expr_node) => { + let value = expr_node.eval(); + assert_eq!(pin.id.output, 0, "Expr node has only one output"); + ui.label(format_float(value)); + PinInfo::square().with_fill(NUMBER_COLOR) + } + DemoNode::ShowImage(_) => { + ui.allocate_at_least(egui::Vec2::ZERO, egui::Sense::hover()); + PinInfo::circle().with_fill(IMAGE_COLOR) + } + } + } + + fn input_color( + &mut self, + pin: &InPin, + _style: &egui::Style, + snarl: &mut Snarl, + ) -> Color32 { + match snarl[pin.id.node] { + DemoNode::Sink => { + assert_eq!(pin.id.input, 0, "Sink node has only one input"); + match &*pin.remotes { + [] => UNTYPED_COLOR, + [remote] => match snarl[remote.node] { + DemoNode::Sink => unreachable!("Sink node has no outputs"), + DemoNode::Number(_) => NUMBER_COLOR, + DemoNode::String(_) => STRING_COLOR, + DemoNode::ExprNode(_) => NUMBER_COLOR, + DemoNode::ShowImage(_) => IMAGE_COLOR, + }, + _ => unreachable!("Sink input has only one wire"), + } + } + DemoNode::Number(_) => { + unreachable!("Number node has no inputs") + } + DemoNode::String(_) => { + unreachable!("String node has no inputs") + } + DemoNode::ShowImage(_) => STRING_COLOR, + DemoNode::ExprNode(_) => { + if pin.id.input == 0 { + STRING_COLOR + } else { + NUMBER_COLOR + } + } + } + } + + fn output_color( + &mut self, + pin: &OutPin, + _style: &egui::Style, + snarl: &mut Snarl, + ) -> Color32 { + match snarl[pin.id.node] { + DemoNode::Sink => { + unreachable!("Sink node has no outputs") + } + DemoNode::Number(_) => NUMBER_COLOR, + DemoNode::String(_) => STRING_COLOR, + DemoNode::ShowImage(_) => IMAGE_COLOR, + DemoNode::ExprNode(_) => NUMBER_COLOR, + } + } + + fn graph_menu( + &mut self, + pos: egui::Pos2, + ui: &mut Ui, + _scale: f32, + snarl: &mut Snarl, + ) { + ui.label("Add node"); + if ui.button("Number").clicked() { + snarl.insert_node(pos, DemoNode::Number(0.0)); + ui.close_menu(); + } + if ui.button("Expr").clicked() { + snarl.insert_node(pos, DemoNode::ExprNode(ExprNode::new())); + ui.close_menu(); + } + if ui.button("String").clicked() { + snarl.insert_node(pos, DemoNode::String("".to_owned())); + ui.close_menu(); + } + if ui.button("Show image").clicked() { + snarl.insert_node(pos, DemoNode::ShowImage("".to_owned())); + ui.close_menu(); + } + if ui.button("Sink").clicked() { + snarl.insert_node(pos, DemoNode::Sink); + ui.close_menu(); + } + } + + fn graph_menu_for_dropped_wire( + &mut self, + pos: egui::Pos2, + ui: &mut Ui, + _scale: f32, + src_pins: AnyPins, + snarl: &mut Snarl, + ) { + // In this demo, we create a context-aware node graph menu, and connect a wire + // dropped on the fly based on user input to a new node created. + // + // In your implementation, you may want to define specifications for each node's + // pin inputs and outputs and compatibility to make this easier. + + ui.label("Add node"); + + type PinCompat = usize; + const PIN_NUM: PinCompat = 1; + const PIN_STR: PinCompat = 2; + const PIN_IMG: PinCompat = 4; + const PIN_SINK: PinCompat = PIN_NUM | PIN_STR | PIN_IMG; + + fn pin_out_compat(node: &DemoNode) -> PinCompat { + match node { + DemoNode::Sink => 0, + DemoNode::Number(_) => PIN_NUM, + DemoNode::String(_) => PIN_STR, + DemoNode::ShowImage(_) => PIN_IMG, + DemoNode::ExprNode(_) => PIN_NUM, + } + } + + fn pin_in_compat(node: &DemoNode) -> PinCompat { + match node { + DemoNode::Sink => PIN_SINK, + DemoNode::Number(_) => 0, + DemoNode::String(_) => 0, + DemoNode::ShowImage(_) => PIN_STR, + DemoNode::ExprNode(_) => PIN_STR, + } + } + + match src_pins { + AnyPins::Out(src_pins) => { + assert!( + src_pins.len() == 1, + "There's no concept of multi-input nodes in this demo" + ); + + let src_pin = src_pins[0]; + let src_out_ty = pin_out_compat(snarl.get_node(src_pin.node).unwrap()); + let dst_in_candidates = [ + ("Sink", (|| DemoNode::Sink) as fn() -> DemoNode, PIN_SINK), + ("Show Image", || DemoNode::ShowImage("".to_owned()), PIN_STR), + ("Expr", || DemoNode::ExprNode(ExprNode::new()), PIN_STR), + ]; + + for (name, ctor, in_ty) in dst_in_candidates { + if src_out_ty & in_ty != 0 { + if ui.button(name).clicked() { + // Create new node. + let new_node = snarl.insert_node(pos, ctor()); + let dst_pin = InPinId { + node: new_node, + input: 0, + }; + + // Connect the wire. + snarl.connect(src_pin, dst_pin); + ui.close_menu(); + } + } + } + } + AnyPins::In(pins) => { + let all_src_types = pins.iter().fold(0, |acc, pin| { + acc | pin_in_compat(snarl.get_node(pin.node).unwrap()) + }); + + let dst_out_candidates = [ + ( + "Number", + (|| DemoNode::Number(0.)) as fn() -> DemoNode, + PIN_NUM, + ), + ("String", || DemoNode::String("".to_owned()), PIN_STR), + ("Expr", || DemoNode::ExprNode(ExprNode::new()), PIN_NUM), + ("Show Image", || DemoNode::ShowImage("".to_owned()), PIN_IMG), + ]; + + for (name, ctor, out_ty) in dst_out_candidates { + if all_src_types & out_ty != 0 { + if ui.button(name).clicked() { + // Create new node. + let new_node = ctor(); + let dst_ty = pin_out_compat(&new_node); + + let new_node = snarl.insert_node(pos, new_node); + let dst_pin = OutPinId { + node: new_node, + output: 0, + }; + + // Connect the wire. + for src_pin in pins { + let src_ty = pin_in_compat(snarl.get_node(src_pin.node).unwrap()); + if src_ty & dst_ty != 0 { + // In this demo, input pin MUST be unique ... + // Therefore here we drop inputs of source input pin. + snarl.drop_inputs(*src_pin); + snarl.connect(dst_pin, *src_pin); + ui.close_menu(); + } + } + } + } + } + } + }; + } + + fn node_menu( + &mut self, + node: NodeId, + _inputs: &[InPin], + _outputs: &[OutPin], + ui: &mut Ui, + _scale: f32, + snarl: &mut Snarl, + ) { + ui.label("Node menu"); + if ui.button("Remove").clicked() { + snarl.remove_node(node); + ui.close_menu(); + } + } + + fn has_on_hover_popup(&mut self, _: &DemoNode) -> bool { + true + } + + fn show_on_hover_popup( + &mut self, + node: NodeId, + _inputs: &[InPin], + _outputs: &[OutPin], + ui: &mut Ui, + _scale: f32, + snarl: &mut Snarl, + ) { + match snarl[node] { + DemoNode::Sink => { + ui.label("Displays anything connected to it"); + } + DemoNode::Number(_) => { + ui.label("Outputs integer value"); + } + DemoNode::String(_) => { + ui.label("Outputs string value"); + } + DemoNode::ShowImage(_) => { + ui.label("Displays image from URL in input"); + } + DemoNode::ExprNode(_) => { + ui.label("Evaluates algebraic expression with input for each unique variable name"); + } + } + } +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +struct ExprNode { + text: String, + bindings: Vec, + values: Vec, + expr: Expr, +} + +impl ExprNode { + fn new() -> Self { + ExprNode { + text: format!("0"), + bindings: Vec::new(), + values: Vec::new(), + expr: Expr::Val(0.0), + } + } + + fn eval(&self) -> f64 { + self.expr.eval(&self.bindings, &self.values) + } +} + +#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)] +enum UnOp { + Pos, + Neg, +} + +#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)] +enum BinOp { + Add, + Sub, + Mul, + Div, +} + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +enum Expr { + Var(String), + Val(f64), + UnOp { + op: UnOp, + expr: Box, + }, + BinOp { + lhs: Box, + op: BinOp, + rhs: Box, + }, +} + +impl Expr { + fn eval(&self, bindings: &[String], args: &[f64]) -> f64 { + let binding_index = + |name: &str| bindings.iter().position(|binding| binding == name).unwrap(); + + match self { + Expr::Var(ref name) => args[binding_index(name)], + Expr::Val(value) => *value, + Expr::UnOp { op, ref expr } => match op { + UnOp::Pos => expr.eval(bindings, args), + UnOp::Neg => -expr.eval(bindings, args), + }, + Expr::BinOp { + ref lhs, + op, + ref rhs, + } => match op { + BinOp::Add => lhs.eval(bindings, args) + rhs.eval(bindings, args), + BinOp::Sub => lhs.eval(bindings, args) - rhs.eval(bindings, args), + BinOp::Mul => lhs.eval(bindings, args) * rhs.eval(bindings, args), + BinOp::Div => lhs.eval(bindings, args) / rhs.eval(bindings, args), + }, + } + } + + fn extend_bindings(&self, bindings: &mut Vec) { + match self { + Expr::Var(name) => { + if !bindings.contains(name) { + bindings.push(name.clone()); + } + } + Expr::Val(_) => {} + Expr::UnOp { expr, .. } => { + expr.extend_bindings(bindings); + } + Expr::BinOp { lhs, rhs, .. } => { + lhs.extend_bindings(bindings); + rhs.extend_bindings(bindings); + } + } + } +} + +impl syn::parse::Parse for UnOp { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(syn::Token![+]) { + input.parse::()?; + Ok(UnOp::Pos) + } else if lookahead.peek(syn::Token![-]) { + input.parse::()?; + Ok(UnOp::Neg) + } else { + Err(lookahead.error()) + } + } +} + +impl syn::parse::Parse for BinOp { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(syn::Token![+]) { + input.parse::()?; + Ok(BinOp::Add) + } else if lookahead.peek(syn::Token![-]) { + input.parse::()?; + Ok(BinOp::Sub) + } else if lookahead.peek(syn::Token![*]) { + input.parse::()?; + Ok(BinOp::Mul) + } else if lookahead.peek(syn::Token![/]) { + input.parse::()?; + Ok(BinOp::Div) + } else { + Err(lookahead.error()) + } + } +} + +impl syn::parse::Parse for Expr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + let lhs; + if lookahead.peek(syn::token::Paren) { + let content; + syn::parenthesized!(content in input); + let expr = content.parse::()?; + if input.is_empty() { + return Ok(expr); + } + lhs = expr; + // } else if lookahead.peek(syn::LitFloat) { + // let lit = input.parse::()?; + // let value = lit.base10_parse::()?; + // let expr = Expr::Val(value); + // if input.is_empty() { + // return Ok(expr); + // } + // lhs = expr; + } else if lookahead.peek(syn::LitInt) { + let lit = input.parse::()?; + let value = lit.base10_parse::()?; + let expr = Expr::Val(value); + if input.is_empty() { + return Ok(expr); + } + lhs = expr; + } else if lookahead.peek(syn::Ident) { + let ident = input.parse::()?; + let expr = Expr::Var(ident.to_string()); + if input.is_empty() { + return Ok(expr); + } + lhs = expr; + } else { + let unop = input.parse::()?; + + return Self::parse_with_unop(unop, input); + } + + let binop = input.parse::()?; + + Self::parse_binop(Box::new(lhs), binop, input) + } +} + +impl Expr { + fn parse_with_unop(op: UnOp, input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + let lhs; + if lookahead.peek(syn::token::Paren) { + let content; + syn::parenthesized!(content in input); + let expr = Expr::UnOp { + op, + expr: Box::new(content.parse::()?), + }; + if input.is_empty() { + return Ok(expr); + } + lhs = expr; + } else if lookahead.peek(syn::LitFloat) { + let lit = input.parse::()?; + let value = lit.base10_parse::()?; + let expr = Expr::UnOp { + op, + expr: Box::new(Expr::Val(value)), + }; + if input.is_empty() { + return Ok(expr); + } + lhs = expr; + } else if lookahead.peek(syn::LitInt) { + let lit = input.parse::()?; + let value = lit.base10_parse::()?; + let expr = Expr::UnOp { + op, + expr: Box::new(Expr::Val(value)), + }; + if input.is_empty() { + return Ok(expr); + } + lhs = expr; + } else if lookahead.peek(syn::Ident) { + let ident = input.parse::()?; + let expr = Expr::UnOp { + op, + expr: Box::new(Expr::Var(ident.to_string())), + }; + if input.is_empty() { + return Ok(expr); + } + lhs = expr; + } else { + return Err(lookahead.error()); + } + + let op = input.parse::()?; + + Self::parse_binop(Box::new(lhs), op, input) + } + + fn parse_binop(lhs: Box, op: BinOp, input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + let rhs; + if lookahead.peek(syn::token::Paren) { + let content; + syn::parenthesized!(content in input); + rhs = Box::new(content.parse::()?); + if input.is_empty() { + return Ok(Expr::BinOp { lhs, op, rhs }); + } + } else if lookahead.peek(syn::LitFloat) { + let lit = input.parse::()?; + let value = lit.base10_parse::()?; + rhs = Box::new(Expr::Val(value)); + if input.is_empty() { + return Ok(Expr::BinOp { lhs, op, rhs }); + } + } else if lookahead.peek(syn::LitInt) { + let lit = input.parse::()?; + let value = lit.base10_parse::()?; + rhs = Box::new(Expr::Val(value)); + if input.is_empty() { + return Ok(Expr::BinOp { lhs, op, rhs }); + } + } else if lookahead.peek(syn::Ident) { + let ident = input.parse::()?; + rhs = Box::new(Expr::Var(ident.to_string())); + if input.is_empty() { + return Ok(Expr::BinOp { lhs, op, rhs }); + } + } else { + return Err(lookahead.error()); + } + + let next_op = input.parse::()?; + + match (op, next_op) { + (BinOp::Add | BinOp::Sub, BinOp::Mul | BinOp::Div) => { + let rhs = Self::parse_binop(rhs, next_op, input)?; + Ok(Expr::BinOp { + lhs, + op, + rhs: Box::new(rhs), + }) + } + _ => { + let lhs = Expr::BinOp { lhs, op, rhs }; + Self::parse_binop(Box::new(lhs), next_op, input) + } + } + } +} + +pub struct DemoApp { + snarl: Snarl, + style: SnarlStyle, +} + +impl DemoApp { + pub fn new() -> Self { + // let snarl = match cx.storage { + // None => Snarl::new(), + // Some(storage) => { + // let snarl = storage + // .get_string("snarl") + // .and_then(|snarl| serde_json::from_str(&snarl).ok()) + // .unwrap_or_else(Snarl::new); + + // snarl + // } + // }; + let snarl = Snarl::new(); + + // let style = match cx.storage { + // None => SnarlStyle::new(), + // Some(storage) => { + // let style = storage + // .get_string("style") + // .and_then(|style| serde_json::from_str(&style).ok()) + // .unwrap_or_else(SnarlStyle::new); + + // style + // } + // }; + let style = SnarlStyle::new(); + + DemoApp { snarl, style } + } + + pub fn update(&mut self, ctx: &egui::Context,) { + // egui_extras::install_image_loaders(ctx); + + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + // The top panel is often a good place for a menu bar: + + egui::menu::bar(ui, |ui| { + #[cfg(not(target_arch = "wasm32"))] + { + ui.menu_button("File", |ui| { + if ui.button("Quit").clicked() { + ctx.send_viewport_cmd(egui::ViewportCommand::Close) + } + }); + ui.add_space(16.0); + } + + egui::widgets::global_dark_light_mode_switch(ui); + + if ui.button("Clear All").clicked() { + self.snarl = Default::default(); + } + }); + }); + + egui::SidePanel::left("style").show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + // egui_probe::Probe::new("Snarl style", &mut self.style).show(ui); + }); + }); + + egui::CentralPanel::default().show(ctx, |ui| { + self.snarl + .show(&mut DemoViewer, &self.style, egui::Id::new("snarl"), ui); + }); + } +} + +fn format_float(v: f64) -> String { + let v = (v * 1000.0).round() / 1000.0; + format!("{}", v) +} diff --git a/modules/khors-graphics/src/renderer.rs b/modules/khors-graphics/src/renderer.rs new file mode 100644 index 0000000..89d19e8 --- /dev/null +++ b/modules/khors-graphics/src/renderer.rs @@ -0,0 +1,504 @@ +use flax::{entity_ids, BoxedSystem, Query, QueryBorrow, Schedule, System, World}; +use glam::{ + f32::{Mat3, Vec3}, + Mat4, +}; +use std::sync::Arc; +use vulkano::{ + buffer::{ + allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, + Buffer, BufferCreateInfo, BufferUsage, + }, + command_buffer::{ + allocator::{CommandBufferAllocator, StandardCommandBufferAllocator}, + CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer, + RenderPassBeginInfo, + }, + descriptor_set::{ + allocator::StandardDescriptorSetAllocator, DescriptorSet, WriteDescriptorSet + }, + device::DeviceOwned, + format::Format, + image::{view::ImageView, Image, ImageCreateInfo, ImageType, ImageUsage}, + memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, + pipeline::{graphics::viewport::Viewport, GraphicsPipeline, Pipeline, PipelineBindPoint}, + render_pass::{Framebuffer, RenderPass}, + shader::EntryPoint, + sync::GpuFuture, +}; +use vulkano_util::{ + context::VulkanoContext, renderer::VulkanoWindowRenderer, window::VulkanoWindows, +}; + +use crate::{node_editor, render_module::RenderModule as ThreadLocalModule, vulkan::renderpass::make_render_pass}; +use crate::{ + debug_gui::DebugGuiStack, + vulkan::{ + framebuffer::make_framebuffer, + pipeline::{make_pipeline, PassInfo, PipelineInfo}, + }, +}; +use egui_vulkano::Gui; +use super::node_editor::DemoApp; + +use crate::temp::model::{INDICES, NORMALS, POSITIONS}; + +pub struct RenderModule { + schedule: Schedule, + memory_allocator: Arc, + descriptor_set_allocator: Arc, + command_buffer_allocator: Arc, + viewport: Viewport, + rotation_start: std::time::Instant, + node_editor: DemoApp, +} + +impl RenderModule { + pub fn new( + vk_context: &mut VulkanoContext, + _vk_windows: &mut VulkanoWindows, + _schedule: &mut Schedule, + _world: &mut World, + _events: &mut khors_core::events::Events, + ) -> Self { + let schedule = Schedule::builder() + .with_system(add_distance_system()) + .build(); + + let memory_allocator = Arc::new(StandardMemoryAllocator::new_default( + vk_context.device().clone(), + )); + + let descriptor_set_allocator = Arc::new(StandardDescriptorSetAllocator::new( + vk_context.device().clone(), + Default::default(), + )); + + let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( + vk_context.device().clone(), + Default::default(), + )); + + let viewport = Viewport { + offset: [0.0, 0.0], + extent: [0.0, 0.0], + depth_range: 0.0..=1.0, + }; + + let node_editor = DemoApp::new(); + + let rotation_start = std::time::Instant::now(); + + Self { + schedule, + memory_allocator, + descriptor_set_allocator, + command_buffer_allocator, + viewport, + rotation_start, + node_editor, + } + } +} + +impl ThreadLocalModule for RenderModule { + fn on_update( + &mut self, + gui_stack: &mut DebugGuiStack, + vk_context: &mut VulkanoContext, + vk_windows: &mut vulkano_util::window::VulkanoWindows, + world: &mut World, + _events: &mut khors_core::events::Events, + _frame_time: std::time::Duration, + ) -> anyhow::Result<()> { + self.schedule.execute_seq(world).unwrap(); + + let viewport = &mut self.viewport; + + for (window_id, renderer) in vk_windows.iter_mut() { + let gui = gui_stack.get_mut(*window_id).unwrap(); + let node_editor = &mut self.node_editor; + draw( + vk_context.device().clone(), + self.memory_allocator.clone(), + self.descriptor_set_allocator.clone(), + self.command_buffer_allocator.clone(), + viewport, + vk_context, + renderer, + gui, + node_editor, + self.rotation_start, + ); + } + Ok(()) + } +} + +pub fn add_distance_system() -> BoxedSystem { + let query = Query::new(entity_ids()); + + System::builder() + .with_query(query) + .build(|mut query: QueryBorrow<'_, flax::EntityIds, _>| { + for _id in &mut query { + // println!("----------: {}", _id.index()); + } + }) + .boxed() +} + +#[allow(clippy::too_many_arguments)] +fn draw( + device: Arc, + memory_allocator: Arc, + descriptor_set_allocator: Arc, + command_buffer_allocator: Arc, + _viewport: &mut Viewport, + context: &mut VulkanoContext, + renderer: &mut VulkanoWindowRenderer, + gui: &mut Gui, + node_editor: &mut DemoApp, + rotation_start: std::time::Instant, +) { + let vertex_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + POSITIONS, + ) + .unwrap(); + let normals_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::VERTEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + NORMALS, + ) + .unwrap(); + let index_buffer = Buffer::from_iter( + memory_allocator.clone(), + BufferCreateInfo { + usage: BufferUsage::INDEX_BUFFER, + ..Default::default() + }, + AllocationCreateInfo { + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + INDICES, + ) + .unwrap(); + + let uniform_buffer = SubbufferAllocator::new( + memory_allocator.clone(), + SubbufferAllocatorCreateInfo { + buffer_usage: BufferUsage::UNIFORM_BUFFER, + memory_type_filter: MemoryTypeFilter::PREFER_DEVICE + | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, + ..Default::default() + }, + ); + + // let render_pass = vulkano::single_pass_renderpass!( + // device.clone(), + // attachments: { + // color: { + // format: renderer.swapchain_format(), + // samples: 1, + // load_op: Clear, + // store_op: Store, + // }, + // depth_stencil: { + // format: Format::D16_UNORM, + // samples: 1, + // load_op: Clear, + // store_op: DontCare, + // }, + // }, + // pass: { + // color: [color], + // depth_stencil: {depth_stencil}, + // }, + // ) + // .unwrap(); + + let render_pass = make_render_pass(device.clone(), renderer); + + let vs = vs::load(device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + let fs = fs::load(device.clone()) + .unwrap() + .entry_point("main") + .unwrap(); + + let (mut pipeline, mut framebuffers) = window_size_dependent_setup( + memory_allocator.clone(), + vs.clone(), + fs.clone(), + renderer.swapchain_image_views(), + render_pass.clone(), + ); + + // Do not draw the frame when the screen size is zero. On Windows, this can + // occur when minimizing the application. + let image_extent: [u32; 2] = renderer.window().inner_size().into(); + + if image_extent.contains(&0) { + return; + } + + // Begin rendering by acquiring the gpu future from the window renderer. + let previous_frame_end = renderer + .acquire(None, |swapchain_images| { + // Whenever the window resizes we need to recreate everything dependent + // on the window size. In this example that + // includes the swapchain, the framebuffers + // and the dynamic state viewport. + let (new_pipeline, new_framebuffers) = window_size_dependent_setup( + memory_allocator.clone(), + vs.clone(), + fs.clone(), + swapchain_images, + render_pass.clone(), + ); + + pipeline = new_pipeline; + framebuffers = new_framebuffers; + }) + .unwrap(); + + let uniform_buffer_subbuffer = { + let elapsed = rotation_start.elapsed(); + let rotation = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1_000_000_000.0; + let rotation = Mat3::from_rotation_y(rotation as f32); + + // NOTE: This teapot was meant for OpenGL where the origin is at the lower left + // instead the origin is at the upper left in Vulkan, so we reverse the Y axis. + let aspect_ratio = renderer.aspect_ratio(); + + let proj = Mat4::perspective_rh_gl(std::f32::consts::FRAC_PI_2, aspect_ratio, 0.01, 100.0); + let view = Mat4::look_at_rh( + Vec3::new(0.4, 0.3, 1.0), + Vec3::new(0.0, 0.0, 0.0), + Vec3::new(0.0, -1.0, 0.0), + ); + let scale = Mat4::from_scale(Vec3::splat(0.01)); + + let uniform_data = vs::Data { + world: Mat4::from_mat3(rotation).to_cols_array_2d(), + view: (view * scale).to_cols_array_2d(), + proj: proj.to_cols_array_2d(), + }; + + let subbuffer = uniform_buffer.allocate_sized().unwrap(); + *subbuffer.write().unwrap() = uniform_data; + + subbuffer + }; + + let mut builder = RecordingCommandBuffer::new( + command_buffer_allocator.clone(), + context.graphics_queue().queue_family_index(), + CommandBufferLevel::Primary, + CommandBufferBeginInfo { + usage: CommandBufferUsage::OneTimeSubmit, + ..Default::default() + }, + ) + .unwrap(); + + let layout = &pipeline.layout().set_layouts()[0]; + let set = DescriptorSet::new( + descriptor_set_allocator.clone(), + layout.clone(), + [WriteDescriptorSet::buffer(0, uniform_buffer_subbuffer)], + [], + ) + .unwrap(); + + builder + .begin_render_pass( + RenderPassBeginInfo { + clear_values: vec![Some([0.0, 0.0, 1.0, 1.0].into()), Some(1f32.into())], + ..RenderPassBeginInfo::framebuffer( + framebuffers[renderer.image_index() as usize].clone(), + ) + }, + Default::default(), + ) + .unwrap() + .bind_pipeline_graphics(pipeline.clone()) + .unwrap() + .bind_descriptor_sets( + PipelineBindPoint::Graphics, + pipeline.layout().clone(), + 0, + set, + ) + .unwrap() + .bind_vertex_buffers(0, (vertex_buffer.clone(), normals_buffer.clone())) + .unwrap() + .bind_index_buffer(index_buffer.clone()) + .unwrap(); + + unsafe { + builder + .draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0) + .unwrap(); + } + + builder.end_render_pass(Default::default()).unwrap(); + // Finish recording the command buffer by calling `end`. + let command_buffer = builder.end().unwrap(); + + draw_gui(gui, node_editor); + + let before_future = previous_frame_end + .then_execute(context.graphics_queue().clone(), command_buffer) + .unwrap() + .boxed(); + + let after_future = gui + .draw_on_image(before_future, renderer.swapchain_image_view()) + .boxed(); + + // The color output is now expected to contain our triangle. But in order to + // show it on the screen, we have to *present* the image by calling + // `present` on the window renderer. + // + // This function does not actually present the image immediately. Instead it + // submits a present command at the end of the queue. This means that it will + // only be presented once the GPU has finished executing the command buffer + // that draws the triangle. + renderer.present(after_future, true); +} + +fn draw_gui(gui: &mut Gui, node_editor: &mut DemoApp) { + let mut code = CODE.to_owned(); + + gui.immediate_ui(|gui| { + let ctx = gui.context(); + + node_editor.update(&ctx); + + egui::Window::new("Colors").vscroll(true).show(&ctx, |ui| { + ui.vertical_centered(|ui| { + ui.add(egui::widgets::Label::new("Hi there!")); + sized_text(ui, "Rich Text", 32.0); + }); + ui.separator(); + ui.columns(2, |columns| { + egui::ScrollArea::vertical() + .id_source("source") + .show(&mut columns[0], |ui| { + ui.add( + egui::TextEdit::multiline(&mut code).font(egui::TextStyle::Monospace), + ); + }); + egui::ScrollArea::vertical() + .id_source("rendered") + .show(&mut columns[1], |ui| { + ui.add(egui::widgets::Label::new("Good day!")); + }); + }); + }); + }); +} + +fn sized_text(ui: &mut egui::Ui, text: impl Into, size: f32) { + ui.label( + egui::RichText::new(text) + .size(size) + .family(egui::FontFamily::Monospace), + ); +} + +const CODE: &str = r" +# Some markup +``` +let mut gui = Gui::new(&event_loop, renderer.surface(), None, renderer.queue(), SampleCount::Sample1); +``` +"; + +fn window_size_dependent_setup( + memory_allocator: Arc, + vs: EntryPoint, + fs: EntryPoint, + image_views: &[Arc], + render_pass: Arc, +) -> (Arc, Vec>) { + let device = memory_allocator.device().clone(); + + let extent = image_views[0].image().extent(); + + let depth_buffer = ImageView::new_default( + Image::new( + memory_allocator, + ImageCreateInfo { + image_type: ImageType::Dim2d, + format: Format::D16_UNORM, + extent, + usage: ImageUsage::DEPTH_STENCIL_ATTACHMENT | ImageUsage::TRANSIENT_ATTACHMENT, + ..Default::default() + }, + AllocationCreateInfo::default(), + ) + .unwrap(), + ) + .unwrap(); + + let framebuffers = image_views + .iter() + .map(|image_view| { + make_framebuffer( + render_pass.clone(), + vec![image_view.clone(), depth_buffer.clone()], + ) + .unwrap() + }) + .collect::>(); + + // In the triangle example we use a dynamic viewport, as its a simple example. However in the + // teapot example, we recreate the pipelines with a hardcoded viewport instead. This allows the + // driver to optimize things, at the cost of slower window resizes. + // https://computergraphics.stackexchange.com/questions/5742/vulkan-best-way-of-updating-pipeline-viewport + let pipeline_info = PipelineInfo { vs, fs }; + + let pass_info = PassInfo::new(render_pass.clone(), 0, extent); + + let pipeline = make_pipeline(device.clone(), &pipeline_info, &pass_info); + + (pipeline, framebuffers) +} + +mod vs { + vulkano_shaders::shader! { + ty: "vertex", + path: "src/temp/vert.glsl", + } +} + +mod fs { + vulkano_shaders::shader! { + ty: "fragment", + path: "src/temp/frag.glsl", + } +} + diff --git a/modules/khors-graphics/src/rendergraph/error.rs b/modules/khors-graphics/src/rendergraph/error.rs new file mode 100644 index 0000000..ce01ae3 --- /dev/null +++ b/modules/khors-graphics/src/rendergraph/error.rs @@ -0,0 +1,33 @@ +use thiserror::Error; + +use super::{node::NodeKind, rendergraph::{NodeIndex, ResourceKind}}; + + +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Failed executing rendergraph node")] + NodeExecution(#[from] anyhow::Error), + + #[error("Rendergraph vulkan error")] + Vulkan(#[from] ivy_vulkan::Error), + + #[error("Rendergraph graphics error")] + Graphics(#[from] ivy_graphics::Error), + + #[error("Dependency cycle in rendergraph")] + DependencyCycle, + + #[error("Node read attachment is missing corresponding write attachment for {2:?} required by node {0:?}: {1:?}")] + MissingWrite(NodeIndex, &'static str, ResourceKind), + + #[error("Resource acquisition error")] + Resource(#[from] ivy_resources::Error), + + #[error("Invalid node index {0:?}")] + InvalidNodeIndex(NodeIndex), + + #[error("Specified node {0:?} is not the correct kind. Expected {1:?}, found {2:?}")] + InvalidNodeKind(NodeIndex, NodeKind, NodeKind), +} diff --git a/modules/khors-graphics/src/rendergraph/mod.rs b/modules/khors-graphics/src/rendergraph/mod.rs new file mode 100644 index 0000000..e1b59f6 --- /dev/null +++ b/modules/khors-graphics/src/rendergraph/mod.rs @@ -0,0 +1,7 @@ +// use flax::component; +// use vulkano::image::{view::ImageView, Image}; + +// pub mod node; +// pub mod pass; +// pub mod rendergraph; +// mod error; diff --git a/modules/khors-graphics/src/rendergraph/node.rs b/modules/khors-graphics/src/rendergraph/node.rs new file mode 100644 index 0000000..763c12e --- /dev/null +++ b/modules/khors-graphics/src/rendergraph/node.rs @@ -0,0 +1,172 @@ +use flax::World; +use vulkano::command_buffer::RecordingCommandBuffer; + +use crate::vulkan::pipeline::PassInfo; + +/// Represents a node in the renderpass. +pub trait Node: 'static + Send { + /// Returns the color attachments for this node. Should not be execution heavy function + fn color_attachments(&self) -> &[AttachmentInfo] { + &[] + } + + fn output_attachments(&self) -> &[Handle] { + &[] + } + /// Returns the read attachments for this node. Should not be execution heavy function + fn read_attachments(&self) -> &[Handle] { + &[] + } + /// Partially sampled input attachments. Read from the same pixel coord we write to + fn input_attachments(&self) -> &[Handle] { + &[] + } + /// Returns the optional depth attachment for this node. Should not be execution heavy function + fn depth_attachment(&self) -> Option<&AttachmentInfo> { + None + } + + fn buffer_reads(&self) -> &[Buffer] { + &[] + } + + fn buffer_writes(&self) -> &[Buffer] { + &[] + } + + /// Returns the clear values to initiate this renderpass + fn clear_values(&self) -> &[ClearValue] { + &[] + } + + fn node_kind(&self) -> NodeKind; + + // Optional name, can be empty string + fn debug_name(&self) -> &'static str; + + /// Execute this node inside a compatible renderpass + fn execute( + &mut self, + world: &mut World, + resources: &Resources, + cmd: &CommandBuffer, + pass_info: &PassInfo, + current_frame: usize, + ) -> anyhow::Result<()>; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NodeKind { + // A graphics rendering node. Renderpass and framebuffer will automatically be created. + Graphics, + // execution + // A node that will be executed on the transfer queue. Appropriate pipeline barriers will + // be inserted + Transfer, + // Compute, +} + +#[derive(Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +pub struct AttachmentInfo { + // TODO, derive from edges + pub store_op: StoreOp, + pub load_op: LoadOp, + pub initial_layout: ImageLayout, + pub final_layout: ImageLayout, + pub resource: Handle, + pub clear_value: ClearValue, +} + +impl Default for AttachmentInfo { + fn default() -> Self { + Self { + store_op: StoreOp::STORE, + load_op: LoadOp::DONT_CARE, + initial_layout: ImageLayout::UNDEFINED, + final_layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + resource: Handle::null(), + clear_value: ClearValue::default(), + } + } +} + +impl AttachmentInfo { + pub fn color(resource: Handle) -> Self { + Self { + store_op: StoreOp::STORE, + load_op: LoadOp::CLEAR, + initial_layout: ImageLayout::UNDEFINED, + final_layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + clear_value: ClearValue::color(0.0, 0.0, 0.0, 1.0), + resource, + } + } + + pub fn depth_discard(resource: Handle) -> Self { + Self { + store_op: StoreOp::DONT_CARE, + load_op: LoadOp::CLEAR, + initial_layout: ImageLayout::UNDEFINED, + final_layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + clear_value: ClearValue::depth_stencil(1.0, 0), + resource, + } + } + pub fn depth_store(resource: Handle) -> Self { + Self { + store_op: StoreOp::STORE, + load_op: LoadOp::CLEAR, + initial_layout: ImageLayout::UNDEFINED, + final_layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + clear_value: ClearValue::depth_stencil(1.0, 0), + resource, + } + } +} + +/// Simple node for rendering a pass in the rendergraph +pub struct RenderNode { + renderer: Handle, + marker: PhantomData, +} + +impl RenderNode { + pub fn new(renderer: Handle) -> Self { + Self { + renderer, + marker: PhantomData, + } + } +} + +impl Node for RenderNode +where + Pass: ShaderPass, + T: 'static + Renderer + Send + Sync, + E: Into, +{ + fn node_kind(&self) -> NodeKind { + NodeKind::Graphics + } + + fn execute( + &mut self, + world: &mut World, + resources: &Resources, + cmd: &CommandBuffer, + pass_info: &PassInfo, + current_frame: usize, + ) -> anyhow::Result<()> { + resources + .get_mut(self.renderer) + .with_context(|| format!("Failed to borrow {:?} mutably", type_name::()))? + .draw::(world, resources, cmd, &[], pass_info, &[], current_frame) + .map_err(|e| e.into()) + .with_context(|| format!("Failed to draw using {:?}", type_name::())) + } + + fn debug_name(&self) -> &'static str { + std::any::type_name::>() + } +} diff --git a/modules/khors-graphics/src/rendergraph/pass.rs b/modules/khors-graphics/src/rendergraph/pass.rs new file mode 100644 index 0000000..0f3f1f1 --- /dev/null +++ b/modules/khors-graphics/src/rendergraph/pass.rs @@ -0,0 +1,395 @@ +use std::collections::HashMap; + +use vulkano::{render_pass::Framebuffer, sync::ImageMemoryBarrier}; + +use super::{ + node::{Node, NodeKind}, + rendergraph::{Edge, NodeIndex, ResourceKind}, +}; + +pub struct Pass { + kind: PassKind, + nodes: Vec, +} + +impl Pass { + // Creates a new pass from a group of compatible nodes. + // Two nodes are compatible if: + // * They have no full read dependencies to another. + // * Belong to the same kind and queue. + // * Have the same dependency level. + pub fn new( + context: &SharedVulkanContext, + nodes: &Vec>, + textures: &T, + dependencies: &HashMap>, + pass_nodes: Vec, + kind: NodeKind, + extent: Extent, + ) -> Result + where + T: Deref>, + { + let kind = match kind { + NodeKind::Graphics => { + PassKind::graphics(context, nodes, textures, dependencies, &pass_nodes, extent)? + } + NodeKind::Transfer => { + PassKind::transfer(context, nodes, textures, dependencies, &pass_nodes, extent)? + } + }; + + Ok(Self { + kind, + nodes: pass_nodes, + }) + } + + pub fn execute( + &self, + world: &mut World, + cmd: &CommandBuffer, + nodes: &mut Vec>, + current_frame: usize, + extent: Extent, + ) -> Result<()> { + match &self.kind { + PassKind::Graphics { + renderpass, + framebuffer, + clear_values, + } => { + cmd.begin_renderpass(&renderpass, &framebuffer, extent, clear_values); + + self.nodes + .iter() + .enumerate() + .try_for_each(|(subpass, index)| -> Result<_> { + if subpass > 0 { + cmd.next_subpass(vk::SubpassContents::INLINE); + } + let node = &mut nodes[*index]; + + node.execute( + world, + resources, + cmd, + &PassInfo { + renderpass: renderpass.renderpass(), + subpass: subpass as u32, + extent, + color_attachment_count: node.color_attachments().len() as u32, + depth_attachment: node.depth_attachment().is_some(), + }, + current_frame, + )?; + Ok(()) + })?; + + cmd.end_renderpass(); + } + PassKind::Transfer { + src_stage, + image_barriers, + } => { + if !image_barriers.is_empty() { + cmd.pipeline_barrier( + *src_stage, + vk::PipelineStageFlags::TRANSFER, + &[], + image_barriers, + ); + } + + self.nodes.iter().try_for_each(|index| -> Result<_> { + nodes[*index] + .execute(world, resources, cmd, &PassInfo::default(), current_frame) + .map_err(|e| e.into()) + })?; + } + } + + Ok(()) + } + + /// Get a reference to the pass's kind. + pub fn kind(&self) -> &PassKind { + &self.kind + } + + /// Get a reference to the pass's nodes. + pub fn nodes(&self) -> &[NodeIndex] { + self.nodes.as_slice() + } +} + +pub enum PassKind { + Graphics { + renderpass: RenderPass, + framebuffer: Framebuffer, + clear_values: Vec, + // Index into first node of pass_nodes + }, + + Transfer { + src_stage: vk::PipelineStageFlags, + image_barriers: Vec, + }, +} + +unsafe impl Send for PassKind {} +unsafe impl Sync for PassKind {} + +impl PassKind { + fn graphics( + context: &SharedVulkanContext, + nodes: &Vec>, + textures: &T, + dependencies: &HashMap>, + pass_nodes: &[NodeIndex], + extent: Extent, + ) -> Result + where + T: Deref>, + { + println!( + "Building pass with nodes: {:?}", + pass_nodes + .iter() + .map(|v| nodes[*v].debug_name()) + .collect_vec() + ); + + // Collect clear values + let clear_values = pass_nodes + .iter() + .flat_map(|node| { + let node = &nodes[*node]; + node.clear_values() + .iter() + .cloned() + .chain(repeat(ClearValue::default()).take( + node.color_attachments().len() + node.depth_attachment().iter().count() + - node.clear_values().len(), + )) + }) + .collect::>(); + + // Generate subpass dependencies + let dependencies = pass_nodes + .iter() + .enumerate() + .flat_map(|(subpass_index, node_index)| { + // Get the dependencies of node. + dependencies + .get(node_index) + .into_iter() + .flat_map(|val| val.iter()) + .flat_map(move |edge| match edge.kind { + EdgeKind::Sampled => Some(SubpassDependency { + src_subpass: vk::SUBPASS_EXTERNAL, + dst_subpass: subpass_index as u32, + src_stage_mask: edge.write_stage, + dst_stage_mask: edge.read_stage, + src_access_mask: edge.write_access, + dst_access_mask: edge.read_access, + dependency_flags: Default::default(), + }), + EdgeKind::Input => Some(SubpassDependency { + src_subpass: pass_nodes + .iter() + .enumerate() + .find(|(_, node)| **node == edge.src) + .unwrap() + .0 as u32, + dst_subpass: subpass_index as u32, + src_stage_mask: edge.write_stage, + dst_stage_mask: edge.read_stage, + src_access_mask: edge.write_access, + dst_access_mask: edge.read_access, + dependency_flags: vk::DependencyFlags::BY_REGION, + }), + EdgeKind::Attachment => Some(SubpassDependency { + src_subpass: pass_nodes + .iter() + .enumerate() + .find(|(_, node)| **node == edge.src) + .map(|v| v.0 as u32) + .unwrap_or(vk::SUBPASS_EXTERNAL), + + dst_subpass: subpass_index as u32, + src_stage_mask: edge.write_stage, + dst_stage_mask: edge.read_stage, + src_access_mask: edge.write_access, + dst_access_mask: edge.read_access, + dependency_flags: vk::DependencyFlags::BY_REGION, + }), + EdgeKind::Buffer => None, + }) + }) + .collect::>(); + + let mut attachment_descriptions = Vec::new(); + let mut attachments = Vec::new(); + + let attachment_refs = pass_nodes + .iter() + .enumerate() + .map(|(_, node_index)| -> Result<_> { + let node = &nodes[*node_index]; + + let offset = attachments.len(); + + let color_attachments = node + .color_attachments() + .iter() + .enumerate() + .map(|(i, _)| AttachmentReference { + attachment: (i + offset) as u32, + layout: ImageLayout::COLOR_ATTACHMENT_OPTIMAL, + }) + .collect::>(); + + let input_attachments = node + .input_attachments() + .iter() + .map(|tex| -> Result<_> { + let view = textures.get(*tex)?.image_view(); + Ok(AttachmentReference { + attachment: attachments + .iter() + .enumerate() + .find(|(_, val)| view == **val) + .unwrap() + .0 as u32, + layout: ImageLayout::SHADER_READ_ONLY_OPTIMAL, + }) + }) + .collect::>>()?; + + let depth_attachment = + node.depth_attachment() + .as_ref() + .map(|_| AttachmentReference { + attachment: (color_attachments.len() + input_attachments.len() + offset) + as u32, + layout: ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }); + + for attachment in node + .color_attachments() + .iter() + .chain(node.depth_attachment().into_iter()) + { + let texture = textures.get(attachment.resource)?; + + attachments.push(texture.image_view()); + + attachment_descriptions.push(AttachmentDescription { + flags: vk::AttachmentDescriptionFlags::default(), + format: texture.format(), + samples: texture.samples(), + load_op: attachment.load_op, + store_op: attachment.store_op, + stencil_load_op: LoadOp::DONT_CARE, + stencil_store_op: StoreOp::DONT_CARE, + initial_layout: attachment.initial_layout, + final_layout: attachment.final_layout, + }) + } + Ok((color_attachments, input_attachments, depth_attachment)) + }) + .collect::>>()?; + + let subpasses = attachment_refs + .iter() + .map( + |(color_attachments, input_attachments, depth_attachment)| SubpassInfo { + color_attachments, + resolve_attachments: &[], + input_attachments: &input_attachments, + depth_attachment: *depth_attachment, + }, + ) + .collect::>(); + + let renderpass_info = RenderPassInfo { + attachments: &attachment_descriptions, + subpasses: &subpasses, + dependencies: &dependencies, + }; + + let renderpass = RenderPass::new(context.device().clone(), &renderpass_info)?; + + let framebuffer = + Framebuffer::new(context.device().clone(), &renderpass, &attachments, extent)?; + + Ok(PassKind::Graphics { + renderpass, + framebuffer, + clear_values, + }) + } + + fn transfer( + _context: &SharedVulkanContext, + _nodes: &Vec>, + textures: &T, + dependencies: &HashMap>, + pass_nodes: &[NodeIndex], + _extent: Extent, + ) -> Result + where + T: Deref>, + { + // Get the dependencies of node. + let mut src_stage = vk::PipelineStageFlags::default(); + + let image_barriers = dependencies + .get(&pass_nodes[0]) + .into_iter() + .flat_map(|val| val.iter()) + .filter_map(|val| { + if let ResourceKind::Texture(tex) = val.resource { + Some((val, tex)) + } else { + None + } + }) + .map(|(edge, texture)| -> Result<_> { + let src = textures.get(texture)?; + + let aspect_mask = + if edge.read_access == vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE { + vk::ImageAspectFlags::DEPTH + } else { + vk::ImageAspectFlags::COLOR + }; + src_stage = edge.write_stage.max(src_stage); + + Ok(ImageMemoryBarrier { + src_access_mask: edge.write_access, + dst_access_mask: vk::AccessFlags::TRANSFER_READ, + old_layout: edge.layout, + new_layout: ImageLayout::TRANSFER_SRC_OPTIMAL, + src_queue_family_index: vk::QUEUE_FAMILY_IGNORED, + dst_queue_family_index: vk::QUEUE_FAMILY_IGNORED, + image: src.image(), + subresource_range: vk::ImageSubresourceRange { + aspect_mask, + base_mip_level: 0, + level_count: src.mip_levels(), + base_array_layer: 0, + layer_count: 1, + }, + ..Default::default() + }) + }) + .collect::>>()?; + + Ok(Self::Transfer { + src_stage, + image_barriers, + }) + } +} diff --git a/modules/khors-graphics/src/rendergraph/rendergraph.rs b/modules/khors-graphics/src/rendergraph/rendergraph.rs new file mode 100644 index 0000000..10b7b60 --- /dev/null +++ b/modules/khors-graphics/src/rendergraph/rendergraph.rs @@ -0,0 +1,622 @@ +use super::error::Error; +use std::collections::HashMap; +use vulkano::{ + command_buffer::pool::{CommandPool, CommandPoolCreateFlags, CommandPoolCreateInfo}, + sync::{fence::Fence, semaphore::Semaphore}, +}; + +use super::{node::Node, pass::Pass}; +pub type PassIndex = usize; +pub type NodeIndex = usize; + +/// Direct acyclic graph abstraction for renderpasses, barriers and subpass dependencies. +pub struct RenderGraph { + context: SharedVulkanContext, + /// The unordered nodes in the arena. + nodes: Vec>, + edges: HashMap>, + passes: Vec, + // Maps a node to a pass index + node_pass_map: HashMap, + + // Data for each frame in flight + frames: Vec, + extent: Extent, + frames_in_flight: usize, + current_frame: usize, +} + +impl RenderGraph { + /// Creates a new empty rendergraph. + pub fn new( + context: SharedVulkanContext, + frames_in_flight: usize, + ) -> super::error::Result { + let frames = (0..frames_in_flight) + .map(|_| FrameData::new(context.clone()).map_err(|e| e.into())) + .collect::>>()?; + + Ok(Self { + context, + nodes: Default::default(), + edges: Default::default(), + passes: Vec::new(), + node_pass_map: Default::default(), + frames, + extent: Extent::new(0, 0), + frames_in_flight, + current_frame: 0, + }) + } + + /// Adds a new node into the rendergraph. + /// **Note**: The new node won't take effect until [`RenderGraph::build`] is called. + pub fn add_node(&mut self, node: T) -> NodeIndex { + let i = self.nodes.len(); + self.nodes.push(Box::new(node)); + i + } + + /// Add several nodes into the rendergraph. + /// Due to the concrete type of iterators, the nodes need to already be boxed. + /// Returns the node indices in order. + pub fn add_nodes>>(&mut self, nodes: I) -> Vec { + nodes + .into_iter() + .map(|node| { + let i = self.nodes.len(); + self.nodes.push(node); + i + }) + .collect_vec() + } + + pub fn build_edges( + &mut self, + ) -> crate::Result<(HashMap>, HashMap>)> { + // Iterate all node's read attachments, and find any node which has the same write + // attachment before the current node. + // Finally, automatically construct edges. + let nodes = &self.nodes; + let mut edges = HashMap::new(); + let mut dependencies = HashMap::new(); + + nodes + .iter() + .enumerate() + .flat_map(|node| { + EdgeConstructor { + nodes, + dst: node.0, + reads: node.1.input_attachments().into_iter().cloned(), + kind: EdgeKind::Input, + } + .chain(BufferEdgeConstructor { + nodes, + dst: node.0, + reads: node.1.buffer_reads().into_iter().cloned(), + kind: EdgeKind::Buffer, + }) + .chain(EdgeConstructor { + nodes, + dst: node.0, + reads: node.1.read_attachments().into_iter().cloned(), + kind: EdgeKind::Sampled, + }) + .chain( + EdgeConstructor { + nodes, + dst: node.0, + reads: node.1.color_attachments().iter().map(|v| v.resource), + kind: EdgeKind::Attachment, + } + .into_iter() + .filter(|val| !matches!(*val, Err(Error::MissingWrite(_, _, _)))), + ) + .chain( + EdgeConstructor { + nodes, + dst: node.0, + reads: node.1.output_attachments().into_iter().cloned(), + kind: EdgeKind::Sampled, + } + .into_iter() + .filter(|val| !matches!(*val, Err(Error::MissingWrite(_, _, _)))), + ) + }) + .try_for_each( + |edge: super::error::Result| -> super::error::Result<_> { + let edge = edge?; + edges.entry(edge.src).or_insert_with(Vec::new).push(edge); + + dependencies + .entry(edge.dst) + .or_insert_with(Vec::new) + .push(edge); + + Ok(()) + }, + )?; + + Ok((edges, dependencies)) + } + + pub fn node(&self, node: NodeIndex) -> super::error::Result<&dyn Node> { + self.nodes + .get(node) + .ok_or_else(|| Error::InvalidNodeIndex(node)) + .map(|val| val.as_ref()) + } + + pub fn node_renderpass<'a>( + &'a self, + node: NodeIndex, + ) -> super::error::Result<(&'a RenderPass, u32)> { + let (pass, index) = self + .node_pass_map + .get(&node) + .and_then(|(pass, subpass_index)| Some((self.passes.get(*pass)?, *subpass_index))) + .ok_or(Error::InvalidNodeIndex(node))?; + + match pass.kind() { + PassKind::Graphics { + renderpass, + framebuffer: _, + clear_values: _, + } => Ok((renderpass, index)), + PassKind::Transfer { .. } => Err(Error::InvalidNodeKind( + node, + NodeKind::Graphics, + self.nodes[node].node_kind(), + )), + } + } + + /// Builds or rebuilds the rendergraph and creates appropriate renderpasses and framebuffers. + pub fn build(&mut self, textures: T, extent: Extent) -> super::error::Result<()> + where + T: Deref>, + { + let (_edges, dependencies) = self.build_edges()?; + let (ordered, depths) = topological_sort(&self.nodes, &self.edges)?; + + let context = &self.context; + + let nodes = &self.nodes; + + self.node_pass_map.clear(); + + let node_pass_map = &mut self.node_pass_map; + node_pass_map.clear(); + + let passes = &mut self.passes; + passes.clear(); + + // Build all graphics nodes + let groups = ordered.iter().cloned().group_by(|node| { + return (depths[node], nodes[*node].node_kind()); + }); + + for (key, group) in &groups { + println!("New pass: {:?}", key); + let pass_nodes = group.collect_vec(); + + let pass = Pass::new( + context, + nodes, + &textures, + &dependencies, + pass_nodes, + key.1, + extent, + )?; + + // Insert pass into slotmap + let pass_index = passes.len(); + passes.push(pass); + + let pass_nodes = passes[pass_index].nodes(); + + // Map the node into the pass + for (i, node) in pass_nodes.iter().enumerate() { + node_pass_map.insert(*node, (pass_index, i as u32)); + } + } + + self.extent = extent; + + Ok(()) + } + + // Begins the current frame and ensures resources are ready by waiting on fences. + // Begins recording of the commandbuffers. + // Returns the current frame in flight + pub fn begin(&self) -> super::error::Result { + let frame = &self.frames[self.current_frame]; + let device = self.context.device(); + + // Make sure frame is available before beginning execution + fence::wait(device, &[frame.fence], true)?; + fence::reset(device, &[frame.fence])?; + + // Reset commandbuffers for this frame + frame.commandpool.reset(false)?; + + // Get the commandbuffer for this frame + let commandbuffer = &frame.commandbuffer; + + // Start recording + commandbuffer.begin(CommandBufferUsageFlags::ONE_TIME_SUBMIT)?; + + Ok(self.current_frame) + } + + // Executes the whole rendergraph by starting renderpass recording and filling it using the + // node execution functions. Submits the resulting commandbuffer. + pub fn execute(&mut self, world: &mut World) -> super::error::Result<()> { + // Reset all commandbuffers for this frame + let frame = &mut self.frames[self.current_frame]; + + let nodes = &mut self.nodes; + let passes = &self.passes; + let extent = self.extent; + let current_frame = self.current_frame; + + let cmd = &frame.commandbuffer; + + // Execute all nodes + passes + .iter() + .try_for_each(|pass| -> super::error::Result<()> { + pass.execute(world, &cmd, nodes, current_frame, extent) + })?; + + Ok(()) + } + + /// Ends and submits recording of commandbuffer for the current frame, and increments the + /// current_frame. + pub fn end(&mut self) -> super::error::Result<()> { + let frame = &self.frames[self.current_frame]; + let commandbuffer = &frame.commandbuffer; + commandbuffer.end()?; + + // Submit the results + commandbuffer.submit( + self.context.graphics_queue(), + &[frame.wait_semaphore], + &[frame.signal_semaphore], + frame.fence, + &[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT], + )?; + + // Move to the next frame in flight and wrap around to n-buffer + self.current_frame = (self.current_frame + 1) % self.frames_in_flight; + + Ok(()) + } + + /// Get a reference to the current signal semaphore for the specified frame + pub fn signal_semaphore(&self, current_frame: usize) -> Semaphore { + self.frames[current_frame].signal_semaphore + } + + /// Get a reference to the current wait semaphore for the specified frame + pub fn wait_semaphore(&self, current_frame: usize) -> Semaphore { + self.frames[current_frame].wait_semaphore + } + + /// Get a reference to the current fence for the specified frame + pub fn fence(&self, current_frame: usize) -> Fence { + self.frames[current_frame].fence + } + + pub fn commandbuffer(&self, current_frame: usize) -> &CommandBuffer { + &self.frames[current_frame].commandbuffer + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum VisitedState { + Pending, + Visited, +} + +type Depth = u32; + +/// Toplogically sorts the graph provided by nodes and edges. +/// Returns a tuple containing a dense array of ordered node indices, and a map containing each node's maximum depth. +// TODO: Move graph functionality into separate crate. +fn topological_sort( + nodes: &Vec>, + edges: &HashMap>, +) -> super::error::Result<(Vec, HashMap)> { + fn internal( + stack: &mut Vec, + visited: &mut HashMap, + depths: &mut HashMap, + current_node: NodeIndex, + edges: &HashMap>, + depth: Depth, + ) -> super::error::Result<()> { + // Update maximum recursion depth + depths + .entry(current_node) + .and_modify(|d| *d = (*d).max(depth)) + .or_insert(depth); + + // Node is already visited + match visited.get(¤t_node) { + Some(VisitedState::Pending) => return Err(Error::DependencyCycle), + Some(VisitedState::Visited) => return Ok(()), + _ => {} + }; + + visited.insert(current_node, VisitedState::Pending); + + // Add all children of `node`, before node to the stack. + edges + .get(¤t_node) + .iter() + .flat_map(|node_edges| node_edges.iter()) + .try_for_each(|edge| { + internal( + stack, + visited, + depths, + edge.dst, + edges, + // Break depth if sampling is required since they can't share subpasses + match edge.kind { + EdgeKind::Sampled | EdgeKind::Buffer => depth + 1, + EdgeKind::Attachment | EdgeKind::Input => depth, + }, + ) + })?; + + stack.push(current_node); + + visited.insert(current_node, VisitedState::Visited); + + Ok(()) + } + + let nodes_iter = nodes.into_iter(); + let cap = nodes_iter.size_hint().1.unwrap_or_default(); + let mut stack = Vec::with_capacity(cap); + let mut visited = HashMap::with_capacity(cap); + let mut depths = HashMap::with_capacity(cap); + + for (node, _) in nodes_iter.enumerate() { + internal(&mut stack, &mut visited, &mut depths, node, edges, 0)?; + } + + // stack.reverse(); + + Ok((stack, depths)) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Edge { + pub src: NodeIndex, + pub dst: NodeIndex, + pub resource: ResourceKind, + pub write_stage: PipelineStageFlags, + pub read_stage: PipelineStageFlags, + pub write_access: vk::AccessFlags, + pub read_access: vk::AccessFlags, + pub layout: ImageLayout, + pub kind: EdgeKind, +} + +impl std::ops::Deref for Edge { + type Target = ResourceKind; + + fn deref(&self) -> &Self::Target { + &self.resource + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ResourceKind { + Texture(Handle), + Buffer(vk::Buffer), +} + +impl From for ResourceKind { + fn from(val: vk::Buffer) -> Self { + Self::Buffer(val) + } +} + +impl From> for ResourceKind { + fn from(val: Handle) -> Self { + Self::Texture(val) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum EdgeKind { + /// Dependency is sampled and requires the whole attachment to be ready. + Sampled, + /// Dependency is used as input attachment and can use dependency by region. + Input, + /// The attachment is loaded and written to. Depend on earlier nodes + Attachment, + Buffer, +} + +impl Default for EdgeKind { + fn default() -> Self { + Self::Sampled + } +} + +struct FrameData { + context: SharedVulkanContext, + fence: Fence, + commandpool: CommandPool, + commandbuffer: CommandBuffer, + wait_semaphore: Semaphore, + signal_semaphore: Semaphore, +} + +impl FrameData { + pub fn new(context: SharedVulkanContext) -> super::error::Result { + let commandpool = CommandPool::new( + context.device().clone(), + CommandPoolCreateInfo { + flags: CommandPoolCreateFlags { + ..Default::default() + }, + queue_family_index: context.queue_families().graphics().unwrap(), + ..Default::default() + }, + )?; + + let commandbuffer = commandpool.allocate_one()?; + let fence = fence::create(context.device(), true)?; + + let wait_semaphore = semaphore::create(context.device())?; + let signal_semaphore = semaphore::create(context.device())?; + + Ok(Self { + context, + fence, + commandpool, + commandbuffer, + wait_semaphore, + signal_semaphore, + }) + } +} + +struct EdgeConstructor<'a, I> { + nodes: &'a Vec>, + dst: NodeIndex, + reads: I, + kind: EdgeKind, +} + +impl<'a, I: Iterator>> Iterator for EdgeConstructor<'a, I> { + type Item = super::error::Result; + + fn next(&mut self) -> Option { + self.reads + .next() + // Find the corresponding write attachment + .map(move |read| { + self.nodes + .iter() + .enumerate() + .take_while(|(src, _)| *src != self.dst) + .filter_map(|(src, src_node)| { + // Found color attachment output + if let Some(write) = src_node + .color_attachments() + .iter() + .find(|w| w.resource == read) + { + Some(Edge { + src, + dst: self.dst, + resource: read.into(), + layout: write.final_layout, + write_stage: vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, + read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER, + write_access: vk::AccessFlags::COLOR_ATTACHMENT_WRITE, + read_access: vk::AccessFlags::SHADER_READ, + kind: self.kind, + }) + // Found transfer attachment output + } else if let Some(_) = + src_node.output_attachments().iter().find(|d| **d == read) + { + Some(Edge { + src, + dst: self.dst, + resource: read.into(), + layout: ImageLayout::TRANSFER_DST_OPTIMAL, + write_stage: vk::PipelineStageFlags::TRANSFER, + read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER, + write_access: vk::AccessFlags::TRANSFER_WRITE, + read_access: vk::AccessFlags::SHADER_READ, + kind: self.kind, + }) + } else if let Some(write) = src_node + .depth_attachment() + .as_ref() + .filter(|d| d.resource == read) + { + Some(Edge { + src, + dst: self.dst, + resource: read.into(), + layout: write.final_layout, + // Write stage is between + write_stage: vk::PipelineStageFlags::LATE_FRAGMENT_TESTS, + read_stage: vk::PipelineStageFlags::FRAGMENT_SHADER, + write_access: vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, + read_access: vk::AccessFlags::SHADER_READ, + kind: self.kind, + }) + } else { + None + } + }) + .last() + .ok_or(Error::MissingWrite( + self.dst, + self.nodes[self.dst].debug_name(), + read.into(), + )) + }) + } +} + +struct BufferEdgeConstructor<'a, I> { + nodes: &'a Vec>, + dst: NodeIndex, + reads: I, + kind: EdgeKind, +} + +impl<'a, I: Iterator> Iterator for BufferEdgeConstructor<'a, I> { + type Item = crate::Result; + + fn next(&mut self) -> Option { + self.reads + .next() + // Find the corresponding write attachment + .map(move |read| { + self.nodes + .iter() + .enumerate() + .take_while(|(src, _)| *src != self.dst) + .filter_map(|(src, src_node)| { + // Found color attachment output + if let Some(_) = src_node.buffer_writes().iter().find(|w| **w == read) { + Some(Edge { + src, + dst: self.dst, + resource: read.into(), + layout: Default::default(), + write_stage: vk::PipelineStageFlags::TRANSFER, + read_stage: vk::PipelineStageFlags::VERTEX_SHADER, + write_access: vk::AccessFlags::COLOR_ATTACHMENT_WRITE, + read_access: vk::AccessFlags::SHADER_READ, + kind: self.kind, + }) + } else { + None + } + }) + .last() + .ok_or(Error::MissingWrite( + self.dst, + self.nodes[self.dst].debug_name(), + read.into(), + )) + }) + } +} diff --git a/modules/khors-graphics/src/frag.glsl b/modules/khors-graphics/src/temp/frag.glsl similarity index 100% rename from modules/khors-graphics/src/frag.glsl rename to modules/khors-graphics/src/temp/frag.glsl diff --git a/modules/khors-graphics/src/temp/mod.rs b/modules/khors-graphics/src/temp/mod.rs new file mode 100644 index 0000000..65880be --- /dev/null +++ b/modules/khors-graphics/src/temp/mod.rs @@ -0,0 +1 @@ +pub mod model; diff --git a/modules/khors-graphics/src/model.rs b/modules/khors-graphics/src/temp/model.rs similarity index 99% rename from modules/khors-graphics/src/model.rs rename to modules/khors-graphics/src/temp/model.rs index 1e25a67..84ed6d9 100644 --- a/modules/khors-graphics/src/model.rs +++ b/modules/khors-graphics/src/temp/model.rs @@ -1,4 +1,4 @@ -use super::vulkan::vertex::{Position, Normal}; +use crate::vulkan::vertex::{Position, Normal}; pub const POSITIONS: [Position; 531] = [ Position { diff --git a/modules/khors-graphics/src/vert.glsl b/modules/khors-graphics/src/temp/vert.glsl similarity index 100% rename from modules/khors-graphics/src/vert.glsl rename to modules/khors-graphics/src/temp/vert.glsl diff --git a/modules/khors-graphics/src/vulkan/framebuffer.rs b/modules/khors-graphics/src/vulkan/framebuffer.rs new file mode 100644 index 0000000..7b29a21 --- /dev/null +++ b/modules/khors-graphics/src/vulkan/framebuffer.rs @@ -0,0 +1,14 @@ +use std::sync::Arc; + +use vulkano::{image::view::ImageView, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass}}; + +pub fn make_framebuffer(render_pass: Arc, attachments: Vec>) -> Result, vulkano::Validated> { + let extent = attachments[0].image().extent(); + + Framebuffer::new(render_pass.clone(), FramebufferCreateInfo { + attachments, + extent: [extent[0], extent[1]], + layers: 1, + ..Default::default() + }) +} diff --git a/modules/khors-graphics/src/vulkan/mod.rs b/modules/khors-graphics/src/vulkan/mod.rs index 9b936a4..a874aac 100644 --- a/modules/khors-graphics/src/vulkan/mod.rs +++ b/modules/khors-graphics/src/vulkan/mod.rs @@ -1,2 +1,4 @@ -// pub mod pipeline; +pub mod pipeline; +pub mod framebuffer; pub mod vertex; +pub mod renderpass; diff --git a/modules/khors-graphics/src/vulkan/pipeline.rs b/modules/khors-graphics/src/vulkan/pipeline.rs index eae09cd..ab1ffbd 100644 --- a/modules/khors-graphics/src/vulkan/pipeline.rs +++ b/modules/khors-graphics/src/vulkan/pipeline.rs @@ -17,14 +17,14 @@ use vulkano::{ GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, }, render_pass::{RenderPass, Subpass}, - shader::ShaderModule, + shader::EntryPoint, }; use super::vertex::{Normal, Position}; pub struct PipelineInfo { - pub vs: Arc, - pub fs: Arc, + pub vs: EntryPoint, + pub fs: EntryPoint, } pub fn make_pipeline( @@ -32,15 +32,12 @@ pub fn make_pipeline( pipeline_info: &PipelineInfo, pass_info: &PassInfo, ) -> Arc { - let vs = pipeline_info.vs.entry_point("main").unwrap(); - let fs = pipeline_info.fs.entry_point("main").unwrap(); - let vertex_input_state = [Position::per_vertex(), Normal::per_vertex()] - .definition(&vs) + .definition(&pipeline_info.vs) .unwrap(); let stages = [ - PipelineShaderStageCreateInfo::new(vs), - PipelineShaderStageCreateInfo::new(fs), + PipelineShaderStageCreateInfo::new(pipeline_info.vs.clone()), + PipelineShaderStageCreateInfo::new(pipeline_info.fs.clone()), ]; let layout = PipelineLayout::new( device.clone(), diff --git a/modules/khors-graphics/src/vulkan/renderpass.rs b/modules/khors-graphics/src/vulkan/renderpass.rs new file mode 100644 index 0000000..5b272c4 --- /dev/null +++ b/modules/khors-graphics/src/vulkan/renderpass.rs @@ -0,0 +1,116 @@ +use std::sync::Arc; + +use vulkano::{device::Device, format::Format, render_pass::{AttachmentDescription, AttachmentReference, RenderPass as VkRenderPass, RenderPassCreateFlags, RenderPassCreateInfo, SubpassDependency, SubpassDescription, SubpassDescriptionFlags}}; +use vulkano_util::renderer::VulkanoWindowRenderer; + +#[derive(Default, Debug)] +pub struct SubpassInfo { + pub color_attachments: Vec>, + pub color_resolve_attachments: Vec>, + pub input_attachments: Vec>, + pub depth_stencil_attachment: Option, +} + +impl From for SubpassDescription { + fn from(value: SubpassInfo) -> Self { + SubpassDescription { + flags: SubpassDescriptionFlags::default(), + color_attachments: value.color_attachments, + color_resolve_attachments: value.color_resolve_attachments, + input_attachments: value.input_attachments, + depth_stencil_attachment: value.depth_stencil_attachment, + ..Default::default() + } + } +} + +fn make_subpass_description(subpass_info: Arc) -> SubpassDescription { + SubpassDescription { + flags: SubpassDescriptionFlags::default(), + color_attachments: subpass_info.color_attachments.clone(), + color_resolve_attachments: subpass_info.color_resolve_attachments.clone(), + input_attachments: subpass_info.input_attachments.clone(), + depth_stencil_attachment: subpass_info.depth_stencil_attachment.clone(), + ..Default::default() + } +} + +#[derive(Default)] +pub struct RenderPassInfo { + pub attachments: Vec, + pub subpasses: Vec>, + pub dependencies: Vec, +} + +pub struct RenderPass { + device: Arc, + render_pass: Arc +} + +impl RenderPass { + pub fn new(device: Arc, info: Arc) -> Self { + let vk_subpasses = info.subpasses.iter().map(|subpass| make_subpass_description(subpass.clone())).collect::>(); + let render_pass_create_info = RenderPassCreateInfo { + attachments: info.attachments.clone(), + dependencies: info.dependencies.clone(), + subpasses: vk_subpasses, + ..Default::default() + }; + + let render_pass = VkRenderPass::new(device.clone(), render_pass_create_info).unwrap(); + + Self { + device: device.clone(), + render_pass + } + } + + pub fn renderpass(&self) -> Arc { + self.render_pass.clone() + } +} + +pub fn make_render_pass(device: Arc, renderer: &mut VulkanoWindowRenderer) -> Arc { + let color_attachment = AttachmentDescription { + format: renderer.swapchain_format(), + samples: vulkano::image::SampleCount::Sample1, + load_op: vulkano::render_pass::AttachmentLoadOp::Clear, + store_op: vulkano::render_pass::AttachmentStoreOp::Store, + initial_layout: vulkano::image::ImageLayout::ColorAttachmentOptimal, + final_layout: vulkano::image::ImageLayout::ColorAttachmentOptimal, + ..Default::default() + }; + let depth_stencil_attachment = AttachmentDescription { + format: Format::D16_UNORM, + samples: vulkano::image::SampleCount::Sample1, + load_op: vulkano::render_pass::AttachmentLoadOp::Clear, + store_op: vulkano::render_pass::AttachmentStoreOp::DontCare, + initial_layout: vulkano::image::ImageLayout::DepthStencilAttachmentOptimal, + final_layout: vulkano::image::ImageLayout::DepthStencilAttachmentOptimal, + ..Default::default() + }; + + let color_attachment_reference = AttachmentReference { + attachment: 0, + layout: color_attachment.initial_layout, + ..Default::default() + }; + + let depth_stencil_attachment_reference = AttachmentReference { + attachment: 1, + layout: depth_stencil_attachment.initial_layout, + ..Default::default() + }; + + let subpass_description = SubpassDescription { + color_attachments: vec![Some(color_attachment_reference)], + depth_stencil_attachment: Some(depth_stencil_attachment_reference), + ..Default::default() + }; + + VkRenderPass::new(device.clone(), RenderPassCreateInfo { + attachments: vec![color_attachment, depth_stencil_attachment], + subpasses: vec![subpass_description], + ..Default::default() + }).unwrap() +} diff --git a/modules/khors-window/src/window.rs b/modules/khors-window/src/window.rs new file mode 100644 index 0000000..1082d23 --- /dev/null +++ b/modules/khors-window/src/window.rs @@ -0,0 +1,13 @@ +use winit::event_loop::EventLoopBuilder; + +pub struct Window { + handle: winit::window::Window, + event_loop: winit::event_loop::EventLoop<()> +} + +impl Window { + pub fn new() -> Self { + let event_loop = EventLoopBuilder::new().build(); + + } +} diff --git a/vendor/egui-snarl/Cargo.toml b/vendor/egui-snarl/Cargo.toml new file mode 100644 index 0000000..d6fe9d4 --- /dev/null +++ b/vendor/egui-snarl/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "egui-snarl" +version = "0.2.1" +edition = "2021" +description = "Node-graphs for egui" +license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/egui-snarl" +keywords = ["egui", "node", "graph", "ui", "node-graph"] +categories = ["gui", "visualization"] + +[features] +serde = ["dep:serde", "egui/serde", "slab/serde"] + +[dependencies] +egui = { version = "0.27" } +slab = { version = "0.4" } +serde = { version = "1.0", features = ["derive"], optional = true } +tiny-fn = { version = "0.1.6" } + +egui-probe = { version = "0.2", features = ["derive"], optional = true } + +[dev-dependencies] +eframe = { version = "0.26", features = ["serde", "persistence"] } +egui_extras = { version = "0.26", features = ["all_loaders"] } +syn = { version = "2.0", features = ["extra-traits"] } +serde_json = { version = "1.0" } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +wasm-bindgen-futures = "0.4" diff --git a/vendor/egui-snarl/src/lib.rs b/vendor/egui-snarl/src/lib.rs new file mode 100644 index 0000000..8eb36cc --- /dev/null +++ b/vendor/egui-snarl/src/lib.rs @@ -0,0 +1,696 @@ +//! +//! # egui-snarl +//! +//! Provides a node-graph container for egui. +//! +//! + +#![deny(missing_docs)] +#![deny(clippy::correctness, clippy::complexity, clippy::perf, clippy::style)] +// #![warn(clippy::pedantic)] +#![allow(clippy::inline_always)] + +pub mod ui; + +use std::ops::{Index, IndexMut}; + +use egui::{ahash::HashSet, Pos2}; +use slab::Slab; + +impl Default for Snarl { + fn default() -> Self { + Snarl::new() + } +} + +/// Node identifier. +/// +/// This is newtype wrapper around [`usize`] that implements +/// necessary traits, but omits arithmetic operations. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(transparent) +)] +pub struct NodeId(pub usize); + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct Node { + /// Node generic value. + value: T, + + /// Position of the top-left corner of the node. + /// This does not include frame margin. + pos: egui::Pos2, + + /// Flag indicating that the node is open - not collapsed. + open: bool, +} + +/// Output pin identifier. +/// Cosists of node id and pin index. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct OutPinId { + /// Node id. + pub node: NodeId, + + /// Output pin index. + pub output: usize, +} + +/// Input pin identifier. Cosists of node id and pin index. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct InPinId { + /// Node id. + pub node: NodeId, + + /// Input pin index. + pub input: usize, +} + +/// Connection between two nodes. +/// +/// Nodes may support multiple connections to the same input or output. +/// But duplicate connections between same input and the same output are not allowed. +/// Attempt to insert existing connection will be ignored. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +struct Wire { + out_pin: OutPinId, + in_pin: InPinId, +} + +#[derive(Clone, Debug)] +struct Wires { + wires: HashSet, +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Wires { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeSeq; + + let mut seq = serializer.serialize_seq(Some(self.wires.len()))?; + for wire in &self.wires { + seq.serialize_element(&wire)?; + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Wires { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = HashSet; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence of wires") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut wires = HashSet::with_hasher(egui::ahash::RandomState::new()); + while let Some(wire) = seq.next_element()? { + wires.insert(wire); + } + Ok(wires) + } + } + + let wires = deserializer.deserialize_seq(Visitor)?; + Ok(Wires { wires }) + } +} + +impl Wires { + fn new() -> Self { + Wires { + wires: HashSet::with_hasher(egui::ahash::RandomState::new()), + } + } + + fn insert(&mut self, wire: Wire) -> bool { + self.wires.insert(wire) + } + + fn remove(&mut self, wire: &Wire) -> bool { + self.wires.remove(wire) + } + + fn drop_node(&mut self, node: NodeId) -> usize { + let count = self.wires.len(); + self.wires + .retain(|wire| wire.out_pin.node != node && wire.in_pin.node != node); + count - self.wires.len() + } + + fn drop_inputs(&mut self, pin: InPinId) -> usize { + let count = self.wires.len(); + self.wires.retain(|wire| wire.in_pin != pin); + count - self.wires.len() + } + + fn drop_outputs(&mut self, pin: OutPinId) -> usize { + let count = self.wires.len(); + self.wires.retain(|wire| wire.out_pin != pin); + count - self.wires.len() + } + + fn wired_inputs(&self, out_pin: OutPinId) -> impl Iterator + '_ { + self.wires + .iter() + .filter(move |wire| wire.out_pin == out_pin) + .map(|wire| (wire.in_pin)) + } + + fn wired_outputs(&self, in_pin: InPinId) -> impl Iterator + '_ { + self.wires + .iter() + .filter(move |wire| wire.in_pin == in_pin) + .map(|wire| (wire.out_pin)) + } + + fn iter(&self) -> impl Iterator + '_ { + self.wires.iter().copied() + } +} + +/// Snarl is generic node-graph container. +/// +/// It holds graph state - positioned nodes and wires between their pins. +/// It can be rendered using [`Snarl::show`]. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Snarl { + // #[cfg_attr(feature = "serde", serde(with = "serde_nodes"))] + nodes: Slab>, + draw_order: Vec, + wires: Wires, +} + +impl Snarl { + /// Create a new empty Snarl. + /// + /// # Examples + /// + /// ``` + /// # use egui_snarl::Snarl; + /// let snarl = Snarl::<()>::new(); + /// ``` + #[must_use] + pub fn new() -> Self { + Snarl { + nodes: Slab::new(), + draw_order: Vec::new(), + wires: Wires::new(), + } + } + + /// Adds a node to the Snarl. + /// Returns the index of the node. + /// + /// # Examples + /// + /// ``` + /// # use egui_snarl::Snarl; + /// let mut snarl = Snarl::<()>::new(); + /// snarl.insert_node(egui::pos2(0.0, 0.0), ()); + /// ``` + pub fn insert_node(&mut self, pos: egui::Pos2, node: T) -> NodeId { + let idx = self.nodes.insert(Node { + value: node, + pos, + open: true, + }); + let id = NodeId(idx); + self.draw_order.push(id); + id + } + + /// Adds a node to the Snarl in collapsed state. + /// Returns the index of the node. + /// + /// # Examples + /// + /// ``` + /// # use egui_snarl::Snarl; + /// let mut snarl = Snarl::<()>::new(); + /// snarl.insert_node_collapsed(egui::pos2(0.0, 0.0), ()); + /// ``` + pub fn insert_node_collapsed(&mut self, pos: egui::Pos2, node: T) -> NodeId { + let idx = self.nodes.insert(Node { + value: node, + pos, + open: false, + }); + let id = NodeId(idx); + self.draw_order.push(id); + id + } + + /// Opens or collapses a node. + /// + /// # Panics + /// + /// Panics if the node does not exist. + #[track_caller] + pub fn open_node(&mut self, node: NodeId, open: bool) { + self.nodes[node.0].open = open; + } + + /// Removes a node from the Snarl. + /// Returns the node if it was removed. + /// + /// # Panics + /// + /// Panics if the node does not exist. + /// + /// # Examples + /// + /// ``` + /// # use egui_snarl::Snarl; + /// let mut snarl = Snarl::<()>::new(); + /// let node = snarl.insert_node(egui::pos2(0.0, 0.0), ()); + /// snarl.remove_node(node); + /// ``` + #[track_caller] + pub fn remove_node(&mut self, idx: NodeId) -> T { + let value = self.nodes.remove(idx.0).value; + self.wires.drop_node(idx); + let order = self.draw_order.iter().position(|&i| i == idx).unwrap(); + self.draw_order.remove(order); + value + } + + /// Connects two nodes. + /// Returns true if the connection was successful. + /// Returns false if the connection already exists. + /// + /// # Panics + /// + /// Panics if either node does not exist. + #[track_caller] + pub fn connect(&mut self, from: OutPinId, to: InPinId) -> bool { + assert!(self.nodes.contains(from.node.0)); + assert!(self.nodes.contains(to.node.0)); + + let wire = Wire { + out_pin: from, + in_pin: to, + }; + self.wires.insert(wire) + } + + /// Disconnects two nodes. + /// Returns true if the connection was removed. + /// + /// # Panics + /// + /// Panics if either node does not exist. + #[track_caller] + pub fn disconnect(&mut self, from: OutPinId, to: InPinId) -> bool { + assert!(self.nodes.contains(from.node.0)); + assert!(self.nodes.contains(to.node.0)); + + let wire = Wire { + out_pin: from, + in_pin: to, + }; + + self.wires.remove(&wire) + } + + /// Removes all connections to the node's pin. + /// + /// Returns number of removed connections. + /// + /// # Panics + /// + /// Panics if the node does not exist. + #[track_caller] + pub fn drop_inputs(&mut self, pin: InPinId) -> usize { + assert!(self.nodes.contains(pin.node.0)); + self.wires.drop_inputs(pin) + } + + /// Removes all connections from the node's pin. + /// Returns number of removed connections. + /// + /// # Panics + /// + /// Panics if the node does not exist. + #[track_caller] + pub fn drop_outputs(&mut self, pin: OutPinId) -> usize { + assert!(self.nodes.contains(pin.node.0)); + self.wires.drop_outputs(pin) + } + + /// Returns reference to the node. + #[must_use] + pub fn get_node(&self, idx: NodeId) -> Option<&T> { + match self.nodes.get(idx.0) { + Some(node) => Some(&node.value), + None => None, + } + } + + /// Returns mutable reference to the node. + pub fn get_node_mut(&mut self, idx: NodeId) -> Option<&mut T> { + match self.nodes.get_mut(idx.0) { + Some(node) => Some(&mut node.value), + None => None, + } + } + + /// Iterates over shared references to each node. + pub fn nodes(&self) -> NodesIter<'_, T> { + NodesIter { + nodes: self.nodes.iter(), + } + } + + /// Iterates over mutable references to each node. + pub fn nodes_mut(&mut self) -> NodesIterMut<'_, T> { + NodesIterMut { + nodes: self.nodes.iter_mut(), + } + } + + /// Iterates over shared references to each node and its position. + pub fn nodes_pos(&self) -> NodesPosIter<'_, T> { + NodesPosIter { + nodes: self.nodes.iter(), + } + } + + /// Iterates over mutable references to each node and its position. + pub fn nodes_pos_mut(&mut self) -> NodesPosIterMut<'_, T> { + NodesPosIterMut { + nodes: self.nodes.iter_mut(), + } + } + + /// Iterates over shared references to each node and its identifier. + pub fn node_ids(&self) -> NodesIdsIter<'_, T> { + NodesIdsIter { + nodes: self.nodes.iter(), + } + } + + /// Iterates over mutable references to each node and its identifier. + pub fn nodes_ids_mut(&mut self) -> NodesIdsIterMut<'_, T> { + NodesIdsIterMut { + nodes: self.nodes.iter_mut(), + } + } + + /// Iterates over shared references to each node, its position and its identifier. + pub fn nodes_pos_ids(&self) -> NodesPosIdsIter<'_, T> { + NodesPosIdsIter { + nodes: self.nodes.iter(), + } + } + + /// Iterates over mutable references to each node, its position and its identifier. + pub fn nodes_pos_ids_mut(&mut self) -> NodesPosIdsIterMut<'_, T> { + NodesPosIdsIterMut { + nodes: self.nodes.iter_mut(), + } + } + + /// Returns input pin of the node. + #[must_use] + pub fn in_pin(&self, pin: InPinId) -> InPin { + InPin::new(self, pin) + } + + /// Returns output pin of the node. + #[must_use] + pub fn out_pin(&self, pin: OutPinId) -> OutPin { + OutPin::new(self, pin) + } +} + +impl Index for Snarl { + type Output = T; + + #[inline] + #[track_caller] + fn index(&self, idx: NodeId) -> &Self::Output { + &self.nodes[idx.0].value + } +} + +impl IndexMut for Snarl { + #[inline] + #[track_caller] + fn index_mut(&mut self, idx: NodeId) -> &mut Self::Output { + &mut self.nodes[idx.0].value + } +} + +/// Iterator over shared references to nodes. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesIter<'a, T> { + nodes: slab::Iter<'a, Node>, +} + +impl<'a, T> Iterator for NodesIter<'a, T> { + type Item = &'a T; + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<&'a T> { + let (_, node) = self.nodes.next()?; + Some(&node.value) + } + + fn nth(&mut self, n: usize) -> Option<&'a T> { + let (_, node) = self.nodes.nth(n)?; + Some(&node.value) + } +} + +/// Iterator over mutable references to nodes. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesIterMut<'a, T> { + nodes: slab::IterMut<'a, Node>, +} + +impl<'a, T> Iterator for NodesIterMut<'a, T> { + type Item = &'a mut T; + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<&'a mut T> { + let (_, node) = self.nodes.next()?; + Some(&mut node.value) + } + + fn nth(&mut self, n: usize) -> Option<&'a mut T> { + let (_, node) = self.nodes.nth(n)?; + Some(&mut node.value) + } +} + +/// Iterator over shared references to nodes and their positions. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesPosIter<'a, T> { + nodes: slab::Iter<'a, Node>, +} + +impl<'a, T> Iterator for NodesPosIter<'a, T> { + type Item = (Pos2, &'a T); + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<(Pos2, &'a T)> { + let (_, node) = self.nodes.next()?; + Some((node.pos, &node.value)) + } + + fn nth(&mut self, n: usize) -> Option<(Pos2, &'a T)> { + let (_, node) = self.nodes.nth(n)?; + Some((node.pos, &node.value)) + } +} + +/// Iterator over mutable references to nodes and their positions. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesPosIterMut<'a, T> { + nodes: slab::IterMut<'a, Node>, +} + +impl<'a, T> Iterator for NodesPosIterMut<'a, T> { + type Item = (Pos2, &'a mut T); + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<(Pos2, &'a mut T)> { + let (_, node) = self.nodes.next()?; + Some((node.pos, &mut node.value)) + } + + fn nth(&mut self, n: usize) -> Option<(Pos2, &'a mut T)> { + let (_, node) = self.nodes.nth(n)?; + Some((node.pos, &mut node.value)) + } +} + +/// Iterator over shared references to nodes and their identifiers. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesIdsIter<'a, T> { + nodes: slab::Iter<'a, Node>, +} + +impl<'a, T> Iterator for NodesIdsIter<'a, T> { + type Item = (NodeId, &'a T); + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<(NodeId, &'a T)> { + let (idx, node) = self.nodes.next()?; + Some((NodeId(idx), &node.value)) + } + + fn nth(&mut self, n: usize) -> Option<(NodeId, &'a T)> { + let (idx, node) = self.nodes.nth(n)?; + Some((NodeId(idx), &node.value)) + } +} + +/// Iterator over mutable references to nodes and their identifiers. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesIdsIterMut<'a, T> { + nodes: slab::IterMut<'a, Node>, +} + +impl<'a, T> Iterator for NodesIdsIterMut<'a, T> { + type Item = (NodeId, &'a mut T); + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<(NodeId, &'a mut T)> { + let (idx, node) = self.nodes.next()?; + Some((NodeId(idx), &mut node.value)) + } + + fn nth(&mut self, n: usize) -> Option<(NodeId, &'a mut T)> { + let (idx, node) = self.nodes.nth(n)?; + Some((NodeId(idx), &mut node.value)) + } +} + +/// Iterator over shared references to nodes, their positions and their identifiers. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesPosIdsIter<'a, T> { + nodes: slab::Iter<'a, Node>, +} + +impl<'a, T> Iterator for NodesPosIdsIter<'a, T> { + type Item = (NodeId, Pos2, &'a T); + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<(NodeId, Pos2, &'a T)> { + let (idx, node) = self.nodes.next()?; + Some((NodeId(idx), node.pos, &node.value)) + } + + fn nth(&mut self, n: usize) -> Option<(NodeId, Pos2, &'a T)> { + let (idx, node) = self.nodes.nth(n)?; + Some((NodeId(idx), node.pos, &node.value)) + } +} + +/// Iterator over mutable references to nodes, their positions and their identifiers. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct NodesPosIdsIterMut<'a, T> { + nodes: slab::IterMut<'a, Node>, +} + +impl<'a, T> Iterator for NodesPosIdsIterMut<'a, T> { + type Item = (NodeId, Pos2, &'a mut T); + + fn size_hint(&self) -> (usize, Option) { + self.nodes.size_hint() + } + + fn next(&mut self) -> Option<(NodeId, Pos2, &'a mut T)> { + let (idx, node) = self.nodes.next()?; + Some((NodeId(idx), node.pos, &mut node.value)) + } + + fn nth(&mut self, n: usize) -> Option<(NodeId, Pos2, &'a mut T)> { + let (idx, node) = self.nodes.nth(n)?; + Some((NodeId(idx), node.pos, &mut node.value)) + } +} + +/// Node and its output pin. +#[derive(Clone, Debug)] +pub struct OutPin { + /// Output pin identifier. + pub id: OutPinId, + + /// List of input pins connected to this output pin. + pub remotes: Vec, +} + +/// Node and its output pin. +#[derive(Clone, Debug)] +pub struct InPin { + /// Input pin identifier. + pub id: InPinId, + + /// List of output pins connected to this input pin. + pub remotes: Vec, +} + +impl OutPin { + fn new(snarl: &Snarl, pin: OutPinId) -> Self { + OutPin { + id: pin, + remotes: snarl.wires.wired_inputs(pin).collect(), + } + } +} + +impl InPin { + fn new(snarl: &Snarl, pin: InPinId) -> Self { + InPin { + id: pin, + remotes: snarl.wires.wired_outputs(pin).collect(), + } + } +} diff --git a/vendor/egui-snarl/src/ui.rs b/vendor/egui-snarl/src/ui.rs new file mode 100644 index 0000000..f3eab1a --- /dev/null +++ b/vendor/egui-snarl/src/ui.rs @@ -0,0 +1,1089 @@ +//! This module provides functionality for showing [`Snarl`] graph in [`Ui`]. + +use std::{collections::HashMap, hash::Hash}; + +use egui::{ + collapsing_header::paint_default_icon, epaint::Shadow, pos2, vec2, Align, Color32, Frame, Id, + Layout, Modifiers, PointerButton, Pos2, Rect, Sense, Shape, Stroke, Style, Ui, Vec2, +}; + +use crate::{InPin, InPinId, Node, NodeId, OutPin, OutPinId, Snarl}; + +mod background_pattern; +mod pin; +mod state; +mod viewer; +mod wire; +mod zoom; + +use self::{ + pin::{draw_pin, AnyPin}, + state::{NewWires, NodeState, SnarlState}, + wire::{draw_wire, hit_wire, mix_colors}, + zoom::Zoom, +}; + +pub use self::{ + background_pattern::{BackgroundPattern, CustomBackground, Grid, Viewport}, + pin::{AnyPins, CustomPinShape, PinInfo, PinShape}, + viewer::SnarlViewer, + wire::WireLayer, +}; + +/// Style for rendering Snarl. +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))] +pub struct SnarlStyle { + /// Size of pins. + #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..))] + pub pin_size: Option, + + /// Width of wires. + #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..))] + pub wire_width: Option, + + /// Size of wire frame which controls curvature of wires. + pub wire_frame_size: Option, + + /// Whether to downscale wire frame when nodes are close. + pub downscale_wire_frame: bool, + + /// Weather to upscale wire frame when nodes are close. + pub upscale_wire_frame: bool, + + /// Layer where wires are rendered. + pub wire_layer: WireLayer, + + /// Additional blank space for dragging node by header. + pub header_drag_space: Option, + + /// Whether nodes can be collapsed. + /// If true, headers will have collapsing button. + /// When collapsed, node will not show its pins, body and footer. + pub collapsible: bool, + + /// Background fill color. + /// Defaults to `ui.visuals().widgets.noninteractive.bg_fill`. + pub bg_fill: Option, + + /// Background pattern. + /// Defaults to [`BackgroundPattern::Grid`]. + pub bg_pattern: BackgroundPattern, + + /// Stroke for background pattern. + /// Defaults to `ui.visuals().widgets.noninteractive.bg_stroke`. + pub background_pattern_stroke: Option, + + /// Minimum scale that can be set. + #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..=1.0))] + pub min_scale: f32, + + /// Maximum scale that can be set. + #[cfg_attr(feature = "egui-probe", egui_probe(range = 1.0..))] + pub max_scale: f32, + + /// Scale velocity when scaling with mouse wheel. + #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..))] + pub scale_velocity: f32, + + /// Frame used to draw nodes. + /// Defaults to [`Frame::window`] constructed from current ui's style. + #[cfg_attr(feature = "serde", serde(with = "serde_frame_option"))] + pub node_frame: Option, + + /// Frame used to draw node headers. + /// Defaults to [`node_frame`] without shadow and transparent fill. + /// + /// If set, it should not have shadow and fill should be either opaque of fully transparent + /// unless layering of header fill color with node fill color is desired. + #[cfg_attr(feature = "serde", serde(with = "serde_frame_option"))] + pub header_frame: Option, + + #[doc(hidden)] + #[cfg_attr(feature = "egui-probe", egui_probe(skip))] + /// Do not access other than with .., here to emulate `#[non_exhaustive(pub)]` + pub _non_exhaustive: (), +} + +#[cfg(feature = "serde")] +mod serde_frame_option { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Serialize, Deserialize)] + pub struct Frame { + pub inner_margin: egui::Margin, + pub outer_margin: egui::Margin, + pub rounding: egui::Rounding, + pub shadow: egui::epaint::Shadow, + pub fill: egui::Color32, + pub stroke: egui::Stroke, + } + + pub fn serialize(frame: &Option, serializer: S) -> Result + where + S: Serializer, + { + match frame { + Some(frame) => Frame { + inner_margin: frame.inner_margin, + outer_margin: frame.outer_margin, + rounding: frame.rounding, + shadow: frame.shadow, + fill: frame.fill, + stroke: frame.stroke, + } + .serialize(serializer), + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let frame_opt = Option::::deserialize(deserializer)?; + Ok(frame_opt.map(|frame| egui::Frame { + inner_margin: frame.inner_margin, + outer_margin: frame.outer_margin, + rounding: frame.rounding, + shadow: frame.shadow, + fill: frame.fill, + stroke: frame.stroke, + })) + } +} + +impl SnarlStyle { + /// Creates new [`SnarlStyle`] filled with default values. + #[must_use] + pub const fn new() -> Self { + SnarlStyle { + pin_size: None, + wire_width: None, + wire_frame_size: None, + downscale_wire_frame: false, + upscale_wire_frame: true, + wire_layer: WireLayer::BehindNodes, + header_drag_space: None, + collapsible: true, + + bg_fill: None, + bg_pattern: background_pattern::BackgroundPattern::new(), + background_pattern_stroke: None, + + min_scale: 0.1, + max_scale: 2.0, + scale_velocity: 0.005, + node_frame: None, + header_frame: None, + + _non_exhaustive: (), + } + } +} + +impl Default for SnarlStyle { + #[inline] + fn default() -> Self { + Self::new() + } +} + +struct Input { + hover_pos: Option, + scroll_delta: f32, + // primary_pressed: bool, + secondary_pressed: bool, + modifiers: Modifiers, +} + +struct DrawNodeResponse { + node_moved: Option<(NodeId, Vec2)>, + node_to_top: Option, + drag_stopped: bool, + pin_hovered: Option, +} + +impl Snarl { + fn draw_background(style: &SnarlStyle, snarl_state: &SnarlState, viewport: &Rect, ui: &mut Ui) { + let viewport = Viewport { + rect: *viewport, + scale: snarl_state.scale(), + offset: snarl_state.offset(), + }; + + style.bg_pattern.draw(style, &viewport, ui); + } + + /// Render [`Snarl`] using given viewer and style into the [`Ui`]. + + pub fn show(&mut self, viewer: &mut V, style: &SnarlStyle, id_source: impl Hash, ui: &mut Ui) + where + V: SnarlViewer, + { + #![allow(clippy::too_many_lines)] + + let mut node_moved = None; + let mut node_to_top = None; + + let snarl_id = ui.make_persistent_id(id_source); + + // Draw background pattern. + let bg_fill = style + .bg_fill + .unwrap_or_else(|| ui.visuals().widgets.noninteractive.bg_fill); + + let bg_stroke = style + .background_pattern_stroke + .unwrap_or_else(|| ui.visuals().widgets.noninteractive.bg_stroke); + + let input = ui.ctx().input(|i| Input { + scroll_delta: i.raw_scroll_delta.y, + hover_pos: i.pointer.hover_pos(), + modifiers: i.modifiers, + // primary_pressed: i.pointer.primary_pressed(), + secondary_pressed: i.pointer.secondary_pressed(), + }); + + Frame::none() + .fill(bg_fill) + .stroke(bg_stroke) + .show(ui, |ui| { + let mut bg_r = ui.allocate_rect(ui.max_rect(), Sense::click_and_drag()); + let viewport = bg_r.rect; + ui.set_clip_rect(viewport); + + let pivot = input.hover_pos.unwrap_or_else(|| viewport.center()); + + let mut snarl_state = + SnarlState::load(ui.ctx(), snarl_id, pivot, viewport, self, style); + + let mut node_style: Style = (**ui.style()).clone(); + node_style.zoom(snarl_state.scale()); + + //Draw background + Self::draw_background(style, &snarl_state, &viewport, ui); + + let pin_size = style + .pin_size + .zoomed(snarl_state.scale()) + .unwrap_or(node_style.spacing.interact_size.y * 0.5); + + let wire_frame_size = style + .wire_frame_size + .zoomed(snarl_state.scale()) + .unwrap_or(pin_size * 5.0); + let wire_width = style + .wire_width + .zoomed(snarl_state.scale()) + .unwrap_or(pin_size * 0.2); + + let node_frame = style + .node_frame + .zoomed(snarl_state.scale()) + .unwrap_or_else(|| Frame::window(&node_style)); + + let header_frame = style + .header_frame + .zoomed(snarl_state.scale()) + .unwrap_or_else(|| node_frame.shadow(Shadow::NONE).fill(Color32::TRANSPARENT)); + + let wire_shape_idx = match style.wire_layer { + WireLayer::BehindNodes => Some(ui.painter().add(Shape::Noop)), + WireLayer::AboveNodes => None, + }; + + // Zooming + match input.hover_pos { + Some(hover_pos) if viewport.contains(hover_pos) => { + if input.scroll_delta != 0.0 { + let new_scale = (snarl_state.scale() + * (1.0 + input.scroll_delta * style.scale_velocity)) + .clamp(style.min_scale, style.max_scale); + + snarl_state.set_scale(new_scale); + } + } + _ => {} + } + + let mut input_info = HashMap::new(); + let mut output_info = HashMap::new(); + + let mut pin_hovered = None; + + let draw_order = self.draw_order.clone(); + let mut drag_released = false; + + for node_idx in draw_order { + // show_node(node_idx); + let response = self.draw_node( + ui, + node_idx, + viewer, + &mut snarl_state, + style, + snarl_id, + &node_style, + &node_frame, + &header_frame, + &mut input_info, + &input, + &mut output_info, + ); + if let Some(v) = response.node_to_top { + node_to_top = Some(v); + } + if let Some(v) = response.node_moved { + node_moved = Some(v); + } + if let Some(v) = response.pin_hovered { + pin_hovered = Some(v); + } + drag_released |= response.drag_stopped; + } + + let mut hovered_wire = None; + let mut hovered_wire_disconnect = false; + let mut wire_shapes = Vec::new(); + let mut wire_hit = false; + + for wire in self.wires.iter() { + let (from, color_from) = output_info[&wire.out_pin]; + let (to, color_to) = input_info[&wire.in_pin]; + + if !wire_hit + && !snarl_state.has_new_wires() + && bg_r.hovered() + && !bg_r.dragged() + { + // Try to find hovered wire + // If not draggin new wire + // And not hovering over item above. + + if let Some(hover_pos) = input.hover_pos { + wire_hit = hit_wire( + hover_pos, + wire_frame_size, + style.upscale_wire_frame, + style.downscale_wire_frame, + from, + to, + wire_width.max(1.5), + ); + + if wire_hit { + hovered_wire = Some(wire); + + //Remove hovered wire by second click + hovered_wire_disconnect |= + bg_r.clicked_by(PointerButton::Secondary); + + // Background is not hovered then. + bg_r.hovered = false; + bg_r.clicked = false;// [false; egui::NUM_POINTER_BUTTONS]; + // bg_r.double_clicked = false; // [false; egui::NUM_POINTER_BUTTONS]; + // bg_r.triple_clicked = false; // [false; egui::NUM_POINTER_BUTTONS]; + } + } + } + + let color = mix_colors(color_from, color_to); + + let mut draw_width = wire_width; + if hovered_wire == Some(wire) { + draw_width *= 1.5; + } + + draw_wire( + ui, + &mut wire_shapes, + wire_frame_size, + style.upscale_wire_frame, + style.downscale_wire_frame, + from, + to, + Stroke::new(draw_width, color), + ); + } + + //Remove hovered wire by second click + if hovered_wire_disconnect { + if let Some(wire) = hovered_wire { + let out_pin = OutPin::new(self, wire.out_pin); + let in_pin = InPin::new(self, wire.in_pin); + viewer.disconnect(&out_pin, &in_pin, self); + } + } + + if bg_r.dragged_by(PointerButton::Primary) { + snarl_state.pan(-bg_r.drag_delta()); + } + + // If right button is clicked while new wire is being dragged, cancel it. + // This is to provide way to 'not open' the link graph node menu, but just + // releasing the new wire to empty space. + // + // This uses `button_down` directly, instead of `clicked_by` to improve + // responsiveness of the cancel action. + if snarl_state.has_new_wires() + && ui.input(|x| x.pointer.button_down(PointerButton::Secondary)) + { + let _ = snarl_state.take_wires(); + // bg_r.clicked = [false; egui::NUM_POINTER_BUTTONS]; + } + + // Wire end position will be overrided when link graph menu is opened. + let mut wire_end_pos = input.hover_pos.unwrap_or_default(); + + if drag_released { + let new_wires = snarl_state.take_wires(); + if new_wires.is_some() { + ui.ctx().request_repaint(); + } + match (new_wires, pin_hovered) { + (Some(NewWires::In(in_pins)), Some(AnyPin::Out(out_pin))) => { + for in_pin in in_pins { + viewer.connect( + &OutPin::new(self, out_pin), + &InPin::new(self, in_pin), + self, + ); + } + } + (Some(NewWires::Out(out_pins)), Some(AnyPin::In(in_pin))) => { + for out_pin in out_pins { + viewer.connect( + &OutPin::new(self, out_pin), + &InPin::new(self, in_pin), + self, + ); + } + } + (Some(new_wires), None) if bg_r.hovered() => { + // A new pin is dropped without connecting it anywhere. This + // will open a pop-up window for creating a new node. + snarl_state.revert_take_wires(new_wires); + + // Force open context menu. + // bg_r.clicked[PointerButton::Secondary as usize] = true; + } + _ => {} + } + } + + // Open graph menu when right-clicking on empty space. + let mut is_menu_visible = false; + bg_r.context_menu(|ui| { + let graph_pos = snarl_state.screen_pos_to_graph(ui.cursor().min, viewport); + + is_menu_visible = true; + + if snarl_state.has_new_wires() { + if !snarl_state.is_link_menu_open() { + // Mark link menu is now visible. + snarl_state.open_link_menu(); + } + + // Let the wires be drawn on ui position + wire_end_pos = ui.cursor().min; + + let pins = match snarl_state.new_wires().unwrap() { + NewWires::In(x) => AnyPins::In(x), + NewWires::Out(x) => AnyPins::Out(x), + }; + + // The context menu is opened as *link* graph menu. + viewer.graph_menu_for_dropped_wire( + graph_pos, + ui, + snarl_state.scale(), + pins, + self, + ); + } else { + viewer.graph_menu(graph_pos, ui, snarl_state.scale(), self); + } + }); + + if !is_menu_visible && snarl_state.is_link_menu_open() { + // It seems that the context menu was closed. Remove new wires. + snarl_state.close_link_menu(); + } + + match snarl_state.new_wires() { + None => {} + Some(NewWires::In(pins)) => { + for pin in pins { + let from = wire_end_pos; + let (to, color) = input_info[pin]; + + draw_wire( + ui, + &mut wire_shapes, + wire_frame_size, + style.upscale_wire_frame, + style.downscale_wire_frame, + from, + to, + Stroke::new(wire_width, color), + ); + } + } + Some(NewWires::Out(pins)) => { + for pin in pins { + let (from, color) = output_info[pin]; + let to = wire_end_pos; + + draw_wire( + ui, + &mut wire_shapes, + wire_frame_size, + style.upscale_wire_frame, + style.downscale_wire_frame, + from, + to, + Stroke::new(wire_width, color), + ); + } + } + } + + match wire_shape_idx { + None => { + ui.painter().add(Shape::Vec(wire_shapes)); + } + Some(idx) => { + ui.painter().set(idx, Shape::Vec(wire_shapes)); + } + } + + ui.advance_cursor_after_rect(Rect::from_min_size(viewport.min, Vec2::ZERO)); + + snarl_state.store(ui.ctx()); + }); + + if let Some((node, delta)) = node_moved { + ui.ctx().request_repaint(); + let node = &mut self.nodes[node.0]; + node.pos += delta; + } + + if let Some(node_idx) = node_to_top { + ui.ctx().request_repaint(); + if let Some(order) = self.draw_order.iter().position(|idx| *idx == node_idx) { + self.draw_order.remove(order); + self.draw_order.push(node_idx); + } + } + } + + //First step for split big function to parts + /// Draw one node. Return Pins info + #[inline] + #[allow(clippy::too_many_lines)] + #[allow(clippy::too_many_arguments)] + fn draw_node( + &mut self, + ui: &mut Ui, + node: NodeId, + viewer: &mut V, + snarl_state: &mut SnarlState, + style: &SnarlStyle, + snarl_id: Id, + node_style: &Style, + node_frame: &Frame, + header_frame: &Frame, + input_positions: &mut HashMap, + input: &Input, + output_positions: &mut HashMap, + ) -> DrawNodeResponse + where + V: SnarlViewer, + { + let Node { + pos, + open, + ref value, + } = self.nodes[node.0]; + + let mut response = DrawNodeResponse { + node_to_top: None, + node_moved: None, + drag_stopped: false, + pin_hovered: None, + }; + + let viewport = ui.max_rect(); + + // Collect pins + let inputs_count = viewer.inputs(value); + let outputs_count = viewer.outputs(value); + + let node_pos = snarl_state.graph_pos_to_screen(pos, viewport); + + // Generate persistent id for the node. + let node_id = snarl_id.with(("snarl-node", node)); + + let openness = ui.ctx().animate_bool(node_id, open); + + let mut node_state = + NodeState::load(ui.ctx(), node_id, &node_style.spacing, snarl_state.scale()); + + let node_rect = node_state.node_rect(node_pos, openness); + + // Rect for node + frame margin. + let node_frame_rect = node_frame.total_margin().expand_rect(node_rect); + + let pin_size = style + .pin_size + .zoomed(snarl_state.scale()) + .unwrap_or(node_style.spacing.interact_size.y * 0.5); + + let header_drag_space = style + .header_drag_space + .zoomed(snarl_state.scale()) + .unwrap_or_else(|| vec2(node_style.spacing.icon_width, node_style.spacing.icon_width)); + + let inputs = (0..inputs_count) + .map(|idx| InPin::new(self, InPinId { node, input: idx })) + .collect::>(); + + let outputs = (0..outputs_count) + .map(|idx| OutPin::new(self, OutPinId { node, output: idx })) + .collect::>(); + + // Interact with node frame. + let r = ui.interact(node_frame_rect, node_id, Sense::click_and_drag()); + + if r.dragged_by(PointerButton::Primary) { + response.node_moved = Some((node, snarl_state.screen_vec_to_graph(r.drag_delta()))); + } + if r.clicked() || r.dragged() { + response.node_to_top = Some(node); + } + r.context_menu(|ui| { + viewer.node_menu(node, &inputs, &outputs, ui, snarl_state.scale(), self); + }); + + if !self.nodes.contains(node.0) { + node_state.clear(ui.ctx()); + // If removed + return response; + } + + if viewer.has_on_hover_popup(&self.nodes[node.0].value) { + r.on_hover_ui_at_pointer(|ui| { + viewer.show_on_hover_popup(node, &inputs, &outputs, ui, snarl_state.scale(), self); + }); + } + + if !self.nodes.contains(node.0) { + node_state.clear(ui.ctx()); + // If removed + return response; + } + + let node_ui = &mut ui.child_ui_with_id_source( + node_frame_rect, + Layout::top_down(Align::Center), + ("node", node_id), + ); + node_ui.set_style(node_style.clone()); + + node_frame.show(node_ui, |ui| { + // Render header frame. + let mut header_rect = node_rect; + + let mut header_frame_rect = header_frame.total_margin().expand_rect(header_rect); + + // Show node's header + let header_ui = &mut ui.child_ui_with_id_source( + header_frame_rect, + Layout::top_down(Align::Center), + "header", + ); + + header_frame.show(header_ui, |ui: &mut Ui| { + ui.with_layout(Layout::left_to_right(Align::Min), |ui| { + if style.collapsible { + let (_, r) = ui.allocate_exact_size( + vec2(node_style.spacing.icon_width, node_style.spacing.icon_width), + Sense::click(), + ); + paint_default_icon(ui, openness, &r); + + if r.clicked_by(PointerButton::Primary) { + // Toggle node's openness. + self.open_node(node, !open); + } + } + + ui.allocate_exact_size(header_drag_space, Sense::hover()); + + viewer.show_header(node, &inputs, &outputs, ui, snarl_state.scale(), self); + + header_rect = ui.min_rect(); + }); + + header_frame_rect = header_frame.total_margin().expand_rect(header_rect); + + ui.advance_cursor_after_rect(Rect::from_min_max( + header_rect.min, + pos2( + f32::max(header_rect.max.x, node_rect.max.x), + header_rect.min.y, + ), + )); + }); + let header_rect = header_rect; + ui.expand_to_include_rect(header_rect); + let header_size = header_rect.size(); + node_state.set_header_height(header_size.y); + + if !self.nodes.contains(node.0) { + // If removed + return; + } + + let min_pin_y = header_rect.center().y; + + let input_x = node_frame_rect.left() + node_frame.inner_margin.left + pin_size; + + let output_x = node_frame_rect.right() - node_frame.inner_margin.right - pin_size; + + // Input/output pin block + + if (openness < 1.0 && open) || (openness > 0.0 && !open) { + ui.ctx().request_repaint(); + } + + // Pins are placed under the header and must not go outside of the header frame. + let payload_rect = Rect::from_min_max( + pos2( + header_rect.min.x, + header_frame_rect.max.y + node_style.spacing.item_spacing.y + - node_state.payload_offset(openness), + ), + pos2(f32::max(node_rect.max.x, header_rect.max.x), f32::INFINITY), + ); + + let payload_clip_rect = Rect::from_min_max( + pos2(header_rect.min.x, header_frame_rect.max.y), + pos2(f32::max(node_rect.max.x, header_rect.max.x), f32::INFINITY), + ); + + // Show input pins. + + // Input pins on the left. + let inputs_ui = &mut ui.child_ui_with_id_source( + payload_rect, + Layout::top_down(Align::Min), + "inputs", + ); + + inputs_ui.set_clip_rect(payload_clip_rect.intersect(viewport)); + + for in_pin in &inputs { + // Show input pin. + inputs_ui.with_layout(Layout::left_to_right(Align::Min), |ui| { + // Allocate space for pin shape. + let (pin_id, _) = ui.allocate_space(vec2(pin_size * 1.5, pin_size * 1.5)); + + let y0 = ui.cursor().min.y; + + // Show input content + let pin_info = viewer.show_input(in_pin, ui, snarl_state.scale(), self); + if !self.nodes.contains(node.0) { + // If removed + return; + } + + let y1 = ui.min_rect().max.y; + + // ui.end_row(); + + // Centered vertically. + let y = min_pin_y.max((y0 + y1) * 0.5); + + let pin_pos = pos2(input_x, y); + + input_positions.insert(in_pin.id, (pin_pos, pin_info.fill)); + + // Interact with pin shape. + let r = ui.interact( + Rect::from_center_size(pin_pos, vec2(pin_size, pin_size)), + pin_id, + Sense::click_and_drag(), + ); + + if r.clicked_by(PointerButton::Secondary) { + if snarl_state.has_new_wires() { + snarl_state.remove_new_wire_in(in_pin.id); + } else { + viewer.drop_inputs(in_pin, self); + if !self.nodes.contains(node.0) { + // If removed + return; + } + } + } + if r.drag_started_by(PointerButton::Primary) { + if input.modifiers.command { + snarl_state.start_new_wires_out(&in_pin.remotes); + if !input.modifiers.shift { + self.drop_inputs(in_pin.id); + if !self.nodes.contains(node.0) { + // If removed + return; + } + } + } else { + snarl_state.start_new_wire_in(in_pin.id); + } + } + if r.drag_stopped() { + response.drag_stopped = true; + } + + let mut pin_size = pin_size; + + match input.hover_pos { + Some(hover_pos) if r.rect.contains(hover_pos) => { + if input.modifiers.shift { + snarl_state.add_new_wire_in(in_pin.id); + } else if input.secondary_pressed { + snarl_state.remove_new_wire_in(in_pin.id); + } + response.pin_hovered = Some(AnyPin::In(in_pin.id)); + pin_size *= 1.2; + } + _ => {} + } + + draw_pin(ui.painter(), pin_info, pin_pos, pin_size); + }); + } + let inputs_rect = inputs_ui.min_rect(); + ui.expand_to_include_rect(inputs_rect.intersect(payload_clip_rect)); + let inputs_size = inputs_rect.size(); + + if !self.nodes.contains(node.0) { + // If removed + return; + } + + // Show output pins. + + // Outputs are placed under the header and must not go outside of the header frame. + + let outputs_ui = &mut ui.child_ui_with_id_source( + payload_rect, + Layout::top_down(Align::Max), + "outputs", + ); + + outputs_ui.set_clip_rect(payload_clip_rect.intersect(viewport)); + + // Output pins on the right. + for out_pin in &outputs { + // Show output pin. + outputs_ui.with_layout(Layout::right_to_left(Align::Min), |ui| { + // Allocate space for pin shape. + + let (pin_id, _) = ui.allocate_space(vec2(pin_size * 1.5, pin_size * 1.5)); + + let y0 = ui.cursor().min.y; + + // Show output content + let pin_info = viewer.show_output(out_pin, ui, snarl_state.scale(), self); + if !self.nodes.contains(node.0) { + // If removed + return; + } + + let y1 = ui.min_rect().max.y; + + // ui.end_row(); + + // Centered vertically. + let y = min_pin_y.max((y0 + y1) * 0.5); + + let pin_pos = pos2(output_x, y); + + output_positions.insert(out_pin.id, (pin_pos, pin_info.fill)); + + let r = ui.interact( + Rect::from_center_size(pin_pos, vec2(pin_size, pin_size)), + pin_id, + Sense::click_and_drag(), + ); + + if r.clicked_by(PointerButton::Secondary) { + if snarl_state.has_new_wires() { + snarl_state.remove_new_wire_out(out_pin.id); + } else { + viewer.drop_outputs(out_pin, self); + if !self.nodes.contains(node.0) { + // If removed + return; + } + } + } + if r.drag_started_by(PointerButton::Primary) { + if input.modifiers.command { + snarl_state.start_new_wires_in(&out_pin.remotes); + + if !input.modifiers.shift { + self.drop_outputs(out_pin.id); + if !self.nodes.contains(node.0) { + // If removed + return; + } + } + } else { + snarl_state.start_new_wire_out(out_pin.id); + } + } + if r.drag_stopped() { + response.drag_stopped = true; + } + + let mut pin_size = pin_size; + match input.hover_pos { + Some(hover_pos) if r.rect.contains(hover_pos) => { + if input.modifiers.shift { + snarl_state.add_new_wire_out(out_pin.id); + } else if input.secondary_pressed { + snarl_state.remove_new_wire_out(out_pin.id); + } + response.pin_hovered = Some(AnyPin::Out(out_pin.id)); + pin_size *= 1.2; + } + _ => {} + } + draw_pin(ui.painter(), pin_info, pin_pos, pin_size); + }); + } + let outputs_rect = outputs_ui.min_rect(); + ui.expand_to_include_rect(outputs_rect.intersect(payload_clip_rect)); + let outputs_size = outputs_rect.size(); + + if !self.nodes.contains(node.0) { + // If removed + return; + } + + let mut new_pins_size = vec2( + inputs_size.x + outputs_size.x + node_style.spacing.item_spacing.x, + f32::max(inputs_size.y, outputs_size.y), + ); + + let mut pins_bottom = f32::max(inputs_rect.bottom(), outputs_rect.bottom()); + + // Show body if there's one. + if viewer.has_body(&self.nodes.get(node.0).unwrap().value) { + let body_left = inputs_rect.right() + node_style.spacing.item_spacing.x; + let body_right = outputs_rect.left() - node_style.spacing.item_spacing.x; + let body_top = payload_rect.top(); + + let mut body_rect = + Rect::from_min_max(pos2(body_left, body_top), pos2(body_right, f32::INFINITY)); + body_rect = node_state.align_body(body_rect); + + let mut body_ui = ui.child_ui_with_id_source( + body_rect, + Layout::left_to_right(Align::Min), + "body", + ); + body_ui.set_clip_rect(payload_clip_rect.intersect(viewport)); + + viewer.show_body( + node, + &inputs, + &outputs, + &mut body_ui, + snarl_state.scale(), + self, + ); + + body_rect = body_ui.min_rect(); + ui.expand_to_include_rect(body_rect.intersect(payload_clip_rect)); + let body_size = body_rect.size(); + node_state.set_body_width(body_size.x); + + new_pins_size.x += body_size.x + node_style.spacing.item_spacing.x; + new_pins_size.y = f32::max(new_pins_size.y, body_size.y); + + pins_bottom = f32::max(pins_bottom, body_rect.bottom()); + + if !self.nodes.contains(node.0) { + // If removed + return; + } + } + + if viewer.has_footer(&self.nodes[node.0].value) { + let footer_left = node_rect.left(); + let footer_right = node_rect.right(); + let footer_top = pins_bottom + node_style.spacing.item_spacing.y; + + let mut footer_rect = Rect::from_min_max( + pos2(footer_left, footer_top), + pos2(footer_right, f32::INFINITY), + ); + + footer_rect = node_state.align_footer(footer_rect); + + let mut footer_ui = ui.child_ui_with_id_source( + footer_rect, + Layout::left_to_right(Align::Min), + "footer", + ); + footer_ui.set_clip_rect(payload_clip_rect.intersect(viewport)); + + viewer.show_footer( + node, + &inputs, + &outputs, + &mut footer_ui, + snarl_state.scale(), + self, + ); + + footer_rect = footer_ui.min_rect(); + ui.expand_to_include_rect(footer_rect.intersect(payload_clip_rect)); + let footer_size = footer_rect.size(); + node_state.set_footer_width(footer_size.x); + + new_pins_size.x = f32::max(new_pins_size.x, footer_size.x); + new_pins_size.y += footer_size.y + node_style.spacing.item_spacing.y; + + if !self.nodes.contains(node.0) { + // If removed + return; + } + } + + node_state.set_size(vec2( + f32::max(header_size.x, new_pins_size.x), + header_size.y + + header_frame.total_margin().bottom + + node_style.spacing.item_spacing.y + + new_pins_size.y, + )); + }); + + if !self.nodes.contains(node.0) { + ui.ctx().request_repaint(); + node_state.clear(ui.ctx()); + // If removed + return response; + } + + node_state.store(ui.ctx()); + ui.ctx().request_repaint(); + response + } +} diff --git a/vendor/egui-snarl/src/ui/background_pattern.rs b/vendor/egui-snarl/src/ui/background_pattern.rs new file mode 100644 index 0000000..44e448b --- /dev/null +++ b/vendor/egui-snarl/src/ui/background_pattern.rs @@ -0,0 +1,259 @@ +use std::fmt; + +use egui::{emath::Rot2, vec2, Pos2, Rect, Stroke, Ui, Vec2}; + +use super::SnarlStyle; + +/// Viewport is a rectangle in graph space that is visible on screen. +pub struct Viewport { + /// Screen-space rectangle. + pub rect: Rect, + + /// Scale of the viewport. + pub scale: f32, + + /// Offset of the viewport. + pub offset: Vec2, +} + +impl Viewport { + /// Converts screen-space position to graph-space position. + #[inline(always)] + pub fn screen_pos_to_graph(&self, pos: Pos2) -> Pos2 { + (pos + self.offset - self.rect.center().to_vec2()) / self.scale + } + + /// Converts graph-space position to screen-space position. + #[inline(always)] + pub fn graph_pos_to_screen(&self, pos: Pos2) -> Pos2 { + pos * self.scale - self.offset + self.rect.center().to_vec2() + } + + /// Converts screen-space vector to graph-space vector. + #[inline(always)] + pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 { + size * self.scale + } + + /// Converts graph-space vector to screen-space vector. + #[inline(always)] + pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 { + size / self.scale + } + + /// Converts screen-space size to graph-space size. + #[inline(always)] + pub fn graph_size_to_screen(&self, size: f32) -> f32 { + size * self.scale + } + + /// Converts graph-space size to screen-space size. + #[inline(always)] + pub fn screen_size_to_graph(&self, size: f32) -> f32 { + size / self.scale + } +} + +///Grid background pattern. +///Use `SnarlStyle::background_pattern_stroke` for change stroke options +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))] +pub struct Grid { + /// Spacing between grid lines. + pub spacing: Vec2, + + /// Angle of the grid. + #[cfg_attr(feature = "egui-probe", egui_probe(as egui_probe::angle))] + pub angle: f32, +} + +const DEFAULT_GRID_SPACING: Vec2 = vec2(5.0, 5.0); +macro_rules! default_grid_spacing { + () => { + stringify!(vec2(5.0, 5.0)) + }; +} + +const DEFAULT_GRID_ANGLE: f32 = 1.0; +macro_rules! default_grid_angle { + () => { + stringify!(1.0) + }; +} + +impl Default for Grid { + fn default() -> Self { + Self { + spacing: DEFAULT_GRID_SPACING, + angle: DEFAULT_GRID_ANGLE, + } + } +} + +impl Grid { + /// Create new grid with given spacing and angle. + pub const fn new(spacing: Vec2, angle: f32) -> Self { + Self { spacing, angle } + } + + fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) { + let bg_stroke = style + .background_pattern_stroke + .unwrap_or_else(|| ui.visuals().widgets.noninteractive.bg_stroke); + + let stroke = Stroke::new( + bg_stroke.width * viewport.scale.max(1.0), + bg_stroke.color.gamma_multiply(viewport.scale.min(1.0)), + ); + + let spacing = + ui.spacing().icon_width * vec2(self.spacing.x.max(1.0), self.spacing.y.max(1.0)); + + let rot = Rot2::from_angle(self.angle); + let rot_inv = rot.inverse(); + + let graph_viewport = Rect::from_min_max( + viewport.screen_pos_to_graph(viewport.rect.min), + viewport.screen_pos_to_graph(viewport.rect.max), + ); + + let pattern_bounds = graph_viewport.rotate_bb(rot_inv); + + let min_x = (pattern_bounds.min.x / spacing.x).ceil(); + let max_x = (pattern_bounds.max.x / spacing.x).floor(); + + for x in 0..=(max_x - min_x) as i64 { + #[allow(clippy::cast_possible_truncation)] + let x = (x as f32 + min_x) * spacing.x; + + let top = (rot * vec2(x, pattern_bounds.min.y)).to_pos2(); + let bottom = (rot * vec2(x, pattern_bounds.max.y)).to_pos2(); + + let top = viewport.graph_pos_to_screen(top); + let bottom = viewport.graph_pos_to_screen(bottom); + + ui.painter().line_segment([top, bottom], stroke); + } + + let min_y = (pattern_bounds.min.y / spacing.y).ceil(); + let max_y = (pattern_bounds.max.y / spacing.y).floor(); + + for y in 0..=(max_y - min_y) as i64 { + #[allow(clippy::cast_possible_truncation)] + let y = (y as f32 + min_y) * spacing.y; + + let top = (rot * vec2(pattern_bounds.min.x, y)).to_pos2(); + let bottom = (rot * vec2(pattern_bounds.max.x, y)).to_pos2(); + + let top = viewport.graph_pos_to_screen(top); + let bottom = viewport.graph_pos_to_screen(bottom); + + ui.painter().line_segment([top, bottom], stroke); + } + } +} + +tiny_fn::tiny_fn! { + /// Custom background pattern function with signature + /// `Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui)` + pub struct CustomBackground = Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui); +} + +impl Default for CustomBackground<'_, INLINE_SIZE> { + fn default() -> Self { + Self::new(|_, _, _| {}) + } +} + +#[cfg(feature = "egui-probe")] +impl egui_probe::EguiProbe for CustomBackground<'_, INLINE_SIZE> { + fn probe( + &mut self, + ui: &mut egui_probe::egui::Ui, + _style: &egui_probe::Style, + ) -> egui_probe::egui::Response { + ui.weak("Custom") + } +} + +/// Background pattern show beneath nodes and wires. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))] +pub enum BackgroundPattern { + /// No pattern. + NoPattern, + + /// Linear grid. + #[cfg_attr(feature = "egui-probe", egui_probe(transparent))] + Grid(Grid), + + /// Custom pattern. + /// Contains function with signature + /// `Fn(style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui)` + #[cfg_attr(feature = "egui-probe", egui_probe(transparent))] + Custom(#[cfg_attr(feature = "serde", serde(skip))] CustomBackground<'static>), +} + +impl PartialEq for BackgroundPattern { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (BackgroundPattern::Grid(l), BackgroundPattern::Grid(r)) => *l == *r, + _ => false, + } + } +} + +impl fmt::Debug for BackgroundPattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BackgroundPattern::Grid(grid) => f + .debug_tuple("BackgroundPattern::Grid") + .field(grid) + .finish(), + BackgroundPattern::Custom(_) => f.write_str("BackgroundPattern::Custom"), + BackgroundPattern::NoPattern => f.write_str("BackgroundPattern::NoPattern"), + } + } +} + +impl Default for BackgroundPattern { + fn default() -> Self { + Self::Grid(Default::default()) + } +} + +impl BackgroundPattern { + /// Create new background pattern with default values. + /// + /// Default patter is `Grid` with spacing - ` + #[doc = default_grid_spacing!()] + /// ` and angle - ` + #[doc = default_grid_angle!()] + /// ` radian. + pub const fn new() -> Self { + Self::Grid(Grid::new(DEFAULT_GRID_SPACING, DEFAULT_GRID_ANGLE)) + } + + /// Create new grid background pattern with given spacing and angle. + pub const fn grid(spacing: Vec2, angle: f32) -> Self { + Self::Grid(Grid::new(spacing, angle)) + } + + /// Create new custom background pattern. + pub fn custom(f: F) -> Self + where + F: Fn(&SnarlStyle, &Viewport, &mut Ui) + 'static, + { + Self::Custom(CustomBackground::new(f)) + } + + /// Draws background pattern. + pub(super) fn draw(&self, style: &SnarlStyle, viewport: &Viewport, ui: &mut Ui) { + match self { + BackgroundPattern::Grid(g) => g.draw(style, viewport, ui), + BackgroundPattern::Custom(c) => c.call(style, viewport, ui), + BackgroundPattern::NoPattern => {} + } + } +} diff --git a/vendor/egui-snarl/src/ui/effect.rs b/vendor/egui-snarl/src/ui/effect.rs new file mode 100644 index 0000000..9b593bf --- /dev/null +++ b/vendor/egui-snarl/src/ui/effect.rs @@ -0,0 +1,160 @@ +use std::cell::RefCell; + +use egui::Pos2; + +use crate::{wire_pins, InPinId, Node, OutPinId, Snarl}; + +pub enum Effect { + /// Adds a new node to the Snarl. + InsertNode { pos: Pos2, node: T }, + + /// Removes a node from snarl. + RemoveNode { node: NodeId }, + + /// Opens/closes a node. + OpenNode { node: NodeId, open: bool }, + + /// Adds connection between two nodes. + Connect { from: OutPinId, to: InPinId }, + + /// Removes connection between two nodes. + Disconnect { from: OutPinId, to: InPinId }, + + /// Removes all connections from the output pin. + DropOutputs { pin: OutPinId }, + + /// Removes all connections to the input pin. + DropInputs { pin: InPinId }, + + /// Executes a closure with mutable reference to the Snarl. + Closure(Box)>), +} + +/// Contained for deferred execution of effects. +/// It is populated by [`SnarlViewer`] methods and then applied to the Snarl. +pub struct Effects { + effects: Vec>, +} + +impl Default for Effects { + #[inline] + fn default() -> Self { + Effects { + effects: Default::default(), + } + } +} + +impl Effects { + #[inline(always)] + #[doc(hidden)] + pub fn new() -> Self { + Effects { + effects: Vec::new(), + } + } + + /// Returns `true` if there are no effects. + /// Returns `false` otherwise. + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.effects.is_empty() + } + + /// Inserts a new node to the Snarl. + #[inline(always)] + pub fn insert_node(&mut self, pos: Pos2, node: T) { + self.effects.push(Effect::InsertNode { node, pos }); + } + + /// Removes a node from the Snarl. + #[inline(always)] + pub fn remove_node(&mut self, node: NodeId) { + self.effects.push(Effect::RemoveNode { node }); + } + + /// Opens/closes a node. + #[inline(always)] + pub fn open_node(&mut self, node: NodeId, open: bool) { + self.effects.push(Effect::OpenNode { node, open }); + } + + /// Connects two nodes. + #[inline(always)] + pub fn connect(&mut self, from: OutPinId, to: InPinId) { + self.effects.push(Effect::Connect { from, to }); + } + + /// Disconnects two nodes. + #[inline(always)] + pub fn disconnect(&mut self, from: OutPinId, to: InPinId) { + self.effects.push(Effect::Disconnect { from, to }); + } + + /// Removes all connections from the output pin. + #[inline(always)] + pub fn drop_inputs(&mut self, pin: InPinId) { + self.effects.push(Effect::DropInputs { pin }); + } + + /// Removes all connections to the input pin. + #[inline(always)] + pub fn drop_outputs(&mut self, pin: OutPinId) { + self.effects.push(Effect::DropOutputs { pin }); + } +} + +impl Snarl { + pub fn apply_effects(&mut self, effects: Effects) { + if effects.effects.is_empty() { + return; + } + for effect in effects.effects { + self.apply_effect(effect); + } + } + + pub fn apply_effect(&mut self, effect: Effect) { + match effect { + Effect::InsertNode { node, pos } => { + let idx = self.nodes.insert(Node { + value: RefCell::new(node), + pos, + open: true, + }); + self.draw_order.push(idx); + } + Effect::RemoveNode { node } => { + if self.nodes.contains(node) { + self.remove_node(node); + } + } + Effect::OpenNode { node, open } => { + if self.nodes.contains(node) { + self.nodes[node].open = open; + } + } + Effect::Connect { from, to } => { + if self.nodes.contains(from.node) && self.nodes.contains(to.node) { + self.wires.insert(wire_pins(from, to)); + } + } + Effect::Disconnect { from, to } => { + if self.nodes.contains(from.node) && self.nodes.contains(to.node) { + self.wires.remove(&wire_pins(from, to)); + } + } + Effect::DropOutputs { pin } => { + if self.nodes.contains(pin.node) { + self.wires.drop_outputs(pin); + } + } + Effect::DropInputs { pin } => { + if self.nodes.contains(pin.node) { + self.wires.drop_inputs(pin); + } + } + Effect::Closure(f) => f(self), + } + } +} diff --git a/vendor/egui-snarl/src/ui/pin.rs b/vendor/egui-snarl/src/ui/pin.rs new file mode 100644 index 0000000..3d0a3e3 --- /dev/null +++ b/vendor/egui-snarl/src/ui/pin.rs @@ -0,0 +1,171 @@ +use egui::{epaint::PathShape, vec2, Color32, Painter, Pos2, Rect, Shape, Stroke, Vec2}; + +use crate::{InPinId, OutPinId}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AnyPin { + Out(OutPinId), + In(InPinId), +} + +/// In the current context, these are the I/O pins of the 'source' node that the newly +/// created node's I/O pins will connect to. +#[derive(Debug)] +pub enum AnyPins<'a> { + /// Output pins. + Out(&'a [OutPinId]), + /// Input pins + In(&'a [InPinId]), +} + +tiny_fn::tiny_fn! { + /// Custom pin shape drawing function with signature + /// `Fn(painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke)` + pub struct CustomPinShape = Fn(painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke); +} + +/// Shape of a pin. +pub enum PinShape { + /// Circle shape. + Circle, + + /// Triangle shape. + Triangle, + + /// Square shape. + Square, + + /// Custom shape. + Custom(CustomPinShape<'static>), +} + +/// Information about a pin returned by `SnarlViewer::show_input` and `SnarlViewer::show_output`. +pub struct PinInfo { + /// Shape of the pin. + pub shape: PinShape, + + /// Size of the pin. + pub size: f32, + + /// Fill color of the pin. + pub fill: Color32, + + /// Outline stroke of the pin. + pub stroke: Stroke, +} + +impl Default for PinInfo { + fn default() -> Self { + PinInfo { + shape: PinShape::Circle, + size: 1.0, + fill: Color32::GRAY, + stroke: Stroke::new(1.0, Color32::BLACK), + } + } +} + +impl PinInfo { + /// Sets the shape of the pin. + pub fn with_shape(mut self, shape: PinShape) -> Self { + self.shape = shape; + self + } + + /// Sets the size of the pin. + pub fn with_size(mut self, size: f32) -> Self { + self.size = size; + self + } + + /// Sets the fill color of the pin. + pub fn with_fill(mut self, fill: Color32) -> Self { + self.fill = fill; + self + } + + /// Sets the outline stroke of the pin. + pub fn with_stroke(mut self, stroke: Stroke) -> Self { + self.stroke = stroke; + self + } + + /// Creates a circle pin. + pub fn circle() -> Self { + PinInfo { + shape: PinShape::Circle, + ..Default::default() + } + } + + /// Creates a triangle pin. + pub fn triangle() -> Self { + PinInfo { + shape: PinShape::Triangle, + ..Default::default() + } + } + + /// Creates a square pin. + pub fn square() -> Self { + PinInfo { + shape: PinShape::Square, + ..Default::default() + } + } + + /// Creates a square pin. + pub fn custom(f: F) -> Self + where + F: Fn(&Painter, Rect, Color32, Stroke) + 'static, + { + PinInfo { + shape: PinShape::Custom(CustomPinShape::new(f)), + ..Default::default() + } + } +} + +pub fn draw_pin(painter: &Painter, pin: PinInfo, pos: Pos2, base_size: f32) { + let size = base_size * pin.size; + match pin.shape { + PinShape::Circle => { + painter.circle(pos, size * 2.0 / std::f32::consts::PI, pin.fill, pin.stroke); + } + PinShape::Triangle => { + const A: Vec2 = vec2(-0.649_519, 0.4875); + const B: Vec2 = vec2(0.649_519, 0.4875); + const C: Vec2 = vec2(0.0, -0.6375); + + let points = vec![pos + A * size, pos + B * size, pos + C * size]; + + painter.add(Shape::Path(PathShape { + points, + closed: true, + fill: pin.fill, + stroke: pin.stroke, + })); + } + PinShape::Square => { + let points = vec![ + pos + vec2(-0.5, -0.5) * size, + pos + vec2(0.5, -0.5) * size, + pos + vec2(0.5, 0.5) * size, + pos + vec2(-0.5, 0.5) * size, + ]; + + painter.add(Shape::Path(PathShape { + points, + closed: true, + fill: pin.fill, + stroke: pin.stroke, + })); + } + PinShape::Custom(f) => f.call( + painter, + Rect::from_center_size(pos, vec2(size, size)), + pin.fill, + pin.stroke, + ), + } +} diff --git a/vendor/egui-snarl/src/ui/state.rs b/vendor/egui-snarl/src/ui/state.rs new file mode 100644 index 0000000..c51aeb7 --- /dev/null +++ b/vendor/egui-snarl/src/ui/state.rs @@ -0,0 +1,404 @@ +use egui::{style::Spacing, Align, Context, Id, Pos2, Rect, Vec2}; + +use crate::{InPinId, OutPinId, Snarl}; + +use super::SnarlStyle; + +/// Node UI state. + +pub struct NodeState { + /// Node size for this frame. + /// It is updated to fit content. + size: Vec2, + header_height: f32, + body_width: f32, + footer_width: f32, + + id: Id, + scale: f32, + dirty: bool, +} + +#[derive(Clone, Copy, PartialEq)] +struct NodeData { + unscaled_size: Vec2, + unscaled_header_height: f32, + unscaled_body_width: f32, + unsacled_footer_width: f32, +} + +impl NodeState { + pub fn load(cx: &Context, id: Id, spacing: &Spacing, scale: f32) -> Self { + match cx.data_mut(|d| d.get_temp::(id)) { + Some(data) => NodeState { + size: data.unscaled_size * scale, + header_height: data.unscaled_header_height * scale, + body_width: data.unscaled_body_width * scale, + footer_width: data.unsacled_footer_width * scale, + id, + scale, + dirty: false, + }, + None => Self::initial(id, spacing, scale), + } + } + + pub fn clear(self, cx: &Context) { + cx.data_mut(|d| d.remove::(self.id)); + } + + pub fn store(&self, cx: &Context) { + if self.dirty { + cx.data_mut(|d| { + d.insert_temp( + self.id, + NodeData { + unscaled_size: self.size / self.scale, + unscaled_header_height: self.header_height / self.scale, + unscaled_body_width: self.body_width / self.scale, + unsacled_footer_width: self.footer_width / self.scale, + }, + ) + }); + } + } + + /// Finds node rect at specific position (excluding node frame margin). + pub fn node_rect(&self, pos: Pos2, openness: f32) -> Rect { + Rect::from_min_size( + pos, + egui::vec2( + self.size.x, + f32::max(self.header_height, self.size.y * openness), + ), + ) + } + + pub fn payload_offset(&self, openness: f32) -> f32 { + (self.size.y) * (1.0 - openness) + } + + pub fn align_body(&mut self, rect: Rect) -> Rect { + let x_range = Align::Center.align_size_within_range(self.body_width, rect.x_range()); + Rect::from_x_y_ranges(x_range, rect.y_range()) + } + + pub fn align_footer(&mut self, rect: Rect) -> Rect { + let x_range = Align::Center.align_size_within_range(self.footer_width, rect.x_range()); + Rect::from_x_y_ranges(x_range, rect.y_range()) + } + + pub fn set_size(&mut self, size: Vec2) { + if self.size != size { + self.size = size; + self.dirty = true; + } + } + + pub fn set_header_height(&mut self, height: f32) { + if self.header_height != height { + self.header_height = height; + self.dirty = true; + } + } + + pub fn set_body_width(&mut self, width: f32) { + if self.body_width != width { + self.body_width = width; + self.dirty = true; + } + } + + pub fn set_footer_width(&mut self, width: f32) { + if self.footer_width != width { + self.footer_width = width; + self.dirty = true; + } + } + + fn initial(id: Id, spacing: &Spacing, scale: f32) -> Self { + NodeState { + size: spacing.interact_size, + header_height: spacing.interact_size.y, + body_width: 0.0, + footer_width: 0.0, + id, + dirty: true, + scale, + } + } +} + +#[derive(Clone)] +pub enum NewWires { + In(Vec), + Out(Vec), +} + +pub struct SnarlState { + /// Where viewport's center in graph's space. + offset: Vec2, + + /// Scale of the viewport. + scale: f32, + + target_scale: f32, + + new_wires: Option, + + id: Id, + + /// Flag indicating that the graph state is dirty must be saved. + dirty: bool, + + /// Flag indicating that the link menu is open. + is_link_menu_open: bool, +} + +#[derive(Clone)] +struct SnarlStateData { + offset: Vec2, + scale: f32, + target_scale: f32, + new_wires: Option, + is_link_menu_open: bool, +} + +impl SnarlState { + pub fn load( + cx: &Context, + id: Id, + pivot: Pos2, + viewport: Rect, + snarl: &Snarl, + style: &SnarlStyle, + ) -> Self { + let Some(mut data) = cx.data_mut(|d| d.get_temp::(id)) else { + return Self::initial(id, viewport, snarl, style); + }; + + let new_scale = cx.animate_value_with_time(id.with("zoom-scale"), data.target_scale, 0.1); + + let mut dirty = false; + if new_scale != data.scale { + let a = pivot + data.offset - viewport.center().to_vec2(); + + data.offset += a * new_scale / data.scale - a; + data.scale = new_scale; + dirty = true; + } + + SnarlState { + offset: data.offset, + scale: data.scale, + target_scale: data.target_scale, + new_wires: data.new_wires, + is_link_menu_open: data.is_link_menu_open, + id, + dirty, + } + } + + fn initial(id: Id, viewport: Rect, snarl: &Snarl, style: &SnarlStyle) -> Self { + let mut bb = Rect::NOTHING; + + for (_, node) in snarl.nodes.iter() { + bb.extend_with(node.pos); + } + + if !bb.is_positive() { + let scale = 1.0f32.clamp(style.min_scale, style.max_scale); + + return SnarlState { + offset: Vec2::ZERO, + scale, + target_scale: scale, + new_wires: None, + is_link_menu_open: false, + id, + dirty: true, + }; + } + + bb = bb.expand(100.0); + + let bb_size = bb.size(); + let viewport_size = viewport.size(); + + let scale = (viewport_size.x / bb_size.x) + .min(1.0) + .min(viewport_size.y / bb_size.y) + .min(style.max_scale) + .max(style.min_scale); + + let offset = bb.center().to_vec2() * scale; + + SnarlState { + offset, + scale, + target_scale: scale, + new_wires: None, + is_link_menu_open: false, + id, + dirty: true, + } + } + + #[inline(always)] + pub fn store(self, cx: &Context) { + if self.dirty { + cx.data_mut(|d| { + d.insert_temp( + self.id, + SnarlStateData { + offset: self.offset, + scale: self.scale, + target_scale: self.target_scale, + new_wires: self.new_wires, + is_link_menu_open: self.is_link_menu_open, + }, + ) + }); + } + } + + #[inline(always)] + pub fn pan(&mut self, delta: Vec2) { + self.offset += delta; + self.dirty = true; + } + + #[inline(always)] + pub fn scale(&self) -> f32 { + self.scale + } + + #[inline(always)] + pub fn offset(&self) -> Vec2 { + self.offset + } + + #[inline(always)] + pub fn set_scale(&mut self, scale: f32) { + self.target_scale = scale; + self.dirty = true; + } + + #[inline(always)] + pub fn screen_pos_to_graph(&self, pos: Pos2, viewport: Rect) -> Pos2 { + (pos + self.offset - viewport.center().to_vec2()) / self.scale + } + + #[inline(always)] + pub fn graph_pos_to_screen(&self, pos: Pos2, viewport: Rect) -> Pos2 { + pos * self.scale - self.offset + viewport.center().to_vec2() + } + + // #[inline(always)] + // pub fn graph_vec_to_screen(&self, size: Vec2) -> Vec2 { + // size * self.scale + // } + + #[inline(always)] + pub fn screen_vec_to_graph(&self, size: Vec2) -> Vec2 { + size / self.scale + } + + // #[inline(always)] + // pub fn graph_value_to_screen(&self, value: f32) -> f32 { + // value * self.scale + // } + + // #[inline(always)] + // pub fn screen_value_to_graph(&self, value: f32) -> f32 { + // value / self.scale + // } + + pub fn start_new_wire_in(&mut self, pin: InPinId) { + self.new_wires = Some(NewWires::In(vec![pin])); + self.dirty = true; + } + + pub fn start_new_wire_out(&mut self, pin: OutPinId) { + self.new_wires = Some(NewWires::Out(vec![pin])); + self.dirty = true; + } + + pub fn start_new_wires_in(&mut self, pins: &[InPinId]) { + self.new_wires = Some(NewWires::In(pins.to_vec())); + self.dirty = true; + } + + pub fn start_new_wires_out(&mut self, pins: &[OutPinId]) { + self.new_wires = Some(NewWires::Out(pins.to_vec())); + self.dirty = true; + } + + pub fn add_new_wire_in(&mut self, pin: InPinId) { + if let Some(NewWires::In(pins)) = &mut self.new_wires { + if !pins.contains(&pin) { + pins.push(pin); + self.dirty = true; + } + } + } + + pub fn add_new_wire_out(&mut self, pin: OutPinId) { + if let Some(NewWires::Out(pins)) = &mut self.new_wires { + if !pins.contains(&pin) { + pins.push(pin); + self.dirty = true; + } + } + } + + pub fn remove_new_wire_in(&mut self, pin: InPinId) { + if let Some(NewWires::In(pins)) = &mut self.new_wires { + if let Some(idx) = pins.iter().position(|p| *p == pin) { + pins.swap_remove(idx); + self.dirty = true; + } + } + } + + pub fn remove_new_wire_out(&mut self, pin: OutPinId) { + if let Some(NewWires::Out(pins)) = &mut self.new_wires { + if let Some(idx) = pins.iter().position(|p| *p == pin) { + pins.swap_remove(idx); + self.dirty = true; + } + } + } + + pub fn has_new_wires(&self) -> bool { + self.new_wires.is_some() + } + + pub fn new_wires(&self) -> Option<&NewWires> { + self.new_wires.as_ref() + } + + pub fn take_wires(&mut self) -> Option { + self.dirty |= self.new_wires.is_some(); + self.new_wires.take() + } + + pub(crate) fn revert_take_wires(&mut self, wires: NewWires) { + self.new_wires = Some(wires); + } + + pub(crate) fn open_link_menu(&mut self) { + self.is_link_menu_open = true; + self.dirty = true; + } + + pub(crate) fn close_link_menu(&mut self) { + self.new_wires = None; + self.is_link_menu_open = false; + self.dirty = true; + } + + pub(crate) fn is_link_menu_open(&self) -> bool { + self.is_link_menu_open + } +} diff --git a/vendor/egui-snarl/src/ui/viewer.rs b/vendor/egui-snarl/src/ui/viewer.rs new file mode 100644 index 0000000..f7b747a --- /dev/null +++ b/vendor/egui-snarl/src/ui/viewer.rs @@ -0,0 +1,186 @@ +use egui::{Color32, Pos2, Style, Ui}; + +use crate::{InPin, NodeId, OutPin, Snarl}; + +use super::pin::{AnyPins, PinInfo}; + +/// SnarlViewer is a trait for viewing a Snarl. +/// +/// It can extract necessary data from the nodes and controls their +/// response to certain events. +pub trait SnarlViewer { + /// Returns title of the node. + fn title(&mut self, node: &T) -> String; + + /// Checks if node has something to show in body - between input and output pins. + fn has_body(&mut self, node: &T) -> bool { + let _ = node; + false + } + + /// Checks if node has something to show in footer - below pins and body. + fn has_footer(&mut self, node: &T) -> bool { + let _ = node; + false + } + + /// Checks if node has something to show in on-hover popup. + fn has_on_hover_popup(&mut self, node: &T) -> bool { + let _ = node; + false + } + + /// Renders the node's header. + fn show_header( + &mut self, + node: NodeId, + inputs: &[InPin], + outputs: &[OutPin], + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl, + ) { + let _ = (inputs, outputs, scale); + ui.label(self.title(&snarl[node])); + } + + /// Returns number of output pins of the node. + fn outputs(&mut self, node: &T) -> usize; + + /// Returns number of input pins of the node. + fn inputs(&mut self, node: &T) -> usize; + + /// Renders the node's input pin. + fn show_input(&mut self, pin: &InPin, ui: &mut Ui, scale: f32, snarl: &mut Snarl) + -> PinInfo; + + /// Renders the node's output pin. + fn show_output( + &mut self, + pin: &OutPin, + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl, + ) -> PinInfo; + + /// Renders the node's body. + fn show_body( + &mut self, + node: NodeId, + inputs: &[InPin], + outputs: &[OutPin], + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl, + ) { + let _ = (node, inputs, outputs, ui, scale, snarl); + } + + /// Renders the node's footer. + fn show_footer( + &mut self, + node: NodeId, + inputs: &[InPin], + outputs: &[OutPin], + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl, + ) { + let _ = (node, inputs, outputs, ui, scale, snarl); + } + + /// Renders the node's on-hover popup. + fn show_on_hover_popup( + &mut self, + node: NodeId, + inputs: &[InPin], + outputs: &[OutPin], + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl, + ) { + let _ = (node, inputs, outputs, ui, scale, snarl); + } + + /// Returns color of the node's input pin. + /// Called when pin in not visible. + fn input_color(&mut self, pin: &InPin, style: &Style, snarl: &mut Snarl) -> Color32; + + /// Returns color of the node's output pin. + /// Called when pin in not visible. + fn output_color(&mut self, pin: &OutPin, style: &Style, snarl: &mut Snarl) -> Color32; + + /// Show context menu for the snarl. + /// + /// This can be used to implement menu for adding new nodes. + fn graph_menu(&mut self, pos: Pos2, ui: &mut Ui, scale: f32, snarl: &mut Snarl) { + let _ = (pos, ui, scale, snarl); + } + + /// Show context menu for the snarl. This menu is opened when releasing a pin to empty + /// space. It can be used to implement menu for adding new node, and directly + /// connecting it to the released wire. + /// + /// + fn graph_menu_for_dropped_wire( + &mut self, + pos: Pos2, + ui: &mut Ui, + scale: f32, + src_pins: AnyPins, + snarl: &mut Snarl, + ) { + let _ = (pos, scale, src_pins, snarl); + + // Default implementation simply doesn't utilize wire drop. + ui.close_menu(); + } + + /// Show context menu for the snarl. + /// + /// This can be used to implement menu for adding new nodes. + fn node_menu( + &mut self, + node: NodeId, + inputs: &[InPin], + outputs: &[OutPin], + ui: &mut Ui, + scale: f32, + snarl: &mut Snarl, + ) { + let _ = (node, inputs, outputs, ui, scale, snarl); + } + + /// Asks the viewer to connect two pins. + /// + /// This is usually happens when user drags a wire from one node's output pin to another node's input pin or vice versa. + /// By default this method connects the pins and returns `Ok(())`. + #[inline] + fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl) { + snarl.connect(from.id, to.id); + } + + /// Asks the viewer to disconnect two pins. + #[inline] + fn disconnect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl) { + snarl.disconnect(from.id, to.id); + } + + /// Asks the viewer to disconnect all wires from the output pin. + /// + /// This is usually happens when right-clicking on an output pin. + /// By default this method disconnects the pins and returns `Ok(())`. + #[inline] + fn drop_outputs(&mut self, pin: &OutPin, snarl: &mut Snarl) { + snarl.drop_outputs(pin.id); + } + + /// Asks the viewer to disconnect all wires from the input pin. + /// + /// This is usually happens when right-clicking on an input pin. + /// By default this method disconnects the pins and returns `Ok(())`. + #[inline] + fn drop_inputs(&mut self, pin: &InPin, snarl: &mut Snarl) { + snarl.drop_inputs(pin.id); + } +} diff --git a/vendor/egui-snarl/src/ui/wire.rs b/vendor/egui-snarl/src/ui/wire.rs new file mode 100644 index 0000000..5ce72bd --- /dev/null +++ b/vendor/egui-snarl/src/ui/wire.rs @@ -0,0 +1,323 @@ +use egui::{epaint::PathShape, pos2, Color32, Pos2, Rect, Shape, Stroke, Ui}; + +/// Layer where wires are rendered. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))] +pub enum WireLayer { + /// Wires are rendered behind nodes. + /// This is default. + BehindNodes, + + /// Wires are rendered above nodes. + AboveNodes, +} + +/// Returns 6th degree bezier curve for the wire +fn wire_bezier( + mut frame_size: f32, + upscale: bool, + downscale: bool, + from: Pos2, + to: Pos2, +) -> [Pos2; 6] { + if upscale { + frame_size = frame_size.max((from - to).length() / 4.0); + } + if downscale { + frame_size = frame_size.min((from - to).length() / 4.0); + } + + let from_norm_x = frame_size; + let from_2 = pos2(from.x + from_norm_x, from.y); + let to_norm_x = -from_norm_x; + let to_2 = pos2(to.x + to_norm_x, to.y); + + let between = (from_2 - to_2).length(); + + if from_2.x <= to_2.x && between >= frame_size * 2.0 { + let middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size; + let middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size; + + [from, from_2, middle_1, middle_2, to_2, to] + } else if from_2.x <= to_2.x { + let t = + (between - (to_2.y - from_2.y).abs()) / (frame_size * 2.0 - (to_2.y - from_2.y).abs()); + + let mut middle_1 = from_2 + (to_2 - from_2).normalized() * frame_size; + let mut middle_2 = to_2 + (from_2 - to_2).normalized() * frame_size; + + if from_2.y >= to_2.y + frame_size { + let u = (from_2.y - to_2.y - frame_size) / frame_size; + + let t0_middle_1 = pos2(from_2.x + (1.0 - u) * frame_size, from_2.y - frame_size * u); + let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size); + + middle_1 = t0_middle_1.lerp(middle_1, t); + middle_2 = t0_middle_2.lerp(middle_2, t); + } else if from_2.y >= to_2.y { + let u = (from_2.y - to_2.y) / frame_size; + + let t0_middle_1 = pos2(from_2.x + u * frame_size, from_2.y + frame_size * (1.0 - u)); + let t0_middle_2 = pos2(to_2.x, to_2.y + frame_size); + + middle_1 = t0_middle_1.lerp(middle_1, t); + middle_2 = t0_middle_2.lerp(middle_2, t); + } else if to_2.y >= from_2.y + frame_size { + let u = (to_2.y - from_2.y - frame_size) / frame_size; + + let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size); + let t0_middle_2 = pos2(to_2.x - (1.0 - u) * frame_size, to_2.y - frame_size * u); + + middle_1 = t0_middle_1.lerp(middle_1, t); + middle_2 = t0_middle_2.lerp(middle_2, t); + } else if to_2.y >= from_2.y { + let u = (to_2.y - from_2.y) / frame_size; + + let t0_middle_1 = pos2(from_2.x, from_2.y + frame_size); + let t0_middle_2 = pos2(to_2.x - u * frame_size, to_2.y + frame_size * (1.0 - u)); + + middle_1 = t0_middle_1.lerp(middle_1, t); + middle_2 = t0_middle_2.lerp(middle_2, t); + } else { + unreachable!(); + } + + [from, from_2, middle_1, middle_2, to_2, to] + } else if from_2.y >= to_2.y + frame_size * 2.0 { + let middle_1 = pos2(from_2.x, from_2.y - frame_size); + let middle_2 = pos2(to_2.x, to_2.y + frame_size); + + [from, from_2, middle_1, middle_2, to_2, to] + } else if from_2.y >= to_2.y + frame_size { + let t = (from_2.y - to_2.y - frame_size) / frame_size; + + let middle_1 = pos2(from_2.x + (1.0 - t) * frame_size, from_2.y - frame_size * t); + let middle_2 = pos2(to_2.x, to_2.y + frame_size); + + [from, from_2, middle_1, middle_2, to_2, to] + } else if from_2.y >= to_2.y { + let t = (from_2.y - to_2.y) / frame_size; + + let middle_1 = pos2(from_2.x + t * frame_size, from_2.y + frame_size * (1.0 - t)); + let middle_2 = pos2(to_2.x, to_2.y + frame_size); + + [from, from_2, middle_1, middle_2, to_2, to] + } else if to_2.y >= from_2.y + frame_size * 2.0 { + let middle_1 = pos2(from_2.x, from_2.y + frame_size); + let middle_2 = pos2(to_2.x, to_2.y - frame_size); + + [from, from_2, middle_1, middle_2, to_2, to] + } else if to_2.y >= from_2.y + frame_size { + let t = (to_2.y - from_2.y - frame_size) / frame_size; + + let middle_1 = pos2(from_2.x, from_2.y + frame_size); + let middle_2 = pos2(to_2.x - (1.0 - t) * frame_size, to_2.y - frame_size * t); + + [from, from_2, middle_1, middle_2, to_2, to] + } else if to_2.y >= from_2.y { + let t = (to_2.y - from_2.y) / frame_size; + + let middle_1 = pos2(from_2.x, from_2.y + frame_size); + let middle_2 = pos2(to_2.x - t * frame_size, to_2.y + frame_size * (1.0 - t)); + + [from, from_2, middle_1, middle_2, to_2, to] + } else { + unreachable!(); + } +} + +#[allow(clippy::too_many_arguments)] +pub fn draw_wire( + ui: &mut Ui, + shapes: &mut Vec, + frame_size: f32, + upscale: bool, + downscale: bool, + from: Pos2, + to: Pos2, + stroke: Stroke, +) { + let points = wire_bezier(frame_size, upscale, downscale, from, to); + + let bb = Rect::from_points(&points); + if ui.is_rect_visible(bb) { + draw_bezier(shapes, &points, stroke); + } +} + +pub fn hit_wire( + pos: Pos2, + frame_size: f32, + upscale: bool, + downscale: bool, + from: Pos2, + to: Pos2, + threshold: f32, +) -> bool { + let points = wire_bezier(frame_size, upscale, downscale, from, to); + hit_bezier(pos, &points, threshold) +} + +fn bezier_reference_size(points: &[Pos2; 6]) -> f32 { + let [p0, p1, p2, p3, p4, p5] = *points; + + (p1 - p0).length() + + (p2 - p1).length() + + (p3 - p2).length() + + (p4 - p3).length() + + (p5 - p4).length() +} + +const MAX_BEZIER_SAMPLES: usize = 100; + +fn bezier_samples_number(points: &[Pos2; 6], threshold: f32) -> usize { + let reference_size = bezier_reference_size(points); + + #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_possible_truncation)] + ((reference_size / threshold).ceil().max(0.0) as usize).min(MAX_BEZIER_SAMPLES) +} + +fn draw_bezier(shapes: &mut Vec, points: &[Pos2; 6], mut stroke: Stroke) { + if stroke.width < 1.0 { + stroke.color = stroke.color.gamma_multiply(stroke.width); + stroke.width = 1.0; + } + + let samples = bezier_samples_number(points, stroke.width); + + let mut path = Vec::new(); + + for i in 0..samples { + #[allow(clippy::cast_precision_loss)] + let t = i as f32 / (samples - 1) as f32; + path.push(sample_bezier(points, t)); + } + + let shape = Shape::Path(PathShape { + points: path, + closed: false, + fill: Color32::TRANSPARENT, + stroke, + }); + + shapes.push(shape); +} + +#[allow(clippy::let_and_return)] +fn sample_bezier(points: &[Pos2; 6], t: f32) -> Pos2 { + let [p0, p1, p2, p3, p4, p5] = *points; + + let p0_0 = p0; + let p1_0 = p1; + let p2_0 = p2; + let p3_0 = p3; + let p4_0 = p4; + let p5_0 = p5; + + let p0_1 = p0_0.lerp(p1_0, t); + let p1_1 = p1_0.lerp(p2_0, t); + let p2_1 = p2_0.lerp(p3_0, t); + let p3_1 = p3_0.lerp(p4_0, t); + let p4_1 = p4_0.lerp(p5_0, t); + + let p0_2 = p0_1.lerp(p1_1, t); + let p1_2 = p1_1.lerp(p2_1, t); + let p2_2 = p2_1.lerp(p3_1, t); + let p3_2 = p3_1.lerp(p4_1, t); + + let p0_3 = p0_2.lerp(p1_2, t); + let p1_3 = p1_2.lerp(p2_2, t); + let p2_3 = p2_2.lerp(p3_2, t); + + let p0_4 = p0_3.lerp(p1_3, t); + let p1_4 = p1_3.lerp(p2_3, t); + + let p0_5 = p0_4.lerp(p1_4, t); + + p0_5 +} + +fn split_bezier(points: &[Pos2; 6], t: f32) -> [[Pos2; 6]; 2] { + let [p0, p1, p2, p3, p4, p5] = *points; + + let p0_0 = p0; + let p1_0 = p1; + let p2_0 = p2; + let p3_0 = p3; + let p4_0 = p4; + let p5_0 = p5; + + let p0_1 = p0_0.lerp(p1_0, t); + let p1_1 = p1_0.lerp(p2_0, t); + let p2_1 = p2_0.lerp(p3_0, t); + let p3_1 = p3_0.lerp(p4_0, t); + let p4_1 = p4_0.lerp(p5_0, t); + + let p0_2 = p0_1.lerp(p1_1, t); + let p1_2 = p1_1.lerp(p2_1, t); + let p2_2 = p2_1.lerp(p3_1, t); + let p3_2 = p3_1.lerp(p4_1, t); + + let p0_3 = p0_2.lerp(p1_2, t); + let p1_3 = p1_2.lerp(p2_2, t); + let p2_3 = p2_2.lerp(p3_2, t); + + let p0_4 = p0_3.lerp(p1_3, t); + let p1_4 = p1_3.lerp(p2_3, t); + + let p0_5 = p0_4.lerp(p1_4, t); + + [ + [p0_0, p0_1, p0_2, p0_3, p0_4, p0_5], + [p0_5, p1_4, p2_3, p3_2, p4_1, p5_0], + ] +} + +fn hit_bezier(pos: Pos2, points: &[Pos2; 6], threshold: f32) -> bool { + let aabb = Rect::from_points(points); + + if pos.x + threshold < aabb.left() { + return false; + } + if pos.x - threshold > aabb.right() { + return false; + } + if pos.y + threshold < aabb.top() { + return false; + } + if pos.y - threshold > aabb.bottom() { + return false; + } + + let samples = bezier_samples_number(points, threshold); + if samples > 16 { + let [points1, points2] = split_bezier(points, 0.5); + + return hit_bezier(pos, &points1, threshold) || hit_bezier(pos, &points2, threshold); + } + + for i in 0..samples { + #[allow(clippy::cast_precision_loss)] + let t = i as f32 / (samples - 1) as f32; + let p = sample_bezier(points, t); + if (p - pos).length() < threshold { + return true; + } + } + + false +} + +pub fn mix_colors(a: Color32, b: Color32) -> Color32 { + let [or, og, ob, oa] = a.to_array(); + let [ir, ig, ib, ia] = b.to_array(); + + Color32::from_rgba_premultiplied( + or / 2 + ir / 2, + og / 2 + ig / 2, + ob / 2 + ib / 2, + oa / 2 + ia / 2, + ) +} diff --git a/vendor/egui-snarl/src/ui/zoom.rs b/vendor/egui-snarl/src/ui/zoom.rs new file mode 100644 index 0000000..1914928 --- /dev/null +++ b/vendor/egui-snarl/src/ui/zoom.rs @@ -0,0 +1,192 @@ +use egui::{ + epaint::Shadow, + style::{Interaction, ScrollStyle, Spacing, WidgetVisuals, Widgets}, + FontId, Frame, Margin, Rounding, Stroke, Style, Vec2, Visuals, +}; + +pub trait Zoom { + fn zoomed(mut self, zoom: f32) -> Self + where + Self: Copy, + { + self.zoom(zoom); + self + } + + fn zoom(&mut self, zoom: f32); +} + +impl Zoom for f32 { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + *self *= zoom; + } +} + +impl Zoom for Vec2 { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + *self *= zoom; + } +} + +impl Zoom for Rounding { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.nw.zoom(zoom); + self.ne.zoom(zoom); + self.se.zoom(zoom); + self.sw.zoom(zoom); + } +} + +impl Zoom for Margin { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.left.zoom(zoom); + self.right.zoom(zoom); + self.top.zoom(zoom); + self.bottom.zoom(zoom); + } +} + +impl Zoom for Shadow { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.spread = zoom; + // self.extrusion.zoom(zoom); + } +} + +impl Zoom for Stroke { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.width *= zoom; + if self.width < 1.0 { + self.color.gamma_multiply(self.width); + self.width = 1.0; + } + } +} + +impl Zoom for WidgetVisuals { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.bg_stroke.zoom(zoom); + self.rounding.zoom(zoom); + self.fg_stroke.zoom(zoom); + self.expansion.zoom(zoom); + } +} + +impl Zoom for Interaction { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.resize_grab_radius_corner.zoom(zoom); + self.resize_grab_radius_side.zoom(zoom); + } +} + +impl Zoom for Widgets { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.noninteractive.zoom(zoom); + self.inactive.zoom(zoom); + self.hovered.zoom(zoom); + self.active.zoom(zoom); + self.open.zoom(zoom); + } +} + +impl Zoom for Visuals { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.clip_rect_margin.zoom(zoom); + self.menu_rounding.zoom(zoom); + self.popup_shadow.zoom(zoom); + self.resize_corner_size.zoom(zoom); + self.selection.stroke.zoom(zoom); + self.text_cursor.zoom(zoom); + self.widgets.zoom(zoom); + self.window_rounding.zoom(zoom); + self.window_shadow.zoom(zoom); + self.window_stroke.zoom(zoom); + } +} + +impl Zoom for ScrollStyle { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.bar_inner_margin.zoom(zoom); + self.bar_outer_margin.zoom(zoom); + self.bar_width.zoom(zoom); + self.floating_allocated_width.zoom(zoom); + self.floating_width.zoom(zoom); + self.handle_min_length.zoom(zoom); + } +} + +impl Zoom for Spacing { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.button_padding.zoom(zoom); + self.combo_height.zoom(zoom); + self.combo_width.zoom(zoom); + self.icon_spacing.zoom(zoom); + self.icon_width.zoom(zoom); + self.icon_width_inner.zoom(zoom); + self.indent.zoom(zoom); + self.interact_size.zoom(zoom); + self.item_spacing.zoom(zoom); + self.menu_margin.zoom(zoom); + self.scroll.zoom(zoom); + self.slider_width.zoom(zoom); + self.text_edit_width.zoom(zoom); + self.tooltip_width.zoom(zoom); + self.window_margin.zoom(zoom); + } +} + +impl Zoom for FontId { + fn zoom(&mut self, zoom: f32) { + self.size.zoom(zoom); + } +} + +impl Zoom for Style { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + if let Some(font_id) = &mut self.override_font_id { + font_id.zoom(zoom); + } + for font_id in self.text_styles.values_mut() { + font_id.zoom(zoom); + } + self.interaction.zoom(zoom); + self.spacing.zoom(zoom); + self.visuals.zoom(zoom); + } +} + +impl Zoom for Option +where + T: Zoom, +{ + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + if let Some(value) = self { + value.zoom(zoom) + } + } +} + +impl Zoom for Frame { + #[inline(always)] + fn zoom(&mut self, zoom: f32) { + self.inner_margin.zoom(zoom); + self.outer_margin.zoom(zoom); + self.rounding.zoom(zoom); + self.shadow.zoom(zoom); + self.stroke.zoom(zoom); + } +}