commit fc707835f576c6ac66d9d468d6a96f6f8529eec4 Author: Jonathan Johnson Date: Wed Oct 18 08:22:41 2023 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a62e7c3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2259 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1061f3ff92c2f65800df1f12fc7b4ff44ee14783104187dd04dfee6f11b0fd2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "alot" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f1063fbee2ac6d371ffcb55cb443f4d5b469f1b8eace60042878242bb534169" + +[[package]] +name = "android-activity" +version = "0.5.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934936a9ad4dc79563cd6644fcef68dc328f105d927679454de39ad03ca1cfe8" +dependencies = [ + "android-properties", + "bitflags 2.4.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "appit" +version = "0.1.0" +source = "git+https://github.com/khonsulabs/appit#0503b3d4668ec9e153952e509462216a2c5d85a9" +dependencies = [ + "raw-window-handle", + "winit", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5f312b0a56c5cdf967c0aeb67f6289603354951683bc97ddc595ab974ba9aa" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[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 = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd7cf50912cddc06dc5ea7c08c5e81c1b2c842a70d19def1848d54c586fed92" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys", + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "calloop" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +dependencies = [ + "bitflags 1.3.2", + "log", + "nix 0.25.1", + "slotmap", + "thiserror", + "vec_map", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "com-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "cosmic-text" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b68966c2543609f8d92f9d33ac3b719b2a67529b0c6c0b3e025637b477eef9" +dependencies = [ + "aliasable", + "fontdb", + "libm", + "log", + "rangemap", + "rustybuzz", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cursor-icon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740bb192a8e2d1350119916954f4409ee7f62f149b536911eeb78ba5a20526bf" + +[[package]] +name = "d3d12" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16e44ab292b1dddfdaf7be62cfd8877df52f2f3fde5858d95bab606be259f20" +dependencies = [ + "bitflags 2.4.0", + "libloading 0.8.1", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.1", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "euclid" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787" +dependencies = [ + "num-traits", +] + +[[package]] +name = "figures" +version = "0.1.0" +dependencies = [ + "bytemuck", + "euclid", + "wgpu", + "winit", +] + +[[package]] +name = "float_next_after" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc612c5837986b7104a87a0df74a5460931f1c5274be12f8d0f40aa2f30d632" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fontdb" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8d8cbea8f21307d7e84bca254772981296f058a1d36b461bf4d83a7499fc9e" +dependencies = [ + "log", + "memmap2 0.6.2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "gethostname" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "glow" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gooey" +version = "0.1.0" +dependencies = [ + "alot", + "kludgine", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.4.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.4.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce95f9e2e11c2c6fadfce42b5af60005db06576f231f5c92550fdded43c423e8" +dependencies = [ + "backtrace", + "log", + "thiserror", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.4.0", + "gpu-descriptor-types", + "hashbrown 0.14.1", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.4.0", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hassle-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" +dependencies = [ + "bitflags 1.3.2", + "com-rs", + "libc", + "libloading 0.7.4", + "thiserror", + "widestring", + "winapi", +] + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2", + "dispatch", + "objc2", +] + +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", +] + +[[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]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3" +dependencies = [ + "libc", + "libloading 0.7.4", + "pkg-config", +] + +[[package]] +name = "kludgine" +version = "0.1.0" +dependencies = [ + "ahash", + "alot", + "appit", + "bytemuck", + "cosmic-text", + "figures", + "image", + "lyon_tessellation", + "pollster", + "shelf-packer", + "smallvec", + "wgpu", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lyon_geom" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74df1ff0a0147282eb10699537a03baa7d31972b58984a1d44ce0624043fe8ad" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca507745ba7ccbc76e5c44e7b63b1a29d2b0d6126f375806a5bbaf657c7d6c45" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d2124218d5428149f9e09520b9acc024334a607e671f032d06567b61008977c" +dependencies = [ + "float_next_after", + "lyon_path", + "thiserror", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memmap2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "623b5e6cefd76e58f774bd3cc0c6f5c7615c58c03a97815245a25c3c9bdee318" +dependencies = [ + "bitflags 2.4.0", + "block", + "core-graphics-types", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "naga" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ceaaa4eedaece7e4ec08c55c640ba03dbb73fb812a6570a59bcf1930d0f70e" +dependencies = [ + "bit-set", + "bitflags 2.4.0", + "codespan-reporting", + "hexf-parse", + "indexmap 1.9.3", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", +] + +[[package]] +name = "ndk" +version = "0.8.0-beta.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f58741784b0f6ac12311c3f6fbdb3c766a992f8914d035c77a07b5fd2940dc" +dependencies = [ + "bitflags 2.4.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0-beta.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff38603775cba10d0f1141fab1b167df73662a0636a4b631c187fe11fb624042" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[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 = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845" + +[[package]] +name = "objc2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-encode" +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 = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "orbclient" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f" +dependencies = [ + "redox_syscall", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[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", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89dff0959d98c9758c88826cc002e2c3d0b9dfac4139711d1f30de442f1139b" + +[[package]] +name = "quick-xml" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "range-alloc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" + +[[package]] +name = "rangemap" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "977b1e897f9d764566891689e642653e5ed90c6895106acd005eb4c1d0203991" + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "renderdoc-sys" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" + +[[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.38.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustybuzz" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82eea22c8f56965eeaf3a209b3d24508256c7b920fb3b6211b8ba0f7c0583250" +dependencies = [ + "bitflags 1.3.2", + "bytemuck", + "libm", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b1ea0ce9e629064237c642f344cc2d9d8028e9b8367d894d2aa7f9243872176" +dependencies = [ + "ab_glyph", + "log", + "memmap2 0.5.10", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shelf-packer" +version = "0.1.0" +source = "git+https://github.com/khonsulabs/shelf-packer#06246cb4f5ccd473c1d8ae49c8c07f2f61798df4" +dependencies = [ + "figures", +] + +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "smithay-client-toolkit" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1476c3d89bb67079264b88aaf4f14358353318397e083b7c4e8c14517f55de7" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2 0.5.10", + "nix 0.26.4", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", +] + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.2.0+1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" +dependencies = [ + "bitflags 1.3.2", + "num-traits", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "swash" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7c73c813353c347272919aa1af2885068b05e625e5532b43049e4f641ae77f" +dependencies = [ + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sys-locale" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +dependencies = [ + "libc", +] + +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-skia" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b72a92a05db376db09fe6d50b7948d106011761c05a6a45e23e17ee9b556222" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac3865b9708fc7e1961a65c3a4fa55e984272f33092d3c859929f887fceb647" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.0.2", + "toml_datetime", + "winnow", +] + +[[package]] +name = "ttf-parser" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +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 = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wayland-backend" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" +dependencies = [ + "cc", + "downcast-rs", + "io-lifetimes", + "nix 0.26.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489c9654770f674fc7e266b3c579f4053d7551df0ceb392f153adb1f9ed06ac8" +dependencies = [ + "bitflags 1.3.2", + "calloop", + "nix 0.26.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-cursor" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0c3a0d5b4b688b07b0442362d3ed6bf04724fcc16cd69ab6285b90dbc487aa" +dependencies = [ + "nix 0.26.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +dependencies = [ + "dlib", + "lazy_static", + "log", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8208e3fdbc243c8fd30805721869242a7f6de3e2e9f3b057652ab36e52ae1e87" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed547920565c56c7a29afb4538ac5ae5048865a5d2f05bff3ad4fbeb921a9a2c" +dependencies = [ + "arrayvec", + "cfg-if", + "js-sys", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8a44dd301a30ceeed3c27d8c0090433d3da04d7b2a4042738095a424d12ae7" +dependencies = [ + "arrayvec", + "bit-vec", + "bitflags 2.4.0", + "codespan-reporting", + "log", + "naga", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a80bf0e3c77399bb52850cb0830af9bad073d5cfcb9dd8253bef8125c42db17" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.4.0", + "block", + "core-graphics-types", + "d3d12", + "glow", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.1", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee64d7398d0c2f9ca48922c902ef69c42d000c759f3db41e355f4a570b052b67" +dependencies = [ + "bitflags 2.4.0", + "js-sys", + "web-sys", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-wsapoll" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winit" +version = "0.29.1-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab977231134a3123c5382f0358b728118e70c216ec99017aa24e9eed35d5e3e1" +dependencies = [ + "android-activity", + "atomic-waker", + "bitflags 2.4.0", + "bytemuck", + "calloop", + "cfg_aliases", + "core-foundation", + "core-graphics", + "cursor-icon", + "fnv", + "icrate", + "js-sys", + "libc", + "log", + "memmap2 0.5.10", + "ndk", + "ndk-sys", + "objc2", + "once_cell", + "orbclient", + "percent-encoding", + "raw-window-handle", + "redox_syscall", + "rustix", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "web-sys", + "web-time", + "windows-sys 0.48.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.7.4", + "nix 0.26.4", + "once_cell", + "winapi", + "winapi-wsapoll", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" +dependencies = [ + "nix 0.26.4", +] + +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" +dependencies = [ + "bitflags 2.4.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2af0881 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "gooey" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +kludgine = { git = "https://github.com/khonsulabs/kludgine", features = [ + "app", +] } +alot = "0.3" +# appit = { git = "https://github.com/khonsulabs/appit" } + +[patch."https://github.com/khonsulabs/kludgine"] +kludgine = { path = "../kludgine2" } +[patch."https://github.com/khonsulabs/figures"] +figures = { path = "../figures" } diff --git a/examples/button.rs b/examples/button.rs new file mode 100644 index 0000000..6e00657 --- /dev/null +++ b/examples/button.rs @@ -0,0 +1,11 @@ +use gooey::dynamic::Dynamic; +use gooey::widget::Widget; +use gooey::widgets::Button; +use gooey::EventLoopError; + +fn main() -> Result<(), EventLoopError> { + let count = Dynamic::new(0_usize); + Button::new(count.map_each(ToString::to_string)) + .on_click(count.with_clone(|count| move |_| count.set(count.get() + 1))) + .run() +} diff --git a/examples/canvas.rs b/examples/canvas.rs new file mode 100644 index 0000000..82a4131 --- /dev/null +++ b/examples/canvas.rs @@ -0,0 +1,35 @@ +use gooey::widget::Widget; +use gooey::widgets::Canvas; +use kludgine::figures::units::Px; +use kludgine::figures::{Angle, IntoSigned, Point, Rect, Size}; +use kludgine::shapes::Shape; +use kludgine::Color; + +fn main() -> gooey::Result<()> { + let mut angle = Angle::degrees(0); + Canvas::new(move |graphics, _window| { + angle += Angle::degrees(1); + + let center = Point::from(graphics.size()).into_signed() / 2; + graphics.draw_text( + "Canvas exposes the full power of Kludgine", + Color::WHITE, + kludgine::text::TextOrigin::Center, + center - Point::new(Px(0), Px(100)), + None, + None, + None, + ); + graphics.draw_shape( + &Shape::filled_rect( + Rect::new(Point::new(Px(-50), Px(-50)), Size::new(Px(100), Px(100))), + Color::RED, + ), + center, + Some(angle), + None, + ) + }) + .target_fps(60) + .run() +} diff --git a/examples/counter.rs b/examples/counter.rs new file mode 100644 index 0000000..2700d73 --- /dev/null +++ b/examples/counter.rs @@ -0,0 +1,28 @@ +use std::string::ToString; + +use gooey::children::Children; +use gooey::dynamic::Dynamic; +use gooey::widget::Widget; +use gooey::widgets::array::Array; +use gooey::widgets::{Button, Label}; +use gooey::EventLoopError; + +fn main() -> Result<(), EventLoopError> { + let counter = Dynamic::new(0i32); + let label = counter.map_each(ToString::to_string); + Array::rows( + Children::new() + .with_widget(Label::new(label)) + .with_widget(Button::new("+").on_click(counter.with_clone(|counter| { + move |_| { + counter.set(counter.get() + 1); + } + }))) + .with_widget(Button::new("-").on_click(counter.with_clone(|counter| { + move |_| { + counter.set(counter.get() - 1); + } + }))), + ) + .run() +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..db443f8 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,6 @@ +unstable_features = true +use_field_init_shorthand = true +imports_granularity = "Module" +group_imports = "StdExternalCrate" +format_code_in_doc_comments = true +reorder_impl_items = true diff --git a/src/children.rs b/src/children.rs new file mode 100644 index 0000000..addb602 --- /dev/null +++ b/src/children.rs @@ -0,0 +1,69 @@ +use std::ops::{Index, IndexMut}; + +use alot::OrderedLots; + +use crate::widget::{BoxedWidget, Widget}; + +#[derive(Debug, Default)] +#[must_use] +pub struct Children { + ordered: OrderedLots, +} + +impl Children { + pub const fn new() -> Self { + Self { + ordered: OrderedLots::new(), + } + } + + pub fn with_widget(mut self, widget: W) -> Self + where + W: Widget, + { + self.ordered.push(BoxedWidget::new(widget)); + self + } + + #[must_use] + pub fn len(&self) -> usize { + self.ordered.len() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.ordered.is_empty() + } + + #[must_use] + pub fn get(&self, index: usize) -> Option<&BoxedWidget> { + self.ordered.get_by_index(index) + } + + #[must_use] + pub fn iter(&self) -> alot::ordered::Iter<'_, BoxedWidget> { + self.into_iter() + } +} + +impl Index for Children { + type Output = BoxedWidget; + + fn index(&self, index: usize) -> &Self::Output { + &self.ordered[index] + } +} +impl IndexMut for Children { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.ordered[index] + } +} + +impl<'a> IntoIterator for &'a Children { + type IntoIter = alot::ordered::Iter<'a, BoxedWidget>; + type Item = &'a BoxedWidget; + + fn into_iter(self) -> Self::IntoIter { + self.ordered.iter() + } +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..7f7930f --- /dev/null +++ b/src/context.rs @@ -0,0 +1,313 @@ +use std::ops::{Deref, DerefMut}; + +use kludgine::app::winit::event::{DeviceId, MouseButton}; +use kludgine::figures::units::{Px, UPx}; +use kludgine::figures::{IntoSigned, Point, Rect, Size}; + +use crate::dynamic::Dynamic; +use crate::graphics::Graphics; +use crate::tree::ManagedWidget; +use crate::widget::{BoxedWidget, EventHandling}; +use crate::window::RunningWindow; +use crate::ConstraintLimit; + +pub struct Context<'context, 'window> { + current_node: &'context ManagedWidget, + window: &'context mut RunningWindow<'window>, + pending_state: PendingState<'context>, +} + +impl<'context, 'window> Context<'context, 'window> { + pub fn new( + current_node: &'context ManagedWidget, + window: &'context mut RunningWindow<'window>, + ) -> Self { + Self { + current_node, + window, + pending_state: PendingState::Owned(PendingWidgetState { + focus: current_node + .tree + .focused_widget() + .map(|id| current_node.tree.widget(id)), + active: current_node + .tree + .active_widget() + .map(|id| current_node.tree.widget(id)), + }), + } + } + + pub fn for_other<'child>( + &'child mut self, + widget: &'child ManagedWidget, + ) -> Context<'child, 'window> { + Context { + current_node: widget, + window: &mut *self.window, + pending_state: self.pending_state.borrowed(), + } + } + + pub(crate) fn parent(&self) -> Option { + self.current_node.parent() + } + + pub fn redraw_when_changed(&self, value: &Dynamic) { + value.redraw_when_changed(self.window.handle()); + } + + pub fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>) { + // TODO this should not use clip_rect, because it forces UPx, and once + // we have scrolling, we can have negative offsets of rectangles where + // it's clipped partially. + self.current_node + .note_rendered_rect(graphics.clip_rect().into_signed()); + self.current_node.lock().redraw(graphics, self); + } + + pub fn measure( + &mut self, + available_space: Size, + graphics: &mut Graphics<'_, '_, '_>, + ) -> Size { + self.current_node + .lock() + .measure(available_space, graphics, self) + } + + pub fn hit_test(&mut self, location: Point) -> bool { + self.current_node.lock().hit_test(location, self) + } + + pub fn mouse_down( + &mut self, + location: Point, + device_id: DeviceId, + button: MouseButton, + ) -> EventHandling { + self.current_node + .lock() + .mouse_down(location, device_id, button, self) + } + + pub fn mouse_drag(&mut self, location: Point, device_id: DeviceId, button: MouseButton) { + self.current_node + .lock() + .mouse_drag(location, device_id, button, self); + } + + pub fn mouse_up( + &mut self, + location: Option>, + device_id: DeviceId, + button: MouseButton, + ) { + self.current_node + .lock() + .mouse_up(location, device_id, button, self); + } + + #[must_use] + pub fn push_child(&self, child: BoxedWidget) -> ManagedWidget { + self.current_node + .tree + .push_boxed(child, Some(self.current_node)) + } + + pub fn remove_child(&self, child: ManagedWidget) { + self.current_node + .tree + .remove_child(child, self.current_node); + } + + #[must_use] + pub fn last_rendered_at(&self) -> Option> { + self.current_node.last_rendered_at() + } + + pub(crate) fn hover(&mut self, location: Point) { + let newly_hovered = match self.current_node.tree.hover(Some(self.current_node)) { + Ok(old_hover) => { + if let Some(old_hover) = old_hover { + let mut old_hover_context = self.for_other(&old_hover); + old_hover.lock().unhover(&mut old_hover_context); + } + true + } + Err(_) => false, + }; + if newly_hovered { + self.current_node.lock().hover(location, self); + } + } + + pub(crate) fn clear_hover(&mut self) { + if let Ok(Some(old_hover)) = self.current_node.tree.hover(None) { + let mut old_hover_context = self.for_other(&old_hover); + old_hover.lock().unhover(&mut old_hover_context); + } + } + + pub fn focus(&mut self) { + self.pending_state.focus = Some(self.current_node.clone()); + } + + pub(crate) fn clear_focus(&mut self) { + self.pending_state.focus = None; + } + + pub fn blur(&mut self) -> bool { + if self.focused() { + self.clear_focus(); + true + } else { + false + } + } + + pub fn activate(&mut self) -> bool { + if self + .pending_state + .active + .as_ref() + .map_or(true, |active| active != self.current_node) + { + self.pending_state.active = Some(self.current_node.clone()); + true + } else { + false + } + } + + pub fn deactivate(&mut self) -> bool { + if self.active() { + self.clear_active(); + true + } else { + false + } + } + + pub(crate) fn clear_active(&mut self) { + self.pending_state.active = None; + } + + #[must_use] + pub fn active(&self) -> bool { + self.pending_state.active.as_ref() == Some(self.current_node) + } + + #[must_use] + pub fn hovered(&self) -> bool { + self.current_node.hovered() + } + + #[must_use] + pub fn focused(&self) -> bool { + self.pending_state.focus.as_ref() == Some(self.current_node) + } + + fn apply_pending_state(&mut self) { + let active = self.pending_state.active.take(); + if self.current_node.tree.active_widget() != active.as_ref().map(|active| active.id) { + let new = match self.current_node.tree.activate(active.as_ref()) { + Ok(old) => { + if let Some(old) = old { + let mut old_context = self.for_other(&old); + old.lock().deactivate(&mut old_context); + } + true + } + Err(_) => false, + }; + if new { + if let Some(active) = active { + active.lock().activate(self); + } + } + } + + let focus = self.pending_state.focus.take(); + if self.current_node.tree.focused_widget() != focus.as_ref().map(|focus| focus.id) { + let new = match self.current_node.tree.focus(focus.as_ref()) { + Ok(old) => { + if let Some(old) = old { + let mut old_context = self.for_other(&old); + old.lock().blur(&mut old_context); + } + true + } + Err(_) => false, + }; + if new { + if let Some(focus) = focus { + focus.lock().focus(self); + } + } + } + } + + #[must_use] + pub const fn widget(&self) -> &ManagedWidget { + self.current_node + } +} + +impl Drop for Context<'_, '_> { + fn drop(&mut self) { + if matches!(self.pending_state, PendingState::Owned(_)) { + self.apply_pending_state(); + } + } +} + +impl<'window> Deref for Context<'_, 'window> { + type Target = RunningWindow<'window>; + + fn deref(&self) -> &Self::Target { + self.window + } +} +impl<'window> DerefMut for Context<'_, 'window> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.window + } +} + +enum PendingState<'a> { + Borrowed(&'a mut PendingWidgetState), + Owned(PendingWidgetState), +} + +#[derive(Default)] +struct PendingWidgetState { + focus: Option, + active: Option, +} + +impl PendingState<'_> { + pub fn borrowed(&mut self) -> PendingState<'_> { + PendingState::Borrowed(self) + } +} + +impl Deref for PendingState<'_> { + type Target = PendingWidgetState; + + fn deref(&self) -> &Self::Target { + match self { + PendingState::Borrowed(state) => state, + PendingState::Owned(state) => state, + } + } +} + +impl DerefMut for PendingState<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + PendingState::Borrowed(state) => state, + PendingState::Owned(state) => state, + } + } +} diff --git a/src/dynamic.rs b/src/dynamic.rs new file mode 100644 index 0000000..68fb8f7 --- /dev/null +++ b/src/dynamic.rs @@ -0,0 +1,307 @@ +use std::fmt::Debug; +use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError}; + +use kludgine::app::WindowHandle; + +use crate::window::sealed::WindowCommand; + +#[derive(Debug)] +pub struct Dynamic(Arc>); + +impl Dynamic { + pub fn new(value: T) -> Self { + Self(Arc::new(DynamicData { + state: Mutex::new(State { + wrapped: GenerationalValue { + value, + generation: 0, + }, + callbacks: Vec::new(), + windows: Vec::new(), + readers: 0, + }), + sync: Condvar::new(), + })) + } + + pub fn map_ref(&self, map: impl FnOnce(&T) -> R) -> R { + let state = self.state(); + map(&state.wrapped.value) + } + + pub fn map_mut(&self, map: impl FnOnce(&mut T) -> R) -> R { + let mut state = self.state(); + state.wrapped.generation = state.wrapped.generation.wrapping_add(1); + let result = map(&mut state.wrapped.value); + drop(state); + self.0.sync.notify_all(); + result + } + + pub fn for_each(&self, mut map: F) + where + F: for<'a> FnMut(&'a T) + Send + 'static, + { + self.0.for_each(move |gen| map(&gen.value)); + } + + pub fn map_each(&self, mut map: F) -> Dynamic + where + F: for<'a> FnMut(&'a T) -> R + Send + 'static, + R: Send + 'static, + { + self.0.map_each(move |gen| map(&gen.value)) + } + + pub fn with_clone(&self, with_clone: impl FnOnce(Self) -> R) -> R { + with_clone(self.clone()) + } + + pub fn redraw_when_changed(&self, window: WindowHandle) { + self.0.redraw_when_changed(window); + } + + #[must_use] + pub fn get(&self) -> T + where + T: Clone, + { + self.0.get().value + } + + #[must_use] + pub fn replace(&self, new_value: T) -> T { + self.0.replace(new_value).value + } + + pub fn set(&self, new_value: T) { + let _old = self.replace(new_value); + } + + #[must_use] + pub fn create_ref_reader(&self) -> DynamicRefReader { + self.state().readers += 1; + DynamicRefReader { + source: self.0.clone(), + read_generation: self.0.state().wrapped.generation, + } + } + + fn state(&self) -> MutexGuard<'_, State> { + self.0.state() + } + + #[must_use] + pub fn generation(&self) -> usize { + self.state().wrapped.generation + } +} + +impl Clone for Dynamic { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Drop for Dynamic { + fn drop(&mut self) { + let state = self.state(); + if state.readers == 0 { + drop(state); + self.0.sync.notify_all(); + } + } +} + +impl From> for DynamicRefReader { + fn from(value: Dynamic) -> Self { + value.create_ref_reader() + } +} + +#[derive(Debug)] +struct DynamicData { + state: Mutex>, + sync: Condvar, +} + +impl DynamicData { + fn state(&self) -> MutexGuard<'_, State> { + self.state + .lock() + .map_or_else(PoisonError::into_inner, |g| g) + } + + pub fn redraw_when_changed(&self, window: WindowHandle) { + let mut state = self.state(); + state.windows.push(window); + } + + #[must_use] + pub fn get(&self) -> GenerationalValue + where + T: Clone, + { + self.state().wrapped.clone() + } + + #[must_use] + pub fn replace(&self, new_value: T) -> GenerationalValue { + let mut state = self.state(); + let old = { + let state = &mut *state; + let generation = state.wrapped.generation.wrapping_add(1); + let old = std::mem::replace( + &mut state.wrapped, + GenerationalValue { + value: new_value, + generation, + }, + ); + + for callback in &mut state.callbacks { + callback.update(&state.wrapped); + } + for window in state.windows.drain(..) { + let _result = window.send(WindowCommand::Redraw); + } + old + }; + drop(state); + + self.sync.notify_all(); + + old + } + + pub fn for_each(&self, map: F) + where + F: for<'a> FnMut(&'a GenerationalValue) + Send + 'static, + { + let mut state = self.state(); + state.callbacks.push(Box::new(map)); + } + + pub fn map_each(&self, mut map: F) -> Dynamic + where + F: for<'a> FnMut(&'a GenerationalValue) -> R + Send + 'static, + R: Send + 'static, + { + let mut state = self.state(); + let initial_value = map(&state.wrapped); + let mapped_value = Dynamic::new(initial_value); + let returned = mapped_value.clone(); + state + .callbacks + .push(Box::new(move |updated: &GenerationalValue| { + mapped_value.set(map(updated)); + })); + + returned + } +} +struct State { + wrapped: GenerationalValue, + callbacks: Vec>>, + windows: Vec>, + readers: usize, +} + +impl Debug for State +where + T: Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("State") + .field("wrapped", &self.wrapped) + .field("readers", &self.readers) + .finish_non_exhaustive() + } +} + +trait ValueCallback: Send { + fn update(&mut self, value: &GenerationalValue); +} + +impl ValueCallback for F +where + F: for<'a> FnMut(&'a GenerationalValue) + Send + 'static, +{ + fn update(&mut self, value: &GenerationalValue) { + self(value); + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct GenerationalValue { + pub value: T, + pub generation: usize, +} + +#[derive(Debug)] +pub struct DynamicRefReader { + source: Arc>, + read_generation: usize, +} + +impl DynamicRefReader { + pub fn map_ref(&mut self, map: impl FnOnce(&T) -> R) -> R { + let state = self.source.state(); + self.read_generation = state.wrapped.generation; + map(&state.wrapped.value) + } + + #[must_use] + pub fn get(&self) -> T + where + T: Clone, + { + self.source.get().value + } + + pub fn block_until_updated(&mut self) -> bool { + let mut state = self.source.state(); + loop { + if state.wrapped.generation != self.read_generation { + return true; + } else if state.readers == Arc::strong_count(&self.source) { + return false; + } + + state = self + .source + .sync + .wait(state) + .map_or_else(PoisonError::into_inner, |g| g); + } + } + + pub fn redraw_if_changed(&mut self, window: WindowHandle) { + self.source.redraw_when_changed(window); + } +} + +impl Clone for DynamicRefReader { + fn clone(&self) -> Self { + self.source.state().readers += 1; + Self { + source: self.source.clone(), + read_generation: self.read_generation, + } + } +} + +impl Drop for DynamicRefReader { + fn drop(&mut self) { + let mut state = self.source.state(); + state.readers -= 1; + } +} + +#[test] +fn disconnecting_reader_from_dynamic() { + let value = Dynamic::new(1); + let mut ref_reader = value.create_ref_reader(); + drop(value); + assert!(!ref_reader.block_until_updated()); +} diff --git a/src/graphics.rs b/src/graphics.rs new file mode 100644 index 0000000..2aa912e --- /dev/null +++ b/src/graphics.rs @@ -0,0 +1,48 @@ +use std::ops::{Deref, DerefMut}; + +use kludgine::figures::units::UPx; +use kludgine::figures::Rect; + +pub struct Graphics<'clip, 'gfx, 'pass> { + renderer: GraphicsContext<'clip, 'gfx, 'pass>, +} + +enum GraphicsContext<'clip, 'gfx, 'pass> { + Renderer(kludgine::render::Renderer<'gfx, 'pass>), + Clipped(kludgine::ClipGuard<'clip, kludgine::render::Renderer<'gfx, 'pass>>), +} + +impl<'clip, 'gfx, 'pass> Graphics<'clip, 'gfx, 'pass> { + #[must_use] + pub fn new(renderer: kludgine::render::Renderer<'gfx, 'pass>) -> Self { + Self { + renderer: GraphicsContext::Renderer(renderer), + } + } + + pub fn clipped_to(&mut self, clip: Rect) -> Graphics<'_, 'gfx, 'pass> { + Graphics { + renderer: GraphicsContext::Clipped(self.deref_mut().clipped_to(clip)), + } + } +} + +impl<'gfx, 'pass> Deref for Graphics<'_, 'gfx, 'pass> { + type Target = kludgine::render::Renderer<'gfx, 'pass>; + + fn deref(&self) -> &Self::Target { + match &self.renderer { + GraphicsContext::Renderer(renderer) => renderer, + GraphicsContext::Clipped(clipped) => clipped, + } + } +} + +impl<'gfx, 'pass> DerefMut for Graphics<'_, 'gfx, 'pass> { + fn deref_mut(&mut self) -> &mut Self::Target { + match &mut self.renderer { + GraphicsContext::Renderer(renderer) => renderer, + GraphicsContext::Clipped(clipped) => &mut *clipped, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..db0bd6a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,37 @@ +#![warn(clippy::pedantic)] +#![allow( + clippy::module_name_repetitions, + clippy::missing_errors_doc, + clippy::missing_panics_doc +)] + +pub mod children; +pub mod context; +pub mod dynamic; +pub mod graphics; +mod tree; +mod utils; +pub mod widget; +pub mod widgets; +pub mod window; + +pub use kludgine::app::winit::error::EventLoopError; +pub use kludgine::app::winit::event::ElementState; +use kludgine::figures::units::UPx; + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum ConstraintLimit { + Known(UPx), + ClippedAfter(UPx), +} + +impl ConstraintLimit { + #[must_use] + pub fn max(self) -> UPx { + match self { + ConstraintLimit::Known(v) | ConstraintLimit::ClippedAfter(v) => v, + } + } +} + +pub type Result = std::result::Result; diff --git a/src/tree.rs b/src/tree.rs new file mode 100644 index 0000000..ef6d7ca --- /dev/null +++ b/src/tree.rs @@ -0,0 +1,251 @@ +use std::fmt::Debug; +use std::mem; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; + +use alot::{LotId, Lots}; +use kludgine::figures::units::Px; +use kludgine::figures::{Point, Rect}; + +use crate::widget::{BoxedWidget, Widget}; + +#[derive(Clone, Default)] +pub struct Tree { + data: Arc>, +} + +impl Tree { + pub fn push(&self, widget: W, parent: Option<&ManagedWidget>) -> ManagedWidget + where + W: Widget, + { + self.push_boxed(BoxedWidget::new(widget), parent) + } + + pub fn push_boxed(&self, widget: BoxedWidget, parent: Option<&ManagedWidget>) -> ManagedWidget { + let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + let id = WidgetId(data.nodes.push(Node { + widget: widget.clone(), + children: Vec::new(), + parent: parent.map(|parent| parent.id), + last_rendered_location: None, + })); + if let Some(parent) = parent { + let parent = &mut data.nodes[parent.id.0]; + parent.children.push(id); + } + ManagedWidget { + id, + widget, + tree: self.clone(), + } + } + + #[allow(clippy::needless_pass_by_value)] // This is sort of a destructor type call + pub fn remove_child(&self, child: ManagedWidget, parent: &ManagedWidget) { + let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.remove_child(child.id, parent.id); + } + + fn note_rendered_rect(&self, widget: WidgetId, rect: Rect) { + let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.nodes[widget.0].last_rendered_location = Some(rect); + data.render_order.push(widget); + } + + fn last_rendered_at(&self, widget: WidgetId) -> Option> { + let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.nodes[widget.0].last_rendered_location + } + + pub(crate) fn reset_render_order(&self) { + let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.render_order.clear(); + } + + pub fn hover(&self, new_hover: Option<&ManagedWidget>) -> Result, ()> { + let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.update_tracked_widget(new_hover, self, |data| &mut data.hover) + } + + pub fn focus(&self, new_focus: Option<&ManagedWidget>) -> Result, ()> { + let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.update_tracked_widget(new_focus, self, |data| &mut data.focus) + } + + pub fn activate( + &self, + new_active: Option<&ManagedWidget>, + ) -> Result, ()> { + let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.update_tracked_widget(new_active, self, |data| &mut data.active) + } + + pub fn widget(&self, id: WidgetId) -> ManagedWidget { + let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + ManagedWidget { + id, + widget: data.nodes[id.0].widget.clone(), + tree: self.clone(), + } + } + + pub fn active_widget(&self) -> Option { + self.data + .lock() + .map_or_else(PoisonError::into_inner, |g| g) + .active + } + + pub fn hovered_widget(&self) -> Option { + self.data + .lock() + .map_or_else(PoisonError::into_inner, |g| g) + .hover + } + + pub fn focused_widget(&self) -> Option { + self.data + .lock() + .map_or_else(PoisonError::into_inner, |g| g) + .focus + } + + pub(crate) fn widgets_at_point(&self, point: Point) -> Vec { + let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + let mut hits = Vec::new(); + for id in data.render_order.iter().rev() { + if let Some(last_rendered) = data.nodes[id.0].last_rendered_location { + if last_rendered.contains(point) { + hits.push(ManagedWidget { + id: *id, + widget: data.nodes[id.0].widget.clone(), + tree: self.clone(), + }); + } + } + } + hits + } + + pub(crate) fn parent(&self, id: WidgetId) -> Option { + let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + data.nodes[id.0].parent + } +} + +#[derive(Default)] +struct TreeData { + nodes: Lots, + active: Option, + focus: Option, + hover: Option, + render_order: Vec, +} + +impl TreeData { + fn remove_child(&mut self, child: WidgetId, parent: WidgetId) { + let removed_node = self.nodes.remove(child.0).expect("widget already removed"); + let parent = &mut self.nodes[parent.0]; + let index = parent + .children + .iter() + .enumerate() + .find_map(|(index, c)| (*c == child).then_some(index)) + .expect("child not found in parent"); + parent.children.remove(index); + let mut detached_nodes = removed_node.children; + + while let Some(node) = detached_nodes.pop() { + let mut node = self.nodes.remove(node.0).expect("detached node missing"); + detached_nodes.append(&mut node.children); + } + } + + fn update_tracked_widget( + &mut self, + new_widget: Option<&ManagedWidget>, + tree: &Tree, + property: impl FnOnce(&mut Self) -> &mut Option, + ) -> Result, ()> { + match ( + mem::replace(property(self), new_widget.map(|w| w.id)), + new_widget, + ) { + (Some(old_widget), Some(new_widget)) if old_widget == new_widget.id => Err(()), + (Some(old_widget), _) => Ok(Some(ManagedWidget { + id: old_widget, + widget: self.nodes[old_widget.0].widget.clone(), + tree: tree.clone(), + })), + (None, _) => Ok(None), + } + } +} + +#[derive(Clone)] +pub struct ManagedWidget { + pub(crate) id: WidgetId, + pub(crate) widget: BoxedWidget, + pub(crate) tree: Tree, +} + +impl Debug for ManagedWidget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ManagedWidget") + .field("id", &self.id) + .field("widget", &self.widget) + .finish_non_exhaustive() + } +} + +impl ManagedWidget { + pub(crate) fn lock(&self) -> MutexGuard<'_, dyn Widget> { + self.widget.lock() + } + + pub(crate) fn note_rendered_rect(&self, rect: Rect) { + self.tree.note_rendered_rect(self.id, rect); + } + + pub fn last_rendered_at(&self) -> Option> { + self.tree.last_rendered_at(self.id) + } + + pub fn active(&self) -> bool { + self.tree.active_widget() == Some(self.id) + } + + pub fn hovered(&self) -> bool { + self.tree.hovered_widget() == Some(self.id) + } + + pub fn focused(&self) -> bool { + self.tree.focused_widget() == Some(self.id) + } + + pub fn parent(&self) -> Option { + self.tree.parent(self.id).map(|id| self.tree.widget(id)) + } +} + +impl PartialEq for ManagedWidget { + fn eq(&self, other: &Self) -> bool { + self.widget == other.widget + } +} + +impl PartialEq for ManagedWidget { + fn eq(&self, other: &BoxedWidget) -> bool { + &self.widget == other + } +} + +pub struct Node { + pub widget: BoxedWidget, + pub children: Vec, + pub parent: Option, + pub last_rendered_location: Option>, +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub struct WidgetId(LotId); diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..ae1c89a --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,17 @@ +use kludgine::app::winit::keyboard::ModifiersState; + +pub trait ModifiersExt { + fn primary(&self) -> bool; +} + +impl ModifiersExt for ModifiersState { + #[cfg(any(target_os = "macos", target_os = "ios"))] + fn primary(&self) -> bool { + self.super_key() + } + + #[cfg(not(any(target_os = "macos", target_os = "ios")))] + fn primary(&self) -> bool { + self.control_key() + } +} diff --git a/src/widget.rs b/src/widget.rs new file mode 100644 index 0000000..e1efc3b --- /dev/null +++ b/src/widget.rs @@ -0,0 +1,235 @@ +use std::clone::Clone; +use std::fmt::Debug; +use std::ops::ControlFlow; +use std::panic::UnwindSafe; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; + +use kludgine::app::winit::error::EventLoopError; +use kludgine::app::winit::event::{DeviceId, MouseButton}; +use kludgine::figures::units::{Px, UPx}; +use kludgine::figures::{Point, Size}; + +use crate::context::Context; +use crate::dynamic::Dynamic; +use crate::graphics::Graphics; +use crate::window::{RunningWindow, Window, WindowBehavior}; +use crate::ConstraintLimit; + +pub trait Widget: Send + UnwindSafe + Debug + 'static { + fn run(self) -> Result<(), EventLoopError> + where + Self: Sized, + { + Window::>::new(WidgetWindow(Some(self))).run() + } + + fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context<'_, '_>); + + fn measure( + &mut self, + available_space: Size, + graphics: &mut Graphics<'_, '_, '_>, + context: &mut Context<'_, '_>, + ) -> Size; + + #[allow(unused_variables)] + fn hit_test(&mut self, location: Point, context: &mut Context<'_, '_>) -> bool { + false + } + + #[allow(unused_variables)] + fn hover(&mut self, location: Point, context: &mut Context<'_, '_>) {} + + #[allow(unused_variables)] + fn unhover(&mut self, context: &mut Context<'_, '_>) {} + + #[allow(unused_variables)] + fn focus(&mut self, context: &mut Context<'_, '_>) {} + + #[allow(unused_variables)] + fn blur(&mut self, context: &mut Context<'_, '_>) {} + + #[allow(unused_variables)] + fn activate(&mut self, context: &mut Context<'_, '_>) {} + + #[allow(unused_variables)] + fn deactivate(&mut self, context: &mut Context<'_, '_>) {} + + #[allow(unused_variables)] + fn mouse_down( + &mut self, + location: Point, + device_id: DeviceId, + button: MouseButton, + context: &mut Context<'_, '_>, + ) -> EventHandling { + UNHANDLED + } + + #[allow(unused_variables)] + fn mouse_drag( + &mut self, + location: Point, + device_id: DeviceId, + button: MouseButton, + context: &mut Context<'_, '_>, + ) { + } + + #[allow(unused_variables)] + fn mouse_up( + &mut self, + location: Option>, + device_id: DeviceId, + button: MouseButton, + context: &mut Context<'_, '_>, + ) { + } +} + +pub type EventHandling = ControlFlow; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct EventHandled; +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct EventIgnored; + +pub const HANDLED: EventHandling = EventHandling::Break(EventHandled); +pub const UNHANDLED: EventHandling = EventHandling::Continue(EventIgnored); + +struct WidgetWindow(Option); + +impl WindowBehavior for WidgetWindow +where + T: Widget + Send + UnwindSafe, +{ + type Context = Self; + + fn initialize(_window: &mut RunningWindow<'_>, context: Self::Context) -> Self { + context + } + + fn make_root(&mut self, tree: &crate::tree::Tree) -> crate::tree::ManagedWidget { + tree.push(self.0.take().expect("root already created"), None) + } +} + +#[derive(Clone, Debug)] +pub struct BoxedWidget(Arc>); + +impl BoxedWidget { + pub fn new(widget: W) -> Self + where + W: Widget, + { + Self(Arc::new(Mutex::new(widget))) + } + + pub(crate) fn lock(&self) -> MutexGuard<'_, dyn Widget> { + self.0.lock().map_or_else(PoisonError::into_inner, |g| g) + } +} + +impl Eq for BoxedWidget {} + +impl PartialEq for BoxedWidget { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +#[derive(Debug)] +pub enum Value +where + T: 'static, +{ + Static(T), + Dynamic(Dynamic), +} + +impl Value { + pub fn map(&mut self, map: impl FnOnce(&T) -> R) -> R { + match self { + Value::Static(value) => map(value), + Value::Dynamic(dynamic) => dynamic.map_ref(map), + } + } + + pub fn map_mut(&mut self, map: impl FnOnce(&mut T) -> R) -> R { + match self { + Value::Static(value) => map(value), + Value::Dynamic(dynamic) => dynamic.map_mut(map), + } + } + + pub fn get(&mut self) -> T + where + T: Clone, + { + self.map(Clone::clone) + } + + pub fn generation(&self) -> Option { + match self { + Value::Static(_) => None, + Value::Dynamic(value) => Some(value.generation()), + } + } +} + +pub trait IntoValue { + fn into_value(self) -> Value; +} + +impl IntoValue for T { + fn into_value(self) -> Value { + Value::Static(self) + } +} + +impl<'a> IntoValue for &'a str { + fn into_value(self) -> Value { + Value::Static(self.to_owned()) + } +} + +impl IntoValue for Dynamic { + fn into_value(self) -> Value { + Value::Dynamic(self) + } +} + +pub struct Callback(Box>); + +impl Debug for Callback { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Callback") + .field(&(self as *const Self)) + .finish() + } +} + +impl Callback { + pub fn new(function: F) -> Self + where + F: FnMut(T) + Send + UnwindSafe + 'static, + { + Self(Box::new(function)) + } + + pub fn invoke(&mut self, value: T) { + self.0.invoke(value); + } +} + +trait CallbackFunction: Send + UnwindSafe { + fn invoke(&mut self, value: T); +} +impl CallbackFunction for F +where + F: FnMut(T) + Send + UnwindSafe, +{ + fn invoke(&mut self, value: T) { + self(value); + } +} diff --git a/src/widgets.rs b/src/widgets.rs new file mode 100644 index 0000000..73a582a --- /dev/null +++ b/src/widgets.rs @@ -0,0 +1,8 @@ +pub mod array; +mod button; +mod canvas; +mod label; + +pub use button::Button; +pub use canvas::Canvas; +pub use label::Label; diff --git a/src/widgets/array.rs b/src/widgets/array.rs new file mode 100644 index 0000000..0f77f75 --- /dev/null +++ b/src/widgets/array.rs @@ -0,0 +1,575 @@ +use std::ops::Deref; + +use alot::{LotId, OrderedLots}; +use kludgine::figures::units::UPx; +use kludgine::figures::{Point, Rect, Size}; + +use crate::children::Children; +use crate::context::Context; +use crate::graphics::Graphics; +use crate::tree::ManagedWidget; +use crate::widget::{IntoValue, Value, Widget}; +use crate::ConstraintLimit; + +#[derive(Debug)] +pub struct Array { + pub direction: Value, + pub children: Value, + layout: Layout, + layout_generation: Option, + synced_children: Vec, +} + +impl Array { + pub fn new( + direction: impl IntoValue, + children: impl IntoValue, + ) -> Self { + let mut direction = direction.into_value(); + + let initial_direction = direction.get(); + + Self { + direction, + children: children.into_value(), + layout: Layout::new(initial_direction), + layout_generation: None, + synced_children: Vec::new(), + } + } + + pub fn columns(children: impl IntoValue) -> Self { + Self::new(ArrayDirection::columns(), children) + } + + pub fn rows(children: impl IntoValue) -> Self { + Self::new(ArrayDirection::rows(), children) + } + + fn synchronize_children(&mut self, context: &mut Context<'_, '_>) { + let current_generation = self.children.generation(); + if current_generation.map_or_else( + || self.children.map(Children::len) != self.layout.children.len(), + |gen| Some(gen) != self.layout_generation, + ) { + self.layout_generation = self.children.generation(); + self.children.map(|children| { + for (index, widget) in children.iter().enumerate() { + if self + .synced_children + .get(index) + .map_or(true, |child| child != widget) + { + // These entries do not match. See if we can find the + // new id somewhere else, if so we can swap the entries. + if let Some((swap_index, _)) = self + .synced_children + .iter() + .enumerate() + .skip(index + 1) + .find(|(_, child)| *child == widget) + { + self.synced_children.swap(index, swap_index); + self.layout.swap(index, swap_index); + } else { + // This is a brand new child. + self.synced_children + .insert(index, context.push_child(widget.clone())); + self.layout.insert(index, ArrayDimension::FitContent); + } + } + } + + // Any children remaining at the end of this process are ones + // that have been removed. + for removed in self.synced_children.drain(children.len()..) { + context.remove_child(removed); + } + self.layout.truncate(children.len()); + }); + } + } +} + +impl Widget for Array { + fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context) { + self.synchronize_children(context); + self.layout.update( + Size::new( + ConstraintLimit::Known(graphics.size().width), + ConstraintLimit::Known(graphics.size().height), + ), + |child_index, constraints| { + context + .for_other(&self.synced_children[child_index]) + .measure(constraints, graphics) + }, + ); + + for (index, layout) in self.layout.iter().enumerate() { + let child = &self.synced_children[index]; + if layout.size > 0 { + let mut clipped = graphics.clipped_to(Rect::new( + self.layout.orientation.make_point(layout.offset, UPx(0)), + self.layout + .orientation + .make_size(layout.size, self.layout.other), + )); + context.for_other(child).redraw(&mut clipped); + } + } + } + + fn measure( + &mut self, + available_space: Size, + graphics: &mut Graphics<'_, '_, '_>, + context: &mut Context<'_, '_>, + ) -> Size { + self.synchronize_children(context); + + self.layout + .update(available_space, |child_index, constraints| { + context + .for_other(&self.synced_children[child_index]) + .measure(constraints, graphics) + }) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] + +pub enum ArrayDirection { + Row { reverse: bool }, + Column { reverse: bool }, +} + +impl ArrayDirection { + #[must_use] + pub const fn columns() -> Self { + Self::Column { reverse: false } + } + + #[must_use] + pub const fn columns_rev() -> Self { + Self::Column { reverse: true } + } + + #[must_use] + pub const fn rows() -> Self { + Self::Row { reverse: false } + } + + #[must_use] + pub const fn rows_rev() -> Self { + Self::Row { reverse: true } + } + + pub fn split_size(&self, s: Size) -> (U, U) { + match self { + Self::Row { .. } => (s.height, s.width), + Self::Column { .. } => (s.width, s.height), + } + } + + pub fn make_size(&self, measured: U, other: U) -> Size { + match self { + Self::Row { .. } => Size::new(other, measured), + Self::Column { .. } => Size::new(measured, other), + } + } + + pub fn make_point(&self, measured: U, other: U) -> Point { + match self { + Self::Row { .. } => Point::new(other, measured), + Self::Column { .. } => Point::new(measured, other), + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ArrayDimension { + FitContent, + Fractional { weight: u8 }, + Exact(UPx), +} + +#[derive(Debug)] +struct Layout { + children: OrderedLots, + layouts: Vec, + pub other: UPx, + total_weights: u32, + allocated_space: UPx, + fractional: Vec<(LotId, u8)>, + measured: Vec, + pub orientation: ArrayDirection, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +struct ArrayLayout { + pub offset: UPx, + pub size: UPx, +} + +impl Layout { + pub const fn new(orientation: ArrayDirection) -> Self { + Self { + orientation, + children: OrderedLots::new(), + layouts: Vec::new(), + other: UPx(0), + total_weights: 0, + allocated_space: UPx(0), + fractional: Vec::new(), + measured: Vec::new(), + } + } + + #[cfg(test)] // only used in testing + pub fn push(&mut self, child: ArrayDimension) { + self.insert(self.len(), child); + } + + pub fn remove(&mut self, index: usize) -> ArrayDimension { + let (id, dimension) = self.children.remove_by_index(index).expect("invalid index"); + self.layouts.remove(index); + + match dimension { + ArrayDimension::FitContent => { + self.measured.retain(|&measured| measured != id); + } + ArrayDimension::Fractional { weight } => { + self.fractional.retain(|(measured, _)| *measured != id); + self.total_weights -= u32::from(weight); + } + ArrayDimension::Exact(size) => { + self.allocated_space -= size; + } + } + + dimension + } + + pub fn truncate(&mut self, new_length: usize) { + while self.len() > new_length { + self.remove(self.len() - 1); + } + } + + pub fn swap(&mut self, a: usize, b: usize) { + self.children.swap(a, b); + } + + pub fn insert(&mut self, index: usize, child: ArrayDimension) { + let id = self.children.insert(index, child); + let layout = match child { + ArrayDimension::FitContent => { + self.measured.push(id); + UPx(0) + } + ArrayDimension::Fractional { weight } => { + self.total_weights += u32::from(weight); + self.fractional.push((id, weight)); + UPx(0) + } + ArrayDimension::Exact(size) => { + self.allocated_space += size; + size + } + }; + self.layouts.insert( + index, + ArrayLayout { + offset: UPx(0), + size: layout, + }, + ); + } + + pub fn update( + &mut self, + available: Size, + mut measure: impl FnMut(usize, Size) -> Size, + ) -> Size { + let (space_constraint, other_constraint) = self.orientation.split_size(available); + let available_space = space_constraint.max(); + let mut remaining = available_space.saturating_sub(self.allocated_space); + + // Measure the children that fit their content + for &id in &self.measured { + let index = self.children.index_of_id(id).expect("child not found"); + if remaining > 0 { + let (measured, _) = self.orientation.split_size(measure( + index, + self.orientation + .make_size(ConstraintLimit::ClippedAfter(remaining), other_constraint), + )); + self.layouts[index].size = measured; + remaining = remaining.saturating_sub(measured); + } else { + self.layouts[index].size = UPx(0); + } + } + + // Measure the weighted children within the remaining space + if self.total_weights > 0 { + let space_per_weight = remaining / self.total_weights; + remaining %= self.total_weights; + for (fractional_index, &(id, weight)) in self.fractional.iter().enumerate() { + let index = self.children.index_of_id(id).expect("child not found"); + let size = space_per_weight * u32::from(weight); + self.layouts[index].size = size; + + // If we have fractional amounts remaining, divide the pixels + if remaining > 0 { + let from_end = u32::try_from(self.fractional.len() - fractional_index) + .expect("too many items"); + if remaining >= from_end { + let amount = (remaining + from_end - 1) / from_end; + remaining -= amount; + self.layouts[index].size += amount; + } + } + } + } + + // Now that we know the constrained sizes, we can measure the children + // to get the other measurement using the constrainted measurement. + self.other = UPx(0); + let mut offset = UPx(0); + for index in 0..self.children.len() { + self.layouts[index].offset = offset; + offset += self.layouts[index].size; + let (_, measured) = self.orientation.split_size(measure( + index, + self.orientation.make_size( + ConstraintLimit::Known(self.layouts[index].size), + other_constraint, + ), + )); + self.other = self.other.max(measured); + } + + self.other = match other_constraint { + ConstraintLimit::Known(max) => self.other.max(max), + ConstraintLimit::ClippedAfter(clip_limit) => self.other.min(clip_limit), + }; + + self.orientation.make_size(available_space, self.other) + } +} + +impl Deref for Layout { + type Target = [ArrayLayout]; + + fn deref(&self) -> &Self::Target { + &self.layouts + } +} + +#[cfg(test)] +mod tests { + use std::cmp::Ordering; + + use kludgine::figures::units::UPx; + use kludgine::figures::Size; + + use super::{ArrayDimension, ArrayDirection, Layout}; + use crate::ConstraintLimit; + + struct Child { + size: UPx, + dimension: ArrayDimension, + other: UPx, + divisible_by: Option, + } + + impl Child { + pub fn new(size: impl Into, other: impl Into) -> Self { + Self { + size: size.into(), + dimension: ArrayDimension::FitContent, + other: other.into(), + divisible_by: None, + } + } + + pub fn fixed_size(mut self, size: UPx) -> Self { + self.dimension = ArrayDimension::Exact(size); + self + } + + pub fn weighted(mut self, weight: u8) -> Self { + self.dimension = ArrayDimension::Fractional { weight }; + self + } + + pub fn divisible_by(mut self, split_at: impl Into) -> Self { + self.divisible_by = Some(split_at.into()); + self + } + } + + fn assert_measured_children_in_orientation( + orientation: ArrayDirection, + children: &[Child], + available: Size, + expected: &[UPx], + expected_size: Size, + ) { + assert_eq!(children.len(), expected.len()); + let mut flex = Layout::new(orientation); + for child in children { + flex.push(child.dimension); + } + + let computed_size = flex.update(available, |index, constraints| { + let (measured_constraint, _other_constraint) = orientation.split_size(constraints); + let child = &children[index]; + let maximum_measured = measured_constraint.max(); + let (measured, other) = match (child.size.cmp(&maximum_measured), child.divisible_by) { + (Ordering::Greater, Some(divisible_by)) => { + let available_divided = maximum_measured / divisible_by; + let rows = ((child.size + divisible_by - 1) / divisible_by + available_divided + - 1) + / available_divided; + (available_divided * divisible_by, child.other * rows) + } + _ => (child.size, child.other), + }; + orientation.make_size(measured, other) + }); + assert_eq!(computed_size, expected_size); + let mut offset = UPx(0); + for ((index, &child), &expected) in flex.iter().enumerate().zip(expected) { + assert_eq!( + child.size, + expected, + "child {index} measured to {}, expected {}", + child.size, + expected // TODO Display for UPx + ); + assert_eq!(child.offset, offset); + offset += child.size; + } + } + + fn assert_measured_children( + children: &[Child], + main_constraint: ConstraintLimit, + other_constraint: ConstraintLimit, + expected: &[UPx], + expected_measured: UPx, + expected_other: UPx, + ) { + assert_measured_children_in_orientation( + ArrayDirection::rows(), + children, + ArrayDirection::rows().make_size(main_constraint, other_constraint), + expected, + ArrayDirection::rows().make_size(expected_measured, expected_other), + ); + assert_measured_children_in_orientation( + ArrayDirection::columns(), + children, + ArrayDirection::columns().make_size(main_constraint, other_constraint), + expected, + ArrayDirection::columns().make_size(expected_measured, expected_other), + ); + } + + #[test] + fn size_to_fit() { + assert_measured_children( + &[Child::new(3, 1), Child::new(3, 1), Child::new(3, 1)], + ConstraintLimit::ClippedAfter(UPx(10)), + ConstraintLimit::ClippedAfter(UPx(10)), + &[UPx(3), UPx(3), UPx(3)], + UPx(10), + UPx(1), + ); + } + + #[test] + fn wrapping() { + // This tests some fun rounding edge cases. Because the total weights is + // 4 and the size is 10, we have inexact math to determine the pixel + // width of each child. + // + // In this particular example, it shows the weights are clamped so that + // each is credited for 2px. This is why the first child ends up with + // 4px. However, with 4 total weight, that leaves a remaining 2px to be + // assigned. The flex algorithm divides the remaining pixels amongst the + // remaining children. + assert_measured_children( + &[ + Child::new(20, 1).divisible_by(3).weighted(2), + Child::new(3, 1).weighted(1), + Child::new(3, 1).weighted(1), + ], + ConstraintLimit::Known(UPx(10)), + ConstraintLimit::ClippedAfter(UPx(10)), + &[UPx(4), UPx(3), UPx(3)], + UPx(10), + UPx(7), // 20 / 3 = 6.666, rounded up is 7 + ); + // Same as above, but with an 11px box. This creates a leftover of 3 px + // (11 % 4), adding 1px to all three children. + assert_measured_children( + &[ + Child::new(20, 1).divisible_by(3).weighted(2), + Child::new(3, 1).weighted(1), + Child::new(3, 1).weighted(1), + ], + ConstraintLimit::Known(UPx(11)), + ConstraintLimit::ClippedAfter(UPx(11)), + &[UPx(5), UPx(3), UPx(3)], + UPx(11), + UPx(7), // 20 / 3 = 6.666, rounded up is 7 + ); + // 12px box. This creates no leftover. + assert_measured_children( + &[ + Child::new(20, 1).divisible_by(3).weighted(2), + Child::new(3, 1).weighted(1), + Child::new(3, 1).weighted(1), + ], + ConstraintLimit::Known(UPx(12)), + ConstraintLimit::ClippedAfter(UPx(12)), + &[UPx(6), UPx(3), UPx(3)], + UPx(12), + UPx(4), // 20 / 6 = 3.666, rounded up is 4 + ); + // 13px box. This creates a leftover of 1 px (13 % 4), adding 1px only + // to the final child + assert_measured_children( + &[ + Child::new(20, 1).divisible_by(3).weighted(2), + Child::new(3, 1).weighted(1), + Child::new(3, 1).weighted(1), + ], + ConstraintLimit::Known(UPx(13)), + ConstraintLimit::ClippedAfter(UPx(13)), + &[UPx(6), UPx(3), UPx(4)], + UPx(13), + UPx(4), // 20 / 6 = 3.666, rounded up is 4 + ); + } + + #[test] + fn fixed_size() { + assert_measured_children( + &[ + Child::new(3, 1).fixed_size(UPx(7)), + Child::new(3, 1).weighted(1), + Child::new(3, 1).weighted(1), + ], + ConstraintLimit::Known(UPx(15)), + ConstraintLimit::ClippedAfter(UPx(15)), + &[UPx(7), UPx(4), UPx(4)], + UPx(15), + UPx(1), + ); + } +} diff --git a/src/widgets/button.rs b/src/widgets/button.rs new file mode 100644 index 0000000..aefc478 --- /dev/null +++ b/src/widgets/button.rs @@ -0,0 +1,188 @@ +use std::panic::UnwindSafe; + +use kludgine::app::winit::event::{DeviceId, MouseButton}; +use kludgine::figures::units::{Px, UPx}; +use kludgine::figures::{Point, Rect, Size}; +use kludgine::shapes::{Shape, StrokeOptions}; +use kludgine::Color; + +use crate::context::Context; +use crate::graphics::Graphics; +use crate::widget::{Callback, EventHandling, IntoValue, Value, Widget, HANDLED}; + +#[derive(Debug)] +pub struct Button { + pub label: Value, + pub on_click: Option>, + buttons_pressed: usize, +} + +impl Button { + pub fn new(label: impl IntoValue) -> Self { + Self { + label: label.into_value(), + on_click: None, + buttons_pressed: 0, + } + } + + #[must_use] + pub fn on_click(mut self, callback: F) -> Self + where + F: FnMut(()) + Send + UnwindSafe + 'static, + { + self.on_click = Some(Callback::new(callback)); + self + } +} + +impl Widget for Button { + fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context) { + let center = Point::from(graphics.size()) / 2; + if let Value::Dynamic(label) = &self.label { + context.redraw_when_changed(label); + } + + let visible_rect = Rect::from(graphics.size() - (UPx(1), UPx(1))); + + let background = if context.active() { + Color::new(30, 30, 30, 255) + } else if context.hovered() { + Color::new(40, 40, 40, 255) + } else { + Color::new(10, 10, 10, 255) + }; + let background = Shape::filled_rect(visible_rect, background); + graphics.draw_shape(&background, Point::default(), None, None); + + if context.focused() { + let focus_ring = + Shape::stroked_rect(visible_rect, Color::AQUA, StrokeOptions::default()); + graphics.draw_shape(&focus_ring, Point::default(), None, None); + } + + let width = graphics.size().width; + self.label.map(|label| { + graphics.draw_text( + label, + Color::WHITE, + kludgine::text::TextOrigin::Center, + center, + None, + None, + Some(width), + ); + }); + } + + fn hit_test(&mut self, _location: Point, _context: &mut Context<'_, '_>) -> bool { + true + } + + fn mouse_down( + &mut self, + _location: Point, + _device_id: DeviceId, + _button: MouseButton, + context: &mut Context<'_, '_>, + ) -> EventHandling { + self.buttons_pressed += 1; + context.activate(); + HANDLED + } + + fn mouse_drag( + &mut self, + location: Point, + _device_id: DeviceId, + _button: MouseButton, + context: &mut Context<'_, '_>, + ) { + let changed = if Rect::from( + context + .last_rendered_at() + .expect("must have been rendered") + .size, + ) + .contains(location) + { + context.activate() + } else { + context.deactivate() + }; + + if changed { + context.set_needs_redraw(); + } + } + + fn mouse_up( + &mut self, + location: Option>, + _device_id: DeviceId, + _button: MouseButton, + context: &mut Context<'_, '_>, + ) { + self.buttons_pressed -= 1; + if self.buttons_pressed == 0 { + context.deactivate(); + + if let Some(location) = location { + if Rect::from( + context + .last_rendered_at() + .expect("must have been rendered") + .size, + ) + .contains(location) + { + context.focus(); + + if let Some(on_click) = self.on_click.as_mut() { + on_click.invoke(()); + } + } + } + } + } + + fn measure( + &mut self, + available_space: Size, + graphics: &mut Graphics<'_, '_, '_>, + _context: &mut Context<'_, '_>, + ) -> Size { + let width = available_space.width.max().try_into().unwrap_or(Px::MAX); + self.label.map(|label| { + graphics + .measure_text::(label, Color::RED, Some(width)) + .size + .try_cast::() + .unwrap_or_default() + }) + } + + fn unhover(&mut self, context: &mut Context<'_, '_>) { + context.set_needs_redraw(); + } + + fn hover(&mut self, _location: Point, context: &mut Context<'_, '_>) { + context.set_needs_redraw(); + } + + fn focus(&mut self, context: &mut Context<'_, '_>) { + context.set_needs_redraw(); + } + + fn blur(&mut self, context: &mut Context<'_, '_>) { + context.set_needs_redraw(); + } + + fn activate(&mut self, context: &mut Context<'_, '_>) { + context.set_needs_redraw(); + } + + fn deactivate(&mut self, context: &mut Context<'_, '_>) { + context.set_needs_redraw(); + } +} diff --git a/src/widgets/canvas.rs b/src/widgets/canvas.rs new file mode 100644 index 0000000..6234eab --- /dev/null +++ b/src/widgets/canvas.rs @@ -0,0 +1,90 @@ +use std::fmt::Debug; +use std::panic::UnwindSafe; +use std::time::{Duration, Instant}; + +use kludgine::figures::units::UPx; +use kludgine::figures::Size; + +use crate::context::Context; +use crate::graphics::Graphics; +use crate::widget::Widget; + +#[must_use] +pub struct Canvas { + render: Box, + target_frame_duration: Option, + last_frame_time: Option, +} + +impl Canvas { + pub fn new(render: F) -> Self + where + F: for<'clip, 'gfx, 'pass, 'context, 'window> FnMut( + &mut Graphics<'clip, 'gfx, 'pass>, + &mut Context<'context, 'window>, + ) + Send + + UnwindSafe + + 'static, + { + Self { + render: Box::new(render), + target_frame_duration: None, + last_frame_time: None, + } + } + + pub fn target_fps(mut self, fps: u16) -> Self { + const ONE_SECOND_NS: u64 = 1_000_000_000; + let frame_duration = ONE_SECOND_NS / u64::from(fps); + self.target_frame_duration = Some(Duration::from_nanos(frame_duration)); + self + } +} + +impl Widget for Canvas { + fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context<'_, '_>) { + self.render.render(graphics, context); + + if let Some(target_frame_duration) = self.target_frame_duration { + let now = Instant::now(); + let max_target = now + target_frame_duration; + let next_frame_target = self.last_frame_time.map_or(max_target, |last_frame_time| { + max_target.max(last_frame_time + target_frame_duration) + }); + context.redraw_at(next_frame_target); + } + } + + fn measure( + &mut self, + available_space: Size, + _graphics: &mut Graphics<'_, '_, '_>, + _context: &mut Context<'_, '_>, + ) -> Size { + Size::new(available_space.width.max(), available_space.height.max()) + } +} + +impl Debug for Canvas { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Canvas").finish_non_exhaustive() + } +} + +trait RenderFunction: Send + UnwindSafe + 'static { + fn render(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context<'_, '_>); +} + +impl RenderFunction for F +where + F: for<'clip, 'gfx, 'pass, 'context, 'window> FnMut( + &mut Graphics<'clip, 'gfx, 'pass>, + &mut Context<'context, 'window>, + ) + Send + + UnwindSafe + + 'static, +{ + fn render(&mut self, graphics: &mut Graphics<'_, '_, '_>, window: &mut Context<'_, '_>) { + self(graphics, window); + } +} diff --git a/src/widgets/label.rs b/src/widgets/label.rs new file mode 100644 index 0000000..dc06a42 --- /dev/null +++ b/src/widgets/label.rs @@ -0,0 +1,58 @@ +use kludgine::figures::units::{Px, UPx}; +use kludgine::figures::{Point, Size}; +use kludgine::text::TextOrigin; +use kludgine::Color; + +use crate::context::Context; +use crate::graphics::Graphics; +use crate::widget::{IntoValue, Value, Widget}; + +#[derive(Debug)] +pub struct Label { + pub contents: Value, +} + +impl Label { + pub fn new(contents: impl IntoValue) -> Self { + Self { + contents: contents.into_value(), + } + } +} + +impl Widget for Label { + fn redraw(&mut self, graphics: &mut Graphics<'_, '_, '_>, context: &mut Context<'_, '_>) { + let center = Point::from(graphics.size()) / 2; + if let Value::Dynamic(contents) = &mut self.contents { + context.redraw_when_changed(contents); + } + let width = graphics.size().width; + self.contents.map(|contents| { + graphics.draw_text( + contents, + Color::RED, + TextOrigin::Center, + center, + None, + None, + Some(width), + ); + }); + } + + fn measure( + &mut self, + available_space: Size, + graphics: &mut Graphics<'_, '_, '_>, + _context: &mut Context<'_, '_>, + ) -> Size { + let width = available_space.width.max().try_into().unwrap_or(Px::MAX); + self.contents.map(|contents| { + graphics + .measure_text(contents, Color::RED, Some(width)) + .size + .try_cast() + .unwrap_or_default() + }) + } +} diff --git a/src/window.rs b/src/window.rs new file mode 100644 index 0000000..57fba10 --- /dev/null +++ b/src/window.rs @@ -0,0 +1,424 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::panic::{AssertUnwindSafe, UnwindSafe}; + +use kludgine::app::winit::dpi::PhysicalPosition; +use kludgine::app::winit::error::EventLoopError; +use kludgine::app::winit::event::{DeviceId, ElementState, MouseButton}; +use kludgine::app::winit::keyboard::KeyCode; +use kludgine::app::WindowBehavior as _; +use kludgine::figures::units::Px; +use kludgine::figures::Point; +use kludgine::render::Drawing; + +use crate::context::Context; +use crate::graphics::Graphics; +use crate::tree::{ManagedWidget, Tree}; +use crate::utils::ModifiersExt; +use crate::widget::{EventHandling, HANDLED, UNHANDLED}; +use crate::window::sealed::WindowCommand; + +pub type RunningWindow<'window> = kludgine::app::Window<'window, WindowCommand>; +pub type WindowAttributes = kludgine::app::WindowAttributes; + +pub struct Window +where + Behavior: WindowBehavior, +{ + context: Behavior::Context, + pub attributes: WindowAttributes, +} + +impl Default for Window +where + Behavior: WindowBehavior, + Behavior::Context: Default, +{ + fn default() -> Self { + let context = Behavior::Context::default(); + Self::new(context) + } +} + +impl Window +where + Behavior: WindowBehavior, +{ + pub fn new(context: Behavior::Context) -> Self { + Self { + attributes: WindowAttributes::default(), + context, + } + } + + pub fn run(self) -> Result<(), EventLoopError> { + GooeyWindow::::run_with(AssertUnwindSafe(( + self.context, + RefCell::new(Some(self.attributes)), + ))) + } +} + +pub trait WindowBehavior: Sized + 'static { + type Context: UnwindSafe + Send + 'static; + + fn initialize(window: &mut RunningWindow<'_>, context: Self::Context) -> Self; + + fn make_root(&mut self, tree: &Tree) -> ManagedWidget; + + #[allow(unused_variables)] + fn close_requested(&self, window: &mut RunningWindow<'_>) -> bool { + true + } + + fn run() -> Result<(), EventLoopError> + where + Self::Context: Default, + { + Self::run_with(::default()) + } + + fn run_with(context: Self::Context) -> Result<(), EventLoopError> { + GooeyWindow::::run_with(AssertUnwindSafe(( + context, + RefCell::new(Some(WindowAttributes { + title: String::from("Gooey Application"), + ..WindowAttributes::default() + })), + ))) + } +} + +struct GooeyWindow { + behavior: T, + root: ManagedWidget, + contents: Drawing, + should_close: bool, + mouse_state: MouseState, +} + +impl GooeyWindow +where + T: WindowBehavior, +{ + fn request_close(&mut self, window: &mut RunningWindow<'_>) -> bool { + self.should_close |= self.behavior.close_requested(window); + + self.should_close + } +} + +impl kludgine::app::WindowBehavior for GooeyWindow +where + T: WindowBehavior, +{ + type Context = AssertUnwindSafe<(T::Context, RefCell>)>; + + fn initialize( + mut window: RunningWindow<'_>, + _graphics: &mut kludgine::Graphics<'_>, + context: Self::Context, + ) -> Self { + let mut behavior = T::initialize(&mut window, context.0 .0); + let root = behavior.make_root(&Tree::default()); + Self { + behavior, + root, + contents: Drawing::default(), + should_close: false, + mouse_state: MouseState { + location: None, + widget: None, + devices: HashMap::default(), + }, + } + } + + fn prepare(&mut self, mut window: RunningWindow<'_>, graphics: &mut kludgine::Graphics<'_>) { + graphics.reset_text_attributes(); + self.root.tree.reset_render_order(); + let graphics = self.contents.new_frame(graphics); + let mut context = Context::new(&self.root, &mut window); + context.redraw(&mut Graphics::new(graphics)); + } + + fn render<'pass>( + &'pass mut self, + _window: RunningWindow<'_>, + graphics: &mut kludgine::RenderingGraphics<'_, 'pass>, + ) -> bool { + self.contents.render(graphics); + + !self.should_close + } + + fn initial_window_attributes(context: &Self::Context) -> WindowAttributes { + context + .1 + .borrow_mut() + .take() + .expect("called more than once") + } + + fn close_requested(&mut self, mut window: RunningWindow<'_>) -> bool { + self.request_close(&mut window) + } + + // fn power_preference() -> wgpu::PowerPreference { + // wgpu::PowerPreference::default() + // } + + // fn limits(adapter_limits: wgpu::Limits) -> wgpu::Limits { + // wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter_limits) + // } + + // fn clear_color() -> Option { + // Some(kludgine::Color::BLACK) + // } + + // fn focus_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} + + // fn occlusion_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} + + // fn scale_factor_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} + + // fn resized(&mut self, window: kludgine::app::Window<'_, ()>) {} + + // fn theme_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} + + // fn dropped_file(&mut self, window: kludgine::app::Window<'_, ()>, path: std::path::PathBuf) {} + + // fn hovered_file(&mut self, window: kludgine::app::Window<'_, ()>, path: std::path::PathBuf) {} + + // fn hovered_file_cancelled(&mut self, window: kludgine::app::Window<'_, ()>) {} + + // fn received_character(&mut self, window: kludgine::app::Window<'_, ()>, char: char) {} + + fn keyboard_input( + &mut self, + mut window: RunningWindow<'_>, + _device_id: DeviceId, + input: kludgine::app::winit::event::KeyEvent, + _is_synthetic: bool, + ) { + if !input.state.is_pressed() { + match input.physical_key { + KeyCode::KeyW if window.modifiers().state().primary() => { + if self.request_close(&mut window) { + window.set_needs_redraw(); + } + } + _ => {} + } + } + } + + // fn modifiers_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} + + // fn ime( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // ime: kludgine::app::winit::event::Ime, + // ) { + // } + + fn cursor_moved( + &mut self, + mut window: RunningWindow<'_>, + device_id: DeviceId, + position: PhysicalPosition, + ) { + let location = Point::::from(position); + self.mouse_state.location = Some(location); + + if let Some(state) = self.mouse_state.devices.get(&device_id) { + // Mouse Drag + for (button, handler) in state { + let mut context = Context::new(handler, &mut window); + let last_rendered_at = context.last_rendered_at().expect("passed hit test"); + context.mouse_drag(location - last_rendered_at.origin, device_id, *button); + } + } else { + // Hover + let mut context = Context::new(&self.root, &mut window); + for widget in self.root.tree.widgets_at_point(location) { + let mut widget_context = context.for_other(&widget); + let relative = location + - widget_context + .last_rendered_at() + .expect("passed hit test") + .origin; + + if widget_context.hit_test(relative) { + widget_context.hover(relative); + drop(widget_context); + self.mouse_state.widget = Some(widget); + break; + } + } + } + } + + // fn cursor_entered( + // &mut self, + // window: RunningWindow<'_>, + // device_id: DeviceId, + // ) { + // } + + fn cursor_left(&mut self, mut window: RunningWindow<'_>, _device_id: DeviceId) { + if self.mouse_state.widget.take().is_some() { + let mut context = Context::new(&self.root, &mut window); + context.clear_hover(); + } + } + + // fn mouse_wheel( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // device_id: kludgine::app::winit::event::DeviceId, + // delta: kludgine::app::winit::event::MouseScrollDelta, + // phase: kludgine::app::winit::event::TouchPhase, + // ) { + // } + + fn mouse_input( + &mut self, + mut window: RunningWindow<'_>, + device_id: DeviceId, + state: ElementState, + button: MouseButton, + ) { + match state { + ElementState::Pressed => { + Context::new(&self.root, &mut window).clear_focus(); + + if let (ElementState::Pressed, Some(location), Some(hovered)) = + (state, &self.mouse_state.location, &self.mouse_state.widget) + { + if let Some(handler) = recursively_handle_event( + &mut Context::new(hovered, &mut window), + |context| { + let relative = *location + - context.last_rendered_at().expect("passed hit test").origin; + context.mouse_down(relative, device_id, button) + }, + ) { + self.mouse_state + .devices + .entry(device_id) + .or_default() + .insert(button, handler); + } + } + } + ElementState::Released => { + let Some(device_buttons) = self.mouse_state.devices.get_mut(&device_id) else { + return; + }; + let Some(handler) = device_buttons.remove(&button) else { + return; + }; + if device_buttons.is_empty() { + self.mouse_state.devices.remove(&device_id); + } + + let mut context = Context::new(&handler, &mut window); + + let relative = if let (Some(last_rendered), Some(location)) = + (context.last_rendered_at(), self.mouse_state.location) + { + Some(location - last_rendered.origin) + } else { + None + }; + + context.mouse_up(relative, device_id, button); + } + } + } + + // fn touchpad_pressure( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // device_id: kludgine::app::winit::event::DeviceId, + // pressure: f32, + // stage: i64, + // ) { + // } + + // fn axis_motion( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // device_id: kludgine::app::winit::event::DeviceId, + // axis: kludgine::app::winit::event::AxisId, + // value: f64, + // ) { + // } + + // fn touch( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // touch: kludgine::app::winit::event::Touch, + // ) { + // } + + // fn touchpad_magnify( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // device_id: kludgine::app::winit::event::DeviceId, + // delta: f64, + // phase: kludgine::app::winit::event::TouchPhase, + // ) { + // } + + // fn smart_magnify( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // device_id: kludgine::app::winit::event::DeviceId, + // ) { + // } + + // fn touchpad_rotate( + // &mut self, + // window: kludgine::app::Window<'_, ()>, + // device_id: kludgine::app::winit::event::DeviceId, + // delta: f32, + // phase: kludgine::app::winit::event::TouchPhase, + // ) { + // } + + fn event(&mut self, event: WindowCommand, mut window: RunningWindow<'_>) { + match event { + WindowCommand::Redraw => { + window.set_needs_redraw(); + } + } + } +} + +fn recursively_handle_event( + context: &mut Context<'_, '_>, + mut each_widget: impl FnMut(&mut Context<'_, '_>) -> EventHandling, +) -> Option { + match each_widget(context) { + HANDLED => Some(context.widget().clone()), + UNHANDLED => context.parent().and_then(|parent| { + recursively_handle_event(&mut context.for_other(&parent), each_widget) + }), + } +} + +#[derive(Default)] +struct MouseState { + location: Option>, + widget: Option, + devices: HashMap>, +} + +pub(crate) mod sealed { + pub enum WindowCommand { + Redraw, + // RequestClose, + } +}